Python: Wert in einem Tupel ändern

122

Ich bin neu in Python, daher könnte diese Frage ein wenig grundlegend sein. Ich habe ein Tupel namens values, das Folgendes enthält:

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

Ich möchte den ersten Wert (dh 275) in diesem Tupel ändern, aber ich verstehe, dass Tupel unveränderlich sind und daher values[0] = 200nicht funktionieren. Wie kann ich das erreichen?

Dawood
quelle
24
Tupel sind unveränderlich . Sie müssen ein neues Tupel erstellen, um dies zu erreichen.
Hunter McMillen

Antworten:

176

Zuerst müssen Sie fragen, warum Sie dies tun möchten.

Aber es ist möglich über:

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

Aber wenn Sie etwas ändern müssen, ist es wahrscheinlich besser, es als zu behalten list

Jon Clements
quelle
4
Ein Anwendungsfall ist, wenn Sie eine große Anzahl kleiner Sequenzen speichern, bei denen sich die Werte selten ändern, dies aber in einigen Fällen möglicherweise gewünscht wird. Bei einer kleinen Sequenz mit einer Länge ungleich Null macht der Speicherverbrauch von Tupel (60 Byte für ein Element) gegenüber Liste (104 Byte) einen Unterschied. Ein anderer Anwendungsfall ist für namedtuples, da namedlist nicht nativ existiert.
Michael Scott Cuthbert
80
Die Antworten "Warum willst du das tun?" Machen mich bei StackOverflow verrückt. Gehen Sie nicht davon aus, dass das Originalposter dies tun möchte. Eigentlich ist es besser nicht anzunehmen, dass das Originalplakat all dies von Grund auf neu erstellt und es nicht besser weiß. Häufiger handelt es sich um die Ausgabe eines anderen Moduls oder einer anderen Datenquelle in einem Format oder Typ, den wir nicht steuern können.
RTphokie
9
@rtphokie, der einen unveränderlichen Container mutieren möchte (daher ist das "Warum" eine sehr gültige Frage), unterscheidet sich von der Interpretation verschiedener Formate, von denen einige ein Tupel sein können. Vielen Dank für die Entlüftung :)
Jon Clements
7
Es erscheint unnötig und ineffizient, Speicher in eine Liste umzuwandeln und eine temporäre Variable zu verwenden. Sie können einfach in ein Tupel mit demselben Namen auspacken und beim Auspacken das Update auspacken, was auch immer aktualisiert werden muss.
Brian Spiering
5
@ JonClements Sie machen einen sehr wertvollen Punkt, dass dies eine schlechte Praxis ist. Das sollte in deiner Antwort sein. Rhetorische Fragen werden jedoch oft als unnötig abfällig interpretiert. Solche Informationen sind besser strukturiert in der Form: "Dies ist eine schlechte Praxis, weil ..." oder sogar "Überlegen Sie sorgfältig, ob Sie dies wirklich brauchen; es impliziert normalerweise einen Fehler im Design, weil ..."
Philip Couling
74

Abhängig von Ihrem Problem kann das Schneiden eine wirklich gute Lösung sein:

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

Auf diese Weise können Sie mehrere Elemente hinzufügen oder auch einige Elemente ersetzen (insbesondere wenn es sich um "Nachbarn" handelt. Im obigen Fall ist das Casting in eine Liste wahrscheinlich geeigneter und lesbarer (obwohl die Slicing-Notation viel kürzer ist).

Dave Halter
quelle
24

Nun, wie Trufa bereits gezeigt hat, gibt es grundsätzlich zwei Möglichkeiten, das Element eines Tupels bei einem bestimmten Index zu ersetzen. Konvertieren Sie entweder das Tupel in eine Liste, ersetzen Sie das Element und konvertieren Sie es zurück oder erstellen Sie ein neues Tupel durch Verkettung.

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

Also, welche Methode ist besser, das heißt schneller?

Es stellt sich heraus, dass bei kurzen Tupeln (unter Python 3.3) die Verkettung tatsächlich schneller ist!

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

Wenn wir uns jedoch längere Tupel ansehen, ist die Listenkonvertierung der richtige Weg:

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

Bei sehr langen Tupeln ist die Listenkonvertierung wesentlich besser!

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

Die Leistung der Verkettungsmethode hängt auch von dem Index ab, bei dem wir das Element ersetzen. Für die Listenmethode ist der Index 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

Also: Wenn Ihr Tupel kurz ist, schneiden Sie es auf und verketten Sie es. Wenn es lang ist, führen Sie die Listenkonvertierung durch!

sjakobi
quelle
1
@ErikAronesty nicht immer. Ein nützlicher Fall ist das Erweitern einer Klasse, die Sie nicht ändern können und deren Methoden ein Tupel zurückgeben, von dem Sie nur das erste Element ändern möchten. return (val,) + res [1:] ist klarer als res2 = list (res); res2 [0] = val; return tuple (res2)
yucer
9

Es ist mit einem Einzeiler möglich:

values = ('275', '54000', '0.0', '5000.0', '0.0')
values = ('300', *values[1:])
Brian Spiering
quelle
Wie würden Sie damit nur das dritte Element ändern values?
SDBBS
2
Hier ist, wie Sie jedes Element in einem Tupel ändern können -i = 2; values = (*values[:i], '300', *values[i+1:])
Brian Spiering
8

Nicht, dass dies überlegen wäre, aber wenn jemand neugierig ist, kann dies in einer Zeile erfolgen mit:

tuple = tuple([200 if i == 0 else _ for i, _ in enumerate(tuple)])
bphi
quelle
Ist das schneller als tuple = tuple(200 if i == 0 else _ for i, _ in enumerate(tuple))? (Warum nicht Generator Verständnis?)
Anonym
8

Ich glaube, dies beantwortet die Frage technisch, aber mache das nicht zu Hause. Im Moment beinhalten alle Antworten das Erstellen eines neuen Tupels, aber Sie können ctypesein Tupel im Speicher ändern. Unter Verwendung verschiedener Implementierungsdetails von CPython auf einem 64-Bit-System können Sie dies folgendermaßen tun:

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
quelle
1
Es ist immer gut, etwas anderes als die üblichen Antworten zu wissen. Prost !
Kartheek Palepu
Leute, die in C codieren möchten, sollten wahrscheinlich genau das tun. Wenn Sie den Dolmetscher so hacken, wird das Thema hier einfach übersehen. Es ist auch unzuverlässig, da sich die Details der cPython-Implementierung jederzeit ohne Vorwarnung ändern können und wahrscheinlich jeden Code beschädigen, der darauf beruht, dass Tupel unveränderlich sind. Darüber hinaus sind Tupel die leichtesten Sammlungsobjekte in Python, sodass es kein Problem gibt, ein neues zu erstellen. Wenn Sie eine Sammlung unbedingt häufig ändern müssen, verwenden Sie stattdessen eine Liste.
Bachsau
1
Sie haben vergessen, den Referenzzähler auf den Wert zu verringern, den Sie verwerfen. Das wird zu Undichtigkeiten führen.
wizzwizz4
6

Wie Hunter McMillen in den Kommentaren schrieb, sind Tupel unveränderlich. Sie müssen ein neues Tupel erstellen, um dies zu erreichen. Zum Beispiel:

>>> 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
quelle
3

EDIT: Dies funktioniert noch nicht bei Tupeln mit doppelten Einträgen !!

Basierend auf Pooyas Idee :

Wenn Sie dies häufig vorhaben (was Sie nicht tun sollten, da Tupel aus einem bestimmten Grund unveränderlich sind), sollten Sie Folgendes tun:

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

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

Oder basierend auf Jons Idee :

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

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

Frist, fragen Sie sich, warum Sie Ihre mutieren wollen tuple. Es gibt einen Grund, warum Strings und Tupel in Ptyhon unveränderlich sind . Wenn Sie Ihre mutieren möchten, tuplesollte es wahrscheinlich eine sein list.

Zweitens, wenn Sie Ihr Tupel immer noch mutieren möchten, können Sie es tuplein ein listkonvertieren und dann zurückkonvertieren und das neue Tupel derselben Variablen neu zuweisen. Dies ist großartig, wenn Sie Ihr Tupel nur einmal mutieren . Ansonsten halte ich das persönlich für nicht intuitiv. Da im Wesentlichen ein neues Tupel erstellt wird und jedes Mal, wenn Sie das Tupel mutieren möchten, müssen Sie die Konvertierung durchführen. Auch wenn Sie den Code lesen, wäre es verwirrend zu überlegen, warum nicht einfach eine erstellen list? Aber es ist schön, weil es keine Bibliothek benötigt.

Ich schlage vor, mutabletuple(typename, field_names, default=MtNoDefault)von mutabletuple 0.2 zu verwenden . Ich persönlich halte diesen Weg für intuitiver und lesbarer. Das persönliche Lesen des Codes würde wissen, dass der Autor beabsichtigt, dieses Tupel in Zukunft zu mutieren. Der Nachteil im Vergleich zur listobigen Konvertierungsmethode besteht darin, dass Sie hierfür eine zusätzliche py-Datei importieren müssen.

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 : Versuchen Sie nicht zu mutieren tuple. Wenn Sie dies tun und es sich um eine einmalige Operation handelt, konvertieren Sie sie tuplein eine Liste, mutieren Sie sie, verwandeln Sie sie listin eine neue tupleund weisen Sie sie wieder der alten Variablen zu tuple. Wenn Wünsche tupleund irgendwie vermeiden listund mehr als einmal mutieren wollen, dann erstellen mutabletuple.

OLIVER.KOO
quelle
2

basierend auf Jons Idee und der lieben 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")

Es ändert alle Ihre alten Wertevorkommen

Pooya
quelle
Dies wäre ziemlich unangenehm (mangels eines besseren Wortes), wenn Sie mehrere Werte ändern würden, aber warum sollten Sie überhaupt ein Tupel ändern wollen ...
Trufa
@Trufa ja, ich versuche es zu schreiben: D
Pooya
Der Methodenname modify_tuple_by_index ist ungenau und kann zu Verwirrung führen.
Msw
1

Das kannst du nicht. Wenn Sie es ändern möchten, müssen Sie eine Liste anstelle eines Tupels verwenden.

Beachten Sie, dass Sie stattdessen ein neues Tupel erstellen können, dessen erstes Element der neue Wert ist.

BrenBarn
quelle
0

Ich habe festgestellt, dass der beste Weg zum Bearbeiten von Tupeln darin besteht, das Tupel mit der vorherigen Version als Basis neu zu erstellen.

Hier ist ein Beispiel, das ich verwendet habe, um eine hellere Version einer Farbe zu erstellen (ich hatte sie zu diesem Zeitpunkt bereits geöffnet):

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

Was es tut, ist, dass es die Tupelfarbe durchläuft und jedes Element liest, etwas damit macht und es schließlich dem neuen Tupel hinzufügt.

Also, was Sie wollen, wäre so etwas wie:

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

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

Das spezielle funktioniert nicht, aber das Konzept ist genau das, was Sie brauchen.

tuple = (0, 1, 2)

tuple = durch Tupel iterieren, jedes Element nach Bedarf ändern

Das ist das Konzept.

Aedus
quelle
0

Ich bin zu spät zum Spiel, aber ich denke, der einfachste , ressourcenschonendste und schnellste Weg (je nach Situation) besteht darin, das Tupel selbst zu überschreiben. Da dies die Notwendigkeit für die Erstellung von Listen und Variablen beseitigen würde und in einer Zeile archiviert wird.

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

>>> (1, 2, 24)

Aber: Dies ist nur für eher kleine Tupel praktisch und beschränkt Sie auch auf einen festen Tupelwert. Dies ist jedoch ohnehin die meiste Zeit bei Tupeln der Fall.

In diesem speziellen Fall würde es also so aussehen:

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')
GordanTrevis
quelle
Dies funktioniert nur, wenn es sich um ein statisches Tupel mit bekannter Länge handelt. Nicht Code wird höchstwahrscheinlich früher oder später fehlschlagen, weil es zu spezifisch ist ...
Patroqueeet
ja - @patroqueeet, deshalb habe ich die Nachteile dieses Ansatzes nach Aber: ...- klar dargelegt. Bitte überdenken Sie Ihre Ablehnung, danke;)
GordanTrevis
1
Hey, überlegte und klickte auf die Schaltfläche. aber die Abstimmung ist jetzt durch SO gesperrt: /
patroqueeet
0

tldr; Die "Problemumgehung" erstellt ein neues Tupelobjekt, ohne das Original zu ändern

Während dies eine sehr alte Frage ist, erzählte mir jemand von diesem Python-mutierenden Tupel-Wahnsinn. Was mich sehr überrascht / fasziniert hat und ein bisschen gegoogelt hat, bin ich hier gelandet (und andere ähnliche Beispiele online)

Ich habe einen Test durchgeführt, um meine Theorie zu beweisen

Hinweis ==bewirkt Gleichheit und isreferenzielle Gleichheit (ist obj a dieselbe Instanz wie 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))

Ausbeuten:

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
Jason
quelle
0

Sie können keine Elemente in Tupeln ändern, aber Sie können die Eigenschaften veränderbarer Objekte in Tupeln ändern (z. B. wenn diese Objekte Listen oder tatsächliche Klassenobjekte sind).

Beispielsweise

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
quelle
-2

ich war das:

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

und um sich zu ändern, tu es einfach

list[0]=6

und du kannst ein Tupel ändern: D.

Hier wird es genau aus IDLE kopiert

>>> 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]
MC_Creep3r
quelle
2
Tupel ist eine Liste, kein Tupel. x = (y)tut nichts anderes, als x y zuzuweisen.
m4tx
2
Wenn Sie auch die Namen "Tupel" und "Liste" als Variablen verwenden, wird es später schwierig, Tupel- und Listentypen zu vergleichen.
Michael Scott Cuthbert
-4

Sie können den Wert des Tupels mithilfe der Referenzkopie ändern

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

>>> tuple2=tuple1

>>> tuple2
    [20, 30, 40]

>>> tuple2[1]=10

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

>>> print(tuple1)
    [20, 10, 40]
Rohit
quelle
1
Nur, dass dies Listen sind, keine Tupel, die Sie ohnehin ändern können, wenn Sie eine zweite Referenz haben oder nicht.
Bachsau