175

I have a tuple called values which contains the following:

('275', '54000', '0.0', '5000.0', '0.0')

I want to change the first value (i.e., 275) in this tuple but I understand that tuples are immutable so values[0] = 200 will not work. How can I achieve this?

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
Dawood
  • 5,106
  • 4
  • 23
  • 27

18 Answers18

239

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

kevinarpe
  • 20,319
  • 26
  • 127
  • 154
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
  • 5
    One use case is if you are storing a large number of small sequences where the values rarely change but on few occasions they might want to. For a small but non-zero length sequence, the memory consumption of tuple (60-bytes for one-element) vs list (104 bytes) and make a difference. Another use case is for namedtuples since namedlist doesn't natively exist. – Michael Scott Asato Cuthbert Aug 02 '15 at 17:46
  • (scratch the last use case; there is namedtuple._replace, which isn't really a private method, just a hack to avoid namespace clashes). – Michael Scott Asato Cuthbert Aug 02 '15 at 18:51
  • And if you are using `namedtuple`, you can use the `_replace()` method. – Aaron S Nov 07 '16 at 04:44
  • 140
    The "why do you want to do that" responses drive me nuts on StackOverflow. Don't assume the original poster "want"s to do that. Actually, it is better not to assume that the original poster is creating all of this from scratch doesn't know any better. More often we are dealing with output from another module or data source in a format or type that we cant control. – rtphokie Jan 06 '18 at 17:15
  • Example, I found my way here because I'm dealing with dates produced by a variety of modules and trying to get them all into a Pandas dataframe. One module outputs a datetime type, another a numpy array of year, month, day, hour, minute, and float second, still another a similar tuple. Rounding each to a common precision is a pain because of the tuples. – rtphokie Jan 06 '18 at 17:15
  • 13
    @rtphokie wanting to mutate an immutable container (therefore the "why" is a very valid question) is different than interpreting different formats some of which may be a tuple. Thanks for venting though :) – Jon Clements Jan 06 '18 at 17:19
  • 8
    It seems unnecessary and memory inefficient to recast into a list and use a temporary variable. You can just unpack into a tuple with the same name and while unpacking update whatever needs updating. – Brian Spiering Oct 05 '18 at 03:23
  • 10
    @JonClements You make a very valuable point that doing this is bad practice. That *should* be in your answer. However rhetorical questions are often interpreted as unnecessarily derogatory. Such information is better structured in the form: *"This is bad practice because..."* or even *"Consider carefully if you really need this; it usually implies a fault in the design because..."* – Philip Couling Apr 03 '19 at 11:43
  • I want to do this because my sql connector gives me a tuple for each row, and I want to modify the row before I write it to a csv. "You have to cast it to a list" is a perfectly fine answer, I don't understand what the "why are you doing this" adds to the answer either. Don't you have to ask why you're doing anything before you code? – Sasha Kondrashov Jun 04 '19 at 23:06
  • 3
    While an immutable value is not supposed to be mutated, one might want to a new value with a single index updated. How to do this conveniently might not be apparent, and pose this question. As tuples contain fixed length data where each index contains a very specific value, a convenience function similar to `namedtuple._replace` could be helpful. – andho Sep 10 '19 at 22:10
  • this is creating a new tuple, not really changing original – Jason Feb 11 '20 at 05:06
  • 1
    @rtphokie Me too. I removed it. – kevinarpe Jul 23 '21 at 12:26
96

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).

Dave Halter
  • 15,556
  • 13
  • 76
  • 103
  • 4
    This is the best answer to me. I'd love to see this implemented in python by default, as analogous to `NamedTuple._replace(fieldname=fieldvalue)`: `tuple._replace(fieldindex, fieldvalue)` – Eduardo Pignatelli Mar 11 '21 at 16:31
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!

sjakobi
  • 3,546
  • 1
  • 25
  • 43
  • 1
    @ErikAronesty not always. Once useful case is extending a class that you can not change and whose methods returns a tuple from which you want to change just the first element. return (val,) + res[1:] is clearer than res2=list(res); res2[0] = val; return tuple(res2) – yucer Jul 16 '18 at 17:26
  • I'd be interested to see timings for @BrianSpiering's answer. I guess it'd be (much?) faster than concatenation, and probably faster than converting to lists and back. – drevicko Jan 06 '21 at 04:44
  • I did the timing, and BrianSpiering's method came out consistently slower than the other two for all 3 cases here, though not so different to the tuple concatenation method (Python 3.7.6 on linux) – drevicko Jan 06 '21 at 06:59
18

It is possible with a one liner:

values = ('275', '54000', '0.0', '5000.0', '0.0')
values = ('300', *values[1:])
Brian Spiering
  • 1,002
  • 1
  • 9
  • 18
  • 1
    How would you change only the third element in `values` with this? – sdbbs Oct 22 '19 at 12:03
  • 4
    Here is how you could change any element in a tuple - `i = 2; values = (*values[:i], '300', *values[i+1:])` – Brian Spiering Oct 22 '19 at 13:59
  • 1
    This is a nicely elegant solution, but it turns out that it's a trifle slower than the tuple concatenation method in @DaveHalter's answer. – drevicko Jan 06 '21 at 07:01
12

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
fuglede
  • 17,388
  • 2
  • 54
  • 99
  • 5
    Always good to know something different than the usual answers. Cheers ! – Kartheek Palepu Aug 22 '18 at 05:48
  • People who want to code in C should probably just do exactly that. Hacking the interpreter like that just misses the topic here. It is also unreliable, since the cPython implementation details can change any time without warning and is likely to break any code which relies on tuples beeing imutable. Furthermore, tuples are the most lightweight collection objects in python, so there is no problem with creating a new one. If you absolute need to modify a collection frequently, use a list instead. – Bachsau Apr 02 '19 at 08:15
  • 2
    You forgot to decrement the reference count to the value you're discarding. That'll cause leakage. – wizzwizz4 Nov 09 '19 at 20:27
  • Doesn't python keep unique id's for tuples? Doing this, you may end up with two (different) tuples with the same id. Depending on how CPython manages this uniqueness, a new tuple with these values may end up with a different id (so it wouldn't evaluate as equal to the modified one). – drevicko Jan 06 '21 at 04:42
  • 1
    @Bachsau The question asks how to change a tuple not replace it with a new one. I definitely agree with all your points, but I would argue this question is bad not this response. The question asks to change an immutable type which seems paradoxical. – Daemon Jan 30 '23 at 01:34
  • @Bachsau Technically, this is the only answer that *gets* the topic here. All the other ones provide a workaround for a question that, when precisely considered, is something you probably won't want in practice almost ever. If this answer doesn't seem useful but exactly answers the question, perhaps it would be worth you editing the precise question to be a bit more practical. – Kröw Feb 02 '23 at 23:49
9

As Hunter McMillen mentioned, tuples are immutable, you need to create a new tuple in order to achieve this. For instance:

>>> tpl = ('275', '54000', '0.0', '5000.0', '0.0')
>>> change_value = 200
>>> tpl = (change_value,) + tpl[1:]
>>> tpl
(200, '54000', '0.0', '5000.0', '0.0')
SuperNova
  • 25,512
  • 7
  • 93
  • 64
8

Not that this is superior, but if anyone is curious it can be done on one line with:

tuple = tuple([200 if i == 0 else _ for i, _ in enumerate(tuple)])
bphi
  • 3,115
  • 3
  • 23
  • 36
  • 2
    Is that faster than ```tuple = tuple(200 if i == 0 else _ for i, _ in enumerate(tuple))```? (Why not generator comprehension?) – LHeng Dec 01 '19 at 08:28
4

You can't modify items in tuple, but you can modify properties of mutable objects in tuples (for example if those objects are lists or actual class objects)

For example

my_list = [1,2]
tuple_of_lists = (my_list,'hello')
print(tuple_of_lists) # ([1, 2], 'hello')
my_list[0] = 0
print(tuple_of_lists) # ([0, 2], 'hello')
gunit
  • 3,700
  • 4
  • 31
  • 42
2

based on Jon's Idea and dear Trufa

def modifyTuple(tup, oldval, newval):
    lst=list(tup)
    for i in range(tup.count(oldval)):
        index = lst.index(oldval)
        lst[index]=newval

    return tuple(lst)

print modTupByIndex((1, 1, 3), 1, "a")

it changes all of your old values occurrences

Community
  • 1
  • 1
Pooya
  • 4,385
  • 6
  • 45
  • 73
  • This would be quite uncomfortable (for the lack of a better word) if you were to change multiple values but then again, why would you want to be modifying a tuple in the first place... – Trufa Jul 12 '12 at 18:39
  • @Trufa yes, I'm trying to write it :D – Pooya Jul 12 '12 at 18:42
  • The method name modify_tuple_by_index is inaccurate and bound to cause confusion. – msw Jul 14 '12 at 15:25
2

EDIT: This doesn't work on tuples with duplicate entries yet!!

Based on Pooya's idea:

If you are planning on doing this often (which you shouldn't since tuples are inmutable for a reason) you should do something like this:

def modTupByIndex(tup, index, ins):
    return tuple(tup[0:index]) + (ins,) + tuple(tup[index+1:])

print modTupByIndex((1,2,3),2,"a")

Or based on Jon's idea:

def modTupByIndex(tup, index, ins):
    lst = list(tup)
    lst[index] = ins
    return tuple(lst)

print modTupByIndex((1,2,3),1,"a")
Community
  • 1
  • 1
Trufa
  • 39,971
  • 43
  • 126
  • 190
1

You can't. If you want to change it, you need to use a list instead of a tuple.

Note that you could instead make a new tuple that has the new value as its first element.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
1

Frist, ask yourself why you want to mutate your tuple. There is a reason why strings and tuple are immutable in Ptyhon, if you want to mutate your tuple then it should probably be a list instead.

Second, if you still wish to mutate your tuple then you can convert your tuple to a list then convert it back, and reassign the new tuple to the same variable. This is great if you are only going to mutate your tuple once. Otherwise, I personally think that is counterintuitive. Because It is essentially creating a new tuple and every time if you wish to mutate the tuple you would have to perform the conversion. Also If you read the code it would be confusing to think why not just create a list? But it is nice because it doesn't require any library.

I suggest using mutabletuple(typename, field_names, default=MtNoDefault) from mutabletuple 0.2. I personally think this way is a more intuitive and readable. The personal reading the code would know that writer intends to mutate this tuple in the future. The downside compares to the list conversion method above is that this requires you to import additional py file.

from mutabletuple import mutabletuple

myTuple = mutabletuple('myTuple', 'v w x y z')
p = myTuple('275', '54000', '0.0', '5000.0', '0.0')
print(p.v) #print 275
p.v = '200' #mutate myTuple
print(p.v) #print 200

TL;DR: Don't try to mutate tuple. if you do and it is a one-time operation convert tuple to list, mutate it, turn list into a new tuple, and reassign back to the variable holding old tuple. If desires tuple and somehow want to avoid listand want to mutate more than once then create mutabletuple.

OLIVER.KOO
  • 5,654
  • 3
  • 30
  • 62
0

I've found the best way to edit tuples is to recreate the tuple using the previous version as the base.

Here's an example I used for making a lighter version of a colour (I had it open already at the time):

colour = tuple([c+50 for c in colour])

What it does, is it goes through the tuple 'colour' and reads each item, does something to it, and finally adds it to the new tuple.

So what you'd want would be something like:

values = ('275', '54000', '0.0', '5000.0', '0.0')

values  = (tuple(for i in values: if i = 0: i = 200 else i = values[i])

That specific one doesn't work, but the concept is what you need.

tuple = (0, 1, 2)

tuple = iterate through tuple, alter each item as needed

that's the concept.

Rajan Chauhan
  • 1,378
  • 1
  • 13
  • 32
Aedus
  • 27
  • 1
  • 8
0

I´m late to the game but I think the simplest, resource-friendliest and fastest way (depending on the situation), is to overwrite the tuple itself. Since this would remove the need for the list & variable creation and is archived in one line.

new = 24
t = (1, 2, 3)
t = (t[0],t[1],new)

>>> (1, 2, 24)

But: This is only handy for rather small tuples and also limits you to a fixed tuple value, nevertheless, this is the case for tuples most of the time anyway.

So in this particular case it would look like this:

new = '200'
t = ('275', '54000', '0.0', '5000.0', '0.0')
t = (new, t[1], t[2], t[3], t[4])

>>> ('200', '54000', '0.0', '5000.0', '0.0')
  • this only works if this is a static tuple with known length. not code will most likely fail sooner or later because its too specific... – patroqueeet Dec 30 '19 at 07:48
  • yes - @patroqueeet, Therefore I clearly stated the downsides of this approach, after But:...-. Please reconsider your downvote, thanks ;) – GordanTrevis Dec 30 '19 at 20:43
  • 1
    hey, re-considered and clicked the button. but vote is now locked by SO :/ – patroqueeet Feb 14 '20 at 09:52
0

If you want to do this, you probably don't want to toss a bunch of weird functions all over the place and call attention to you wanting to change values in things specific unable to do that. Also, we can go ahead and assume you're not being efficient.

t = tuple([new_value if p == old_value else p for p in t])
Tatarize
  • 10,238
  • 4
  • 58
  • 64
0

Inspired by @fuglede : With NumPy, it is actually quite easy to modify a tuple. Just copy the values of buffer a to buffer b. The tuples are usable after modifying them, but it is surely safer not to do it. (Don't try this at home)

import ctypes
import sys
import numpy as np


def changetuples(v, new, ind):
    # tuple value to buffer
    buff = (ctypes.c_uint8 * (sys.getsizeof(v[ind]))).from_address(id(v[ind]))
    ax = np.frombuffer(buff, dtype=np.uint8)

    # new value to buffer
    buff2 = (ctypes.c_uint8 * (sys.getsizeof(new))).from_address(id(new))
    ax2 = np.frombuffer(buff2, dtype=np.uint8)

    # copying each byte from one buffer to another
    for i in range(len(ax2)):
        try:
            ax[i] = ax2[i]
        except Exception:
            return False
    return True


t = ("275", 54000, "0.0", "5000.0", "0.0")
newvar = "200"  # the new string needs to be  <=  the old string
index = 0
print(id(t), t)
changetuples(v=t, new=newvar, ind=index)
print(id(t), t)
index = 1
newvar = 20.1
changetuples(v=t, new=newvar, ind=index)
print(id(t), t)
2424588439760 ('275', 54000, '0.0', '5000.0', '0.0')
2424588439760 ('200', 54000, '0.0', '5000.0', '0.0')
2424588439760 ('200', 20.1, '0.0', '5000.0', '0.0')
Hans
  • 148
  • 2
  • 7
-2

i did this:

list = [1,2,3,4,5]
tuple = (list)

and to change, just do

list[0]=6

and u can change a tuple :D

here is it copied exactly from IDLE

>>> list=[1,2,3,4,5,6,7,8,9]

>>> tuple=(list)

>>> print(tuple)

[1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> list[0]=6

>>> print(tuple)

[6, 2, 3, 4, 5, 6, 7, 8, 9]
lxg
  • 12,375
  • 12
  • 51
  • 73
-5

You can change the value of tuple using copy by reference

>>> tuple1=[20,30,40]

>>> tuple2=tuple1

>>> tuple2
    [20, 30, 40]

>>> tuple2[1]=10

>>> print(tuple2)
    [20, 10, 40]

>>> print(tuple1)
    [20, 10, 40]
garfbradaz
  • 3,424
  • 7
  • 43
  • 70
Rohit
  • 27
  • 3
  • 3
    Only that these are lists, not tuples, which you can change anyway, given a second reference or not. – Bachsau Dec 16 '18 at 09:00