Python: changer de valeur dans un tuple


124

Je suis nouveau sur python, donc cette question pourrait être un peu basique. J'ai un tuple appelé valuesqui contient ce qui suit:

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

Je veux changer la première valeur (c'est-à-dire 275) dans ce tuple mais je comprends que les tuples sont immuables et values[0] = 200ne fonctionneront donc pas. Comment puis-je atteindre cet objectif?


24
les tuples sont immuables , vous devez créer un nouveau tuple pour y parvenir.
Hunter McMillen

Réponses:


178

Vous devez d'abord vous demander pourquoi vous voulez faire cela?

Mais c'est possible via:

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

Mais si vous avez besoin de changer les choses, vous feriez probablement mieux de le garder list


4
Un cas d'utilisation est si vous stockez un grand nombre de petites séquences où les valeurs changent rarement, mais à de rares occasions, elles le souhaitent. Pour une séquence de longueur petite mais non nulle, la consommation de mémoire du tuple (60 octets pour un élément) par rapport à la liste (104 octets) fait une différence. Un autre cas d'utilisation est celui des namedtuples puisque namedlist n'existe pas nativement.
Michael Scott Cuthbert

81
Les réponses «pourquoi voulez-vous faire cela» me rendent fou sur StackOverflow. Ne supposez pas que l'affiche originale «veut» faire cela. En fait, il vaut mieux ne pas supposer que l'affiche originale crée tout cela à partir de zéro ne sait pas mieux. Le plus souvent, nous avons affaire à la sortie d'un autre module ou source de données dans un format ou un type que nous ne pouvons pas contrôler.
rtphokie

9
@rtphokie veut muter un conteneur immuable (donc le "pourquoi" est une question très valable) est différent de l'interprétation de différents formats dont certains peuvent être un tuple. Merci de vous avoir évacué :)
Jon Clements

7
Il semble inutile et mémoire inefficace de refondre dans une liste et d'utiliser une variable temporaire. Vous pouvez simplement décompresser dans un tuple avec le même nom et lors de la décompression, mettre à jour tout ce qui doit être mis à jour.
Brian Spiering

5
@JonClements Vous faites un point très précieux sur le fait que cela est une mauvaise pratique. Cela devrait figurer dans votre réponse. Cependant, les questions rhétoriques sont souvent interprétées comme inutilement péjoratives. Ces informations sont mieux structurées sous la forme: "C'est une mauvaise pratique parce que ..." ou même "Réfléchissez bien si vous en avez vraiment besoin; cela implique généralement un défaut de conception parce que ..."
Philip Couling

74

En fonction de votre problème, le tranchage peut être une solution vraiment intéressante:

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

Cela vous permet d'ajouter plusieurs éléments ou aussi de remplacer quelques éléments (surtout s'ils sont "voisins". Dans le cas ci-dessus, le cast dans une liste est probablement plus approprié et lisible (même si la notation de découpage est beaucoup plus courte).


24

Eh bien, comme Trufa l'a déjà montré, il existe essentiellement deux façons de remplacer l'élément d'un tuple à un index donné. Convertissez le tuple en liste, remplacez l'élément et reconvertissez-le, ou construisez un nouveau tuple par concaténation.

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:]
   ...:

Alors, quelle méthode est la meilleure, c'est-à-dire la plus rapide?

Il s'avère que pour les tuples courts (sur Python 3.3), la concaténation est en fait plus rapide!

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

Pourtant, si nous regardons des tuples plus longs, la conversion de liste est la voie à suivre:

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

Pour les tuples très longs, la conversion de liste est nettement meilleure!

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

De plus, les performances de la méthode de concaténation dépendent de l'index auquel nous remplaçons l'élément. Pour la méthode de la liste, l'index n'est pas pertinent.

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

Donc: si votre tuple est court, découpez et concaténez. Si c'est long, faites la conversion de liste!


1
@ErikAronesty pas toujours. Une fois utile, le cas est d'étendre une classe que vous ne pouvez pas modifier et dont les méthodes retournent un tuple à partir duquel vous souhaitez modifier uniquement le premier élément. return (val,) + res [1:] est plus clair que res2 = list (res); res2 [0] = val; return tuple (res2)
yucer

9

C'est possible avec une seule doublure:

values = ('275', '54000', '0.0', '5000.0', '0.0')
values = ('300', *values[1:])

1
Comment changeriez-vous seulement le troisième élément valuesavec cela?
sdbbs

2
Voici comment changer n'importe quel élément d'un tuple -i = 2; values = (*values[:i], '300', *values[i+1:])
Brian Spiering

8

Non pas que ce soit supérieur, mais si quelqu'un est curieux, cela peut être fait sur une seule ligne avec:

tuple = tuple([200 if i == 0 else _ for i, _ in enumerate(tuple)])

Est-ce plus rapide que tuple = tuple(200 if i == 0 else _ for i, _ in enumerate(tuple))? (Pourquoi pas la compréhension du générateur?)
Anonyme

8

Je pense que cela répond techniquement à la question, mais ne le faites pas à la maison. Pour le moment, toutes les réponses impliquent la création d'un nouveau tuple, mais vous pouvez l'utiliser ctypespour modifier un tuple en mémoire. En vous appuyant sur divers détails d'implémentation de CPython sur un système 64 bits, une façon de le faire est la suivante:

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

1
Il est toujours bon de savoir quelque chose de différent des réponses habituelles. À votre santé !
Kartheek Palepu

Les gens qui veulent coder en C devraient probablement faire exactement cela. Le piratage de l'interprète comme ça manque juste le sujet ici. Il est également peu fiable, car les détails de l'implémentation de cPython peuvent changer à tout moment sans avertissement et sont susceptibles de casser tout code qui repose sur des tuples imutables. De plus, les tuples sont les objets de collection les plus légers en python, il n'y a donc aucun problème à en créer un nouveau. Si vous avez absolument besoin de modifier fréquemment une collection, utilisez plutôt une liste.
Bachsau

1
Vous avez oublié de décrémenter le nombre de références à la valeur que vous supprimez. Cela provoquera des fuites.
wizzwizz4

6

Comme Hunter McMillen l'a écrit dans les commentaires, les tuples sont immuables, vous devez créer un nouveau tuple pour y parvenir. Par exemple:

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

3

EDIT: Cela ne fonctionne pas sur les tuples avec des entrées en double !!

Basé sur l'idée de Pooya :

Si vous prévoyez de faire cela souvent (ce que vous ne devriez pas car les tuples sont inmubles pour une raison), vous devriez faire quelque chose comme ceci:

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

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

Ou basé sur l'idée de Jon :

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

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

3

Premièrement, demandez-vous pourquoi vous voulez muter votre tuple. Il y a une raison pour laquelle les chaînes et le tuple sont immuables dans Ptyhon , si vous voulez muter votre tuplealors il devrait probablement être un à la listplace.

Deuxièmement, si vous souhaitez toujours muter votre tuple, vous pouvez le convertir tupleen a, listpuis le reconvertir et réaffecter le nouveau tuple à la même variable. C'est génial si vous n'allez faire muter votre tuple qu'une seule fois . Sinon, je pense personnellement que c'est contre-intuitif. Parce que cela crée essentiellement un nouveau tuple et à chaque fois que vous souhaitez muter le tuple, vous devrez effectuer la conversion. Aussi, si vous lisez le code, il serait déroutant de penser pourquoi ne pas simplement créer un list? Mais c'est bien car il ne nécessite aucune bibliothèque.

Je suggère d'utiliser mutabletuple(typename, field_names, default=MtNoDefault)de mutabletuple 0.2 . Je pense personnellement que cette façon est plus intuitive et lisible. La lecture personnelle du code saurait que l'auteur a l'intention de muter ce tuple à l'avenir. L'inconvénient par rapport à la listméthode de conversion ci-dessus est que cela vous oblige à importer un fichier py supplémentaire.

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 : N'essayez pas de muter tuple. si vous le faites et que c'est une opération unique, convertissez-la tupleen liste, faites-la muter, transformez-la listen une nouvelle tupleet réaffectez-la à la variable contenant old tuple. Si les désirs tupleet d'une manière ou d'une autre veulent éviter listet veulent muter plus d'une fois, créez mutabletuple.


2

basé sur l'idée de Jon et la chère 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")

il modifie toutes vos anciennes occurrences de valeurs


Ce serait assez inconfortable (faute de meilleur mot) si vous deviez changer plusieurs valeurs, mais encore une fois, pourquoi voudriez-vous modifier un tuple en premier lieu ...
Trufa

@Trufa oui, j'essaye de l'écrire: D
Pooya

Le nom de la méthode modify_tuple_by_index est inexact et risque de prêter à confusion.
msw

1

Vous ne pouvez pas. Si vous souhaitez le modifier, vous devez utiliser une liste au lieu d'un tuple.

Notez que vous pouvez à la place créer un nouveau tuple qui a la nouvelle valeur comme premier élément.


0

J'ai trouvé que le meilleur moyen de modifier les tuples est de recréer le tuple en utilisant la version précédente comme base.

Voici un exemple que j'ai utilisé pour créer une version plus claire d'une couleur (je l'avais déjà ouverte à l'époque):

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

Ce qu'il fait, c'est qu'il passe par le tuple 'color' et lit chaque élément, y fait quelque chose et l'ajoute finalement au nouveau tuple.

Donc, ce que vous voudriez serait quelque chose comme:

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

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

Celui-là spécifique ne fonctionne pas, mais le concept est ce dont vous avez besoin.

tuple = (0, 1, 2)

tuple = itérer dans le tuple, modifier chaque élément selon les besoins

c'est ça le concept.


0

Je suis en retard au jeu mais je pense que le moyen le plus simple , le plus convivial et le plus rapide (selon la situation) , est d'écraser le tuple lui-même. Étant donné que cela supprimerait le besoin de création de liste et de variable et est archivé sur une seule ligne.

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

>>> (1, 2, 24)

Mais: Ceci n'est pratique que pour les tuples plutôt petits et vous limite également à une valeur de tuple fixe, néanmoins, c'est le cas pour les tuples la plupart du temps de toute façon.

Donc, dans ce cas particulier, cela ressemblerait à ceci:

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

cela ne fonctionne que s'il s'agit d'un tuple statique de longueur connue. pas le code échouera probablement tôt ou tard parce que c'est trop spécifique ...
patroqueeet

oui - @patroqueeet, J'ai donc clairement énoncé les inconvénients de cette approche, après Mais: ...-. Veuillez reconsidérer votre
vote défavorable

1
hey, reconsidéré et cliqué sur le bouton. mais le vote est maintenant verrouillé par SO: /
patroqueeet le

0

tldr; la "solution de contournement" est de créer un nouvel objet tuple, sans modifier réellement l'original

Bien que ce soit une question très ancienne, quelqu'un m'a parlé de cette folie des tuples mutants de Python. Ce qui m'a beaucoup surpris / intrigué, et en faisant quelques recherches sur Google, j'ai atterri ici (et d'autres échantillons similaires en ligne)

J'ai fait un test pour prouver ma théorie

La note ==valorise l'égalité tandis isque l'égalité référentielle (est obj a la même instance que obj b)

a = ("apple", "canana", "cherry")
b = tuple(["apple", "canana", "cherry"])
c = a

print("a: " + str(a))
print("b: " + str(b))
print("c: " + str(c))
print("a == b :: %s" % (a==b))
print("b == c :: %s" % (b==c))
print("a == c :: %s" % (a==c))
print("a is b :: %s" % (a is b))
print("b is c :: %s" % (b is c))
print("a is c :: %s" % (a is c))

d = list(a)
d[1] = "kiwi"
a = tuple(d)

print("a: " + str(a))
print("b: " + str(b))
print("c: " + str(c))
print("a == b :: %s" % (a==b))
print("b == c :: %s" % (b==c))
print("a == c :: %s" % (a==c))
print("a is b :: %s" % (a is b))
print("b is c :: %s" % (b is c))
print("a is c :: %s" % (a is c))

Rendements:

a: ('apple', 'canana', 'cherry')
b: ('apple', 'canana', 'cherry')
c: ('apple', 'canana', 'cherry')
a == b :: True
b == c :: True
a == c :: True
a is b :: False
b is c :: False
a is c :: True
a: ('apple', 'kiwi', 'cherry')
b: ('apple', 'canana', 'cherry')
c: ('apple', 'canana', 'cherry')
a == b :: False
b == c :: True
a == c :: False
a is b :: False
b is c :: False
a is c :: False

0

Vous ne pouvez pas modifier les éléments dans le tuple, mais vous pouvez modifier les propriétés des objets mutables dans les tuples (par exemple, si ces objets sont des listes ou des objets de classe réels)

Par exemple

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

-2

j'ai fait ça:

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

et pour changer, fais juste

list[0]=6

et vous pouvez changer un tuple: D

le voici copié exactement de 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]

2
tuple est une liste, pas un tuple. x = (y)ne fait rien mais assigne y à x.
m4tx

2
aussi en utilisant le nom "tuple" et "list" comme variables, vous rendrez difficile la comparaison des types de tuple et de liste plus tard.
Michael Scott Cuthbert

-4

Vous pouvez changer la valeur de tuple en utilisant la copie par référence

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

>>> tuple2=tuple1

>>> tuple2
    [20, 30, 40]

>>> tuple2[1]=10

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

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

1
Seulement que ce sont des listes, pas des tuples, que vous pouvez quand même changer, avec une deuxième référence ou non.
Bachsau
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.