Python: modifica del valore in una tupla


124

Sono nuovo in Python, quindi questa domanda potrebbe essere un po 'di base. Ho una tupla chiamata valuesche contiene quanto segue:

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

Voglio cambiare il primo valore (cioè 275) in questa tupla ma capisco che le tuple sono immutabili, quindi values[0] = 200non funzioneranno. Come posso raggiungere questo obiettivo?


24
le tuple sono immutabili , è necessario creare una nuova tupla per ottenere ciò.
Hunter McMillen

Risposte:


178

Per prima cosa devi chiedere, perché vuoi farlo?

Ma è possibile tramite:

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

Ma se hai bisogno di cambiare le cose, probabilmente faresti meglio a tenerlo come file list


4
Un caso d'uso è se si memorizza un numero elevato di piccole sequenze in cui i valori cambiano raramente ma in poche occasioni potrebbero volerlo. Per una sequenza di lunghezza piccola ma diversa da zero, il consumo di memoria della tupla (60 byte per un elemento) rispetto alla lista (104 byte) e fa la differenza. Un altro caso d'uso è per namedtuples poiché namedlist non esiste in modo nativo.
Michael Scott Cuthbert

81
Le risposte "perché vuoi farlo" mi fanno impazzire su StackOverflow. Non dare per scontato che il poster originale "voglia" farlo. In realtà, è meglio non presumere che il poster originale stia creando tutto questo da zero non sa niente di meglio. Più spesso abbiamo a che fare con l'output di un altro modulo o origine dati in un formato o tipo che non possiamo controllare.
rtphokie

9
@rtphokie che vuole mutare un contenitore immutabile (quindi il "perché" è una domanda molto valida) è diverso dall'interpretare formati differenti alcuni dei quali potrebbero essere una tupla. Grazie per esserti sfogato :)
Jon Clements

7
Sembra non necessario e la memoria inefficiente riformulare in un elenco e utilizzare una variabile temporanea. Puoi semplicemente decomprimere in una tupla con lo stesso nome e durante la decompressione aggiornare tutto ciò che deve essere aggiornato.
Brian Spiering

5
@JonClements Tu fai un punto molto importante sul fatto che farlo è una cattiva pratica. Questo dovrebbe essere nella tua risposta. Tuttavia le domande retoriche sono spesso interpretate come inutilmente dispregiative. Tali informazioni sono meglio strutturate nella forma: "Questa è una cattiva pratica perché ..." o anche "Considera attentamente se ne hai davvero bisogno; di solito implica un difetto nel design perché ..."
Philip Couling

74

A seconda del tuo problema, l'affettare può essere una soluzione davvero accurata:

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

Ciò consente di aggiungere più elementi o anche di sostituire alcuni elementi (specialmente se sono "vicini". Nel caso precedente il casting a una lista è probabilmente più appropriato e leggibile (anche se la notazione di slicing è molto più breve).


24

Ebbene, come Trufa ha già mostrato, ci sono fondamentalmente due modi per sostituire l'elemento di una tupla a un dato indice. Converti la tupla in una lista, sostituisci l'elemento e riconvertilo o costruisci una nuova tupla per concatenazione.

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

Quindi, quale metodo è migliore, cioè più veloce?

Si scopre che per tuple brevi (su Python 3.3), la concatenazione è effettivamente più veloce!

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

Tuttavia, se guardiamo a tuple più lunghe, la conversione dell'elenco è la strada da percorrere:

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

Per tuple molto lunghe, la conversione delle liste è sostanzialmente migliore!

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

Inoltre, le prestazioni del metodo di concatenazione dipendono dall'indice in cui sostituiamo l'elemento. Per il metodo elenco, l'indice è irrilevante.

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

Quindi: se la tua tupla è corta, taglia e concatena. Se è lungo, effettua la conversione dell'elenco!


1
@ErikAronesty non sempre. Un caso utile è quello di estendere una classe che non è possibile modificare e i cui metodi restituiscono una tupla da cui si desidera modificare solo il primo elemento. return (val,) + res [1:] è più chiaro di res2 = list (res); res2 [0] = val; return tuple (res2)
yucer

9

È possibile con una fodera:

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

1
Come cambieresti solo il terzo elemento valuescon questo?
sdbbs

2
Ecco come puoi cambiare qualsiasi elemento in una tupla -i = 2; values = (*values[:i], '300', *values[i+1:])
Brian Spiering

8

Non che questo sia superiore, ma se qualcuno è curioso può essere fatto su una riga con:

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

È più veloce di tuple = tuple(200 if i == 0 else _ for i, _ in enumerate(tuple))? (Perché non la comprensione del generatore?)
Anonimo

8

Credo che tecnicamente questo risponda alla domanda, ma non farlo a casa. Al momento, tutte le risposte implicano la creazione di una nuova tupla, ma puoi usarla ctypesper modificare una tupla in memoria. Basandosi su vari dettagli di implementazione di CPython su un sistema a 64 bit, un modo per farlo è il seguente:

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
Sempre bello sapere qualcosa di diverso dalle solite risposte. Saluti !
Kartheek Palepu

Le persone che vogliono programmare in C dovrebbero probabilmente fare esattamente questo. Hacking l'interprete in questo modo manca solo l'argomento qui. È anche inaffidabile, poiché i dettagli dell'implementazione di cPython possono cambiare in qualsiasi momento senza preavviso ed è probabile che rompa qualsiasi codice che si basa su tuple che sono imutabili. Inoltre, le tuple sono gli oggetti di raccolta più leggeri in Python, quindi non ci sono problemi a crearne uno nuovo. Se hai assolutamente bisogno di modificare una raccolta frequentemente, usa invece un elenco.
Bachsau

1
Hai dimenticato di diminuire il conteggio dei riferimenti al valore che stai scartando. Ciò causerà perdite.
wizzwizz4

6

Come ha scritto Hunter McMillen nei commenti, le tuple sono immutabili, è necessario creare una nuova tupla per ottenere ciò. Per esempio:

>>> 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: questo non funziona ancora su tuple con voci duplicate !!

Basato sull'idea di Pooya :

Se hai intenzione di farlo spesso (cosa che non dovresti fare poiché le tuple sono immutabili per una ragione) dovresti fare qualcosa del genere:

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

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

O basato sull'idea di Jon :

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

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

3

Frist, chiediti perché vuoi mutare il tuo tuple. C'è un motivo per cui le stringhe e le tuple sono immutabili in Ptyhon , se vuoi mutare il tuo, tupleallora dovrebbe probabilmente essere a list.

In secondo luogo, se desideri ancora mutare la tua tupla, puoi convertirla tuplein a, listriconvertirla e riassegnare la nuova tupla alla stessa variabile. Questo è fantastico se devi cambiare la tua tupla solo una volta . Altrimenti, personalmente penso che sia controintuitivo. Perché essenzialmente sta creando una nuova tupla e ogni volta se si desidera mutare la tupla è necessario eseguire la conversione. Inoltre, se leggessi il codice, sarebbe fonte di confusione pensare perché non crearne semplicemente un list? Ma è carino perché non richiede alcuna libreria.

Suggerisco di utilizzare mutabletuple(typename, field_names, default=MtNoDefault)da mutabletuple 0.2 . Personalmente penso che in questo modo sia più intuitivo e leggibile. Il personale che legge il codice saprebbe che lo scrittore intende modificare questa tupla in futuro. Lo svantaggio rispetto al listmetodo di conversione sopra è che ciò richiede l'importazione di un file py aggiuntivo.

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 : Non provare a mutare tuple. se lo fai ed è un'operazione una tantum, converti tuplein elenco, modificalo, trasformalo listin nuovo tuplee riassegnalo alla variabile che contiene vecchia tuple. Se desideri tuplee in qualche modo vuoi evitare liste vuoi mutare più di una volta, crea mutabletuple.


2

basato sull'Idea di Jon e sul caro 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")

cambia tutte le ricorrenze dei vecchi valori


Questo sarebbe abbastanza scomodo (per la mancanza di una parola migliore) se dovessi cambiare più valori ma poi di nuovo, perché dovresti voler modificare una tupla in primo luogo ...
Trufa

@Trufa sì, sto cercando di scriverlo: D
Pooya

Il nome del metodo modify_tuple_by_index non è accurato e causa confusione.
msw

1

Non puoi. Se vuoi cambiarlo, devi usare un elenco invece di una tupla.

Nota che potresti invece creare una nuova tupla che abbia il nuovo valore come primo elemento.


0

Ho scoperto che il modo migliore per modificare le tuple è ricreare la tupla utilizzando la versione precedente come base.

Ecco un esempio che ho usato per realizzare una versione più chiara di un colore (l'avevo già aperto all'epoca):

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

Quello che fa è che passa attraverso la tupla 'color' e legge ogni elemento, fa qualcosa su di esso e infine lo aggiunge alla nuova tupla.

Quindi quello che vorresti sarebbe qualcosa del tipo:

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

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

Quello specifico non funziona, ma il concetto è ciò di cui hai bisogno.

tuple = (0, 1, 2)

tuple = itera attraverso la tupla, modifica ogni elemento secondo necessità

questo è il concetto.


0

Sono in ritardo per il gioco, ma penso che il modo più semplice , più efficiente per le risorse e più veloce (a seconda della situazione) sia sovrascrivere la tupla stessa. Poiché ciò eliminerebbe la necessità della creazione di elenchi e variabili e viene archiviato in una riga.

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

>>> (1, 2, 24)

Ma: questo è utile solo per tuple piuttosto piccole e ti limita anche a un valore di tupla fisso, tuttavia, questo è il caso per la maggior parte delle volte comunque.

Quindi in questo caso particolare sarebbe simile a questo:

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

funziona solo se si tratta di una tupla statica di lunghezza nota. non il codice molto probabilmente fallirà prima o poi perché è troppo specifico ...
patroqueeet

si - @patroqueeet, quindi ho affermato chiaramente gli svantaggi di questo approccio, dopo Ma: ...-. Per favore riconsidera il tuo
voto negativo

1
hey, ha riconsiderato e ha fatto clic sul pulsante. ma il voto è ora bloccato da SO: /
patroqueeet

0

TLDR; la "soluzione alternativa" è creare un nuovo oggetto tupla, non modificare effettivamente l'originale

Anche se questa è una domanda molto vecchia, qualcuno mi ha parlato di questa follia mutante di tuple di Python. Cosa che mi ha molto sorpreso / incuriosito e, facendo qualche ricerca su Google, sono atterrato qui (e altri campioni simili online)

Ho eseguito alcuni test per dimostrare la mia teoria

Nota ==valuta l'uguaglianza mentre isfa l'uguaglianza referenziale (è obj a la stessa istanza di 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))

I rendimenti:

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

Non puoi modificare gli elementi nella tupla, ma puoi modificare le proprietà degli oggetti mutabili nelle tuple (ad esempio se quegli oggetti sono elenchi o oggetti classe effettivi)

Per esempio

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

l'ho fatto:

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

e per cambiare, fallo e basta

list[0]=6

e puoi cambiare una tupla: D

eccolo copiato esattamente da 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 è una lista, non una tupla. x = (y)non fa altro che assegna y a x.
m4tx

2
inoltre, utilizzando il nome "tupla" e "lista" come variabili, sarà difficile confrontare i tipi di tupla e di lista in seguito.
Michael Scott Cuthbert

-4

È possibile modificare il valore della tupla utilizzando copia per riferimento

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

>>> tuple2=tuple1

>>> tuple2
    [20, 30, 40]

>>> tuple2[1]=10

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

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

1
Solo che queste sono liste, non tuple, che puoi comunque cambiare, dato o meno un secondo riferimento.
Bachsau
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.