The Python Oracle

How to change values in a tuple?

--------------------------------------------------
Hire the world's top talent on demand or became one of them at Toptal: https://topt.al/25cXVn
--------------------------------------------------

Music by Eric Matyas
https://www.soundimage.org
Track title: Puzzle Game 3 Looping

--

Chapters
00:00 How To Change Values In A Tuple?
00:24 Accepted Answer Score 247
00:39 Answer 2 Score 29
01:59 Answer 3 Score 101
02:22 Answer 4 Score 13
02:55 Thank you

--

Full question
https://stackoverflow.com/questions/1145...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python #tuples

#avk47



ACCEPTED ANSWER

Score 247


It's possible via:

t = ('275', '54000', '0.0', '5000.0', '0.0')
lst = list(t)
lst[0] = '300'
t = tuple(lst)

But if you're going to need to change things, you probably are better off keeping it as a list




ANSWER 2

Score 101


Depending on your problem slicing can be a really neat solution:

>>> b = (1, 2, 3, 4, 5)
>>> b[:2] + (8,9) + b[3:]
(1, 2, 8, 9, 4, 5)
>>> b[:2] + (8,) + b[3:]
(1, 2, 8, 4, 5)

This allows you to add multiple elements or also to replace a few elements (especially if they are "neighbours". In the above case casting to a list is probably more appropriate and readable (even though the slicing notation is much shorter).




ANSWER 3

Score 29


Well, as Trufa has already shown, there are basically two ways of replacing a tuple's element at a given index. Either convert the tuple to a list, replace the element and convert back, or construct a new tuple by concatenation.

In [1]: def replace_at_index1(tup, ix, val):
   ...:     lst = list(tup)
   ...:     lst[ix] = val
   ...:     return tuple(lst)
   ...:

In [2]: def replace_at_index2(tup, ix, val):
   ...:     return tup[:ix] + (val,) + tup[ix+1:]
   ...:

So, which method is better, that is, faster?

It turns out that for short tuples (on Python 3.3), concatenation is actually faster!

In [3]: d = tuple(range(10))

In [4]: %timeit replace_at_index1(d, 5, 99)
1000000 loops, best of 3: 872 ns per loop

In [5]: %timeit replace_at_index2(d, 5, 99)
1000000 loops, best of 3: 642 ns per loop

Yet if we look at longer tuples, list conversion is the way to go:

In [6]: k = tuple(range(1000))

In [7]: %timeit replace_at_index1(k, 500, 99)
100000 loops, best of 3: 9.08 µs per loop

In [8]: %timeit replace_at_index2(k, 500, 99)
100000 loops, best of 3: 10.1 µs per loop

For very long tuples, list conversion is substantially better!

In [9]: m = tuple(range(1000000))

In [10]: %timeit replace_at_index1(m, 500000, 99)
10 loops, best of 3: 26.6 ms per loop

In [11]: %timeit replace_at_index2(m, 500000, 99)
10 loops, best of 3: 35.9 ms per loop

Also, performance of the concatenation method depends on the index at which we replace the element. For the list method, the index is irrelevant.

In [12]: %timeit replace_at_index1(m, 900000, 99)
10 loops, best of 3: 26.6 ms per loop

In [13]: %timeit replace_at_index2(m, 900000, 99)
10 loops, best of 3: 49.2 ms per loop

So: If your tuple is short, slice and concatenate. If it's long, do the list conversion!




ANSWER 4

Score 13


I believe this technically answers the question, but don't do this at home. At the moment, all answers involve creating a new tuple, but you can use ctypes to modify a tuple in-memory. Relying on various implementation details of CPython on a 64-bit system, one way to do this is as follows:

def modify_tuple(t, idx, new_value):
    # `id` happens to give the memory address in CPython; you may
    # want to use `ctypes.addressof` instead.
    element_ptr = (ctypes.c_longlong).from_address(id(t) + (3 + idx)*8)
    element_ptr.value = id(new_value)
    # Manually increment the reference count to `new_value` to pretend that
    # this is not a terrible idea.
    ref_count = (ctypes.c_longlong).from_address(id(new_value))
    ref_count.value += 1

t = (10, 20, 30)
modify_tuple(t, 1, 50)   # t is now (10, 50, 30)
modify_tuple(t, -1, 50)  # Will probably crash your Python runtime