How to change values in a tuple?
Asked Answered
H

18

181

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?

Haveman answered 12/7, 2012 at 18:27 Comment(1)
tuples are immutable, you need to create a new tuple in order to achieve this.Hedberg
E
245

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

Enumerate answered 12/7, 2012 at 18:29 Comment(12)
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.Amplitude
(scratch the last use case; there is namedtuple._replace, which isn't really a private method, just a hack to avoid namespace clashes).Amplitude
And if you are using namedtuple, you can use the _replace() method.Laclos
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.Expostulatory
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.Expostulatory
@Expostulatory 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 :)Enumerate
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.Terryl
@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..."Richardo
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?Volga
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.Kaminsky
this is creating a new tuple, not really changing originalSterilant
@Expostulatory Me too. I removed it.Polyandry
A
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).

Afghan answered 27/3, 2014 at 0:53 Comment(1)
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)Activity
M
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!

Medeah answered 23/11, 2013 at 10:14 Comment(3)
@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)Perot
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.Erelia
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)Erelia
T
19

It is possible with a one liner:

values = ('275', '54000', '0.0', '5000.0', '0.0')
values = ('300', *values[1:])
Terryl answered 5/10, 2018 at 3:21 Comment(3)
How would you change only the third element in values with this?Jessjessa
Here is how you could change any element in a tuple - i = 2; values = (*values[:i], '300', *values[i+1:])Terryl
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.Erelia
A
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
Akee answered 15/8, 2017 at 17:33 Comment(6)
Always good to know something different than the usual answers. Cheers !Musketeer
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.Bikol
You forgot to decrement the reference count to the value you're discarding. That'll cause leakage.Sexual
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).Erelia
@Bikol 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.Armchair
@Bikol 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.Selfpropulsion
H
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')
Hydrophone answered 13/6, 2016 at 8:36 Comment(0)
B
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)])
Baalbek answered 11/11, 2016 at 19:28 Comment(1)
Is that faster than tuple = tuple(200 if i == 0 else _ for i, _ in enumerate(tuple))? (Why not generator comprehension?)Crenellate
C
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')
Chao answered 12/7, 2020 at 7:31 Comment(1)
Interesting. Thanks for that. It seems that a tuple's elements are references to other data objects.Manhattan
C
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

Coyote answered 12/7, 2012 at 18:37 Comment(3)
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...Foxtail
@Foxtail yes, I'm trying to write it :DCoyote
The method name modify_tuple_by_index is inaccurate and bound to cause confusion.Jannette
F
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")
Foxtail answered 12/7, 2012 at 18:58 Comment(0)
W
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.

Whomever answered 12/7, 2012 at 18:29 Comment(0)
E
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.

Essayist answered 15/8, 2017 at 18:54 Comment(0)
S
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.

Swetlana answered 18/3, 2016 at 3:48 Comment(0)
B
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')
Boundary answered 1/9, 2019 at 12:49 Comment(3)
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...Boarder
yes - @patroqueeet, Therefore I clearly stated the downsides of this approach, after But:...-. Please reconsider your downvote, thanks ;)Boundary
hey, re-considered and clicked the button. but vote is now locked by SO :/Boarder
O
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])
Odell answered 21/12, 2022 at 22:48 Comment(0)
E
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')
Elegance answered 18/4, 2023 at 14:33 Comment(0)
H
-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]
Horsewoman answered 1/11, 2014 at 21:11 Comment(2)
tuple is a list, not a tuple. x = (y) does nothing but assigns y to x.Springclean
also by using the name "tuple" and "list" as variables, you'll make it hard to compare tuple and list types later.Amplitude
D
-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]
Donofrio answered 2/10, 2018 at 14:54 Comment(1)
Only that these are lists, not tuples, which you can change anyway, given a second reference or not.Bikol

© 2022 - 2024 — McMap. All rights reserved.