Python przypisuje wiele zmiennych do tej samej wartości? zachowanie listy

138

Próbowałem użyć wielokrotnego przypisania, jak pokazano poniżej, do zainicjowania zmiennych, ale byłem zdezorientowany zachowaniem, spodziewam się ponownego przypisania listy wartości oddzielnie, mam na myśli, że b [0] ic [0] równe 0, jak poprzednio.

a=b=c=[0,3,5]
a[0]=1
print(a)
print(b)
print(c)

Wynik to: [1, 3, 5] [1, 3, 5] [1, 3, 5]

Czy to jest poprawne? czego należy używać do wielokrotnego przypisania? co się od tego różni?

d=e=f=3
e=4
print('f:',f)
print('e:',e)

wynik: ('f:', 3) ('e:', 4)

Marco
źródło
3
Czy chcesz a, bi c,aby wszystko wskazuje na tę samą wartość (w tym przypadku lista), czy chcesz a=0, b=3i c=5. W takim razie chcesz a,b,c = [0,3,5]lub po prostu a,b,c = 0,3,5.
chepner

Odpowiedzi:

276

Jeśli przychodzisz do Pythona z języka w C / Java / etc. rodzina, może pomóc ci przestać myśleć o niej ajako o „zmiennej” i zacząć o niej myśleć jak o „imieniu”.

a, bi cnie są różnymi zmiennymi o równych wartościach; są to różne nazwy tej samej identycznej wartości. Zmienne mają typy, tożsamości, adresy i wszelkiego rodzaju tego typu rzeczy.

Nazwy tego nie mają. Wartości oczywiście mają i możesz mieć wiele nazw dla tej samej wartości.

Jeśli dajesz Notorious B.I.G.hot doga * Biggie Smallsi Chris Wallacemasz hot doga. Jeśli zmienisz pierwszy element ana 1, pierwsze elementy bi cbędą miały wartość 1.

Jeśli chcesz wiedzieć, czy dwie nazwy nazywają ten sam obiekt, użyj isoperatora:

>>> a=b=c=[0,3,5]
>>> a is b
True

Następnie pytasz:

co się od tego różni?

d=e=f=3
e=4
print('f:',f)
print('e:',e)

Tutaj ponownie wiążesz nazwę ez wartością 4. To nie ma wpływu na nazwy di fw żaden sposób.

W poprzedniej wersji przypisywałeś do a[0], a nie do a. Więc z punktu widzenia a[0], ponownie wiążesz a[0], ale z punktu widzenia azmieniasz to na miejscu.

Możesz użyć idfunkcji, która daje ci unikalny numer reprezentujący tożsamość obiektu, aby zobaczyć dokładnie, który obiekt jest którym, nawet jeśli isnie możesz pomóc:

>>> a=b=c=[0,3,5]
>>> id(a)
4473392520
>>> id(b)
4473392520
>>> id(a[0])
4297261120
>>> id(b[0])
4297261120

>>> a[0] = 1
>>> id(a)
4473392520
>>> id(b)
4473392520
>>> id(a[0])
4297261216
>>> id(b[0])
4297261216

Zauważ, że a[0]zmieniło się z 4297261120 na 4297261216 - jest to teraz nazwa innej wartości. A b[0]teraz jest to również nazwa tej samej nowej wartości. To dlatego, że ai bnadal nazywania tego samego obiektu.


Pod okładkami a[0]=1faktycznie wywołuje metodę z obiektu listy. (Jest to równoważne a.__setitem__(0, 1).) Więc tak naprawdę nie jest to wcale ponowne wiązanie. To jak dzwonienie my_object.set_something(1). Oczywiście, prawdopodobnie obiekt ponownie wiąże atrybut instancji w celu zaimplementowania tej metody, ale nie to jest ważne; ważne jest to, że niczego nie przypisujesz, po prostu mutujesz obiekt. To samo dotyczy a[0]=1.


user570826 zadał pytanie:

Co jeśli mamy a = b = c = 10

To dokładnie ta sama sytuacja, co a = b = c = [1, 2, 3]: masz trzy nazwy dla tej samej wartości.

Ale w tym przypadku wartość to an int, a ints są niezmienne. W obu przypadkach możesz aponownie przypisać inną wartość (np. a = "Now I'm a string!"), Ale nie wpłynie to na oryginalną wartość, która bi cnadal będzie nazwami. Różnica polega na tym, że w przypadku listy możesz zmienić wartość [1, 2, 3]na [1, 2, 3, 4], wykonując np a.append(4).; ponieważ to faktycznie zmienia wartość, która bi csą nazwami, bteraz będzie b [1, 2, 3, 4]. Nie ma sposobu, aby zmienić wartość 10na cokolwiek innego. 10ma 10 na zawsze, tak jak wampirzyca Claudia ma 5 na zawsze (przynajmniej do czasu zastąpienia jej przez Kirsten Dunst).


* Ostrzeżenie: nie podawaj Notorious BIG hot doga. Zombie gangsta rapu nigdy nie powinny być karmione po północy.

abarnert
źródło
A co jeśli my have, a = b = c = 10;i kiedy spróbujemy zaktualizować wartość b, wpłynie to na inne? chociaż sprawdziłem, że ich identyfikatory są takie same.?
AJ
1
@ user570826: 10jest niezmienny - oznacza to, że nie ma możliwości zaktualizowania wartości, więc Twoje pytanie nie ma sensu. Możesz wskazać binną wartość, ale nie ma to wpływu na ai c, które nadal wskazują oryginalną wartość. Różnica, jaką wprowadzają listy, polega na tym, że można je modyfikować - np. Można appendprzejść do listy lub lst[0] = 3, a to zaktualizuje wartość, która będzie widoczna we wszystkich nazwach tej wartości.
abarnert
77

Kaszel kaszel

>>> a,b,c = (1,2,3)
>>> a
1
>>> b
2
>>> c
3
>>> a,b,c = ({'test':'a'},{'test':'b'},{'test':'c'})
>>> a
{'test': 'a'}
>>> b
{'test': 'b'}
>>> c
{'test': 'c'}
>>> 
Jimmy Kane
źródło
12
IMHO, to właściwie odpowiada na pierwsze kluczowe pytanie OP, czego powinienem używać do wielokrotnego przypisania, podczas gdy wyżej oceniana i bardziej mózgowa odpowiedź nie.
Will Croxford,
2
Lub a,b,c = 1,2,3bez nawiasów działa w Pythonie 2 lub 3, jeśli naprawdę potrzebujesz dodatkowego cm czytelności.
Will Croxford,
14

Tak, to jest oczekiwane zachowanie. a, b i c są ustawione jako etykiety dla tej samej listy. Jeśli chcesz mieć trzy różne listy, musisz przypisać je indywidualnie. Możesz powtórzyć jawną listę lub użyć jednego z wielu sposobów kopiowania listy:

b = a[:] # this does a shallow copy, which is good enough for this case
import copy
c = copy.deepcopy(a) # this does a deep copy, which matters if the list contains mutable objects

Instrukcje przypisania w Pythonie nie kopiują obiektów - wiążą nazwę z obiektem, a obiekt może mieć tyle etykiet, ile ustawisz. W swojej pierwszej edycji, zmieniając a [0], aktualizujesz jeden element pojedynczej listy, do której odnoszą się a, b i c. W drugim, zmieniając e, zmieniasz e, aby było etykietą dla innego obiektu (4 zamiast 3).

Peter DeGlopper
źródło
12

W Pythonie wszystko jest obiektem, także „proste” typy zmiennych (int, float itp.).

Kiedy zmieniasz wartość zmiennej, w rzeczywistości zmieniasz jej wskaźnik , a jeśli porównujesz dwie zmienne, porównuje ich wskaźniki . (Aby było jasne, wskaźnik to adres w fizycznej pamięci komputera, gdzie przechowywana jest zmienna).

W rezultacie, kiedy zmieniasz wartość zmiennej wewnętrznej, zmieniasz jej wartość w pamięci i wpływa to na wszystkie zmienne, które wskazują na ten adres.

Na przykład, kiedy:

a = b =  5 

Oznacza to, że a i b wskazują na ten sam adres w pamięci, który zawiera wartość 5, ale kiedy to zrobisz:

a = 6

Nie ma to wpływu na b, ponieważ a jest teraz wskazuje na inną lokalizację pamięci, która zawiera 6, a b nadal wskazuje na adres pamięci zawierający 5.

Ale kiedy to zrobisz:

a = b = [1,2,3]

a i b ponownie wskazują na tę samą lokalizację, ale różnica polega na tym, że jeśli zmienisz jedną z wartości listy:

a[0] = 2

Zmienia wartość pamięci, na którą wskazuje a, ale a nadal wskazuje ten sam adres co b, w wyniku czego b również się zmienia.

Ori Seri
źródło
6
Jest to wysoce mylące. Wskaźniki z pewnością nie są widoczne na poziomie Pythona, a co najmniej dwie z czterech głównych implementacji (PyPy i Jython) nie używają ich nawet wewnątrz implementacji.
abarnert
1
Zapraszamy do czytania i eksplorowania wewnętrznych elementów Pythona, a odkryjesz, że każda zmienna w Pythonie jest w rzeczywistości wskaźnikiem.
Ori Seri
4
Nie. W jednej implementacji Pythona (CPython) każda zmienna jest wskaźnikiem do pliku PyObject. Nie jest to prawdą w innych implementacjach, takich jak PyPy czy Jython. (W rzeczywistości nie jest nawet jasne, jak to może być prawdą, ponieważ języki, w których są napisane te implementacje, nie mają nawet wskaźników.)
abarnert
Myślę, że użycie „wskaźnika” w sensie koncepcyjnym jest w porządku (być może z zastrzeżeniem, że implementacje mogą się różnić), szczególnie jeśli celem jest przekazanie zachowania.
Levon
@abarnert Kiedy ludzie mówią Python, mają na myśli CPythona, a nie inne rzadko używane implementacje. Tak jak kiedy ludzie mówią Kleenex, mają na myśli chusteczkę do twarzy. Granie w grę semantyczną w tych komentarzach jest naprawdę niepotrzebne. Co do tego, co napisał, czy zachowanie tego, co opisał, jest złe?
swade
11

Możesz użyć, id(name)aby sprawdzić, czy dwie nazwy reprezentują ten sam obiekt:

>>> a = b = c = [0, 3, 5]
>>> print(id(a), id(b), id(c))
46268488 46268488 46268488

Listy są zmienne; oznacza to, że możesz zmienić wartość bez tworzenia nowego obiektu. Zależy to jednak od tego, jak zmienisz wartość:

>>> a[0] = 1
>>> print(id(a), id(b), id(c))
46268488 46268488 46268488
>>> print(a, b, c)
[1, 3, 5] [1, 3, 5] [1, 3, 5]

Jeśli przypisać nową listę a, a następnie jego id ulegnie zmianie, więc nie wpłynie to bi c„s wartości:

>>> a = [1, 8, 5]
>>> print(id(a), id(b), id(c))
139423880 46268488 46268488
>>> print(a, b, c)
[1, 8, 5] [1, 3, 5] [1, 3, 5]

Liczby całkowite są niezmienne, więc nie możesz zmienić wartości bez utworzenia nowego obiektu:

>>> x = y = z = 1
>>> print(id(x), id(y), id(z))
507081216 507081216 507081216
>>> x = 2
>>> print(id(x), id(y), id(z))
507081248 507081216 507081216
>>> print(x, y, z)
2 1 1
jurgenreza
źródło
1
idniekoniecznie jest miejscem pamięci. Jak mówią doktorzy , zwraca to „tożsamość… liczbę całkowitą… która gwarantuje unikalność i stałą dla tego obiektu podczas jego życia”. CPython używa adresu pamięci jako adresu id, ale inne implementacje Pythona mogą tego nie robić. Na przykład PyPy nie. A powiedzenie „dwie zmienne wskazują na tę samą lokalizację pamięci” jest mylące dla każdego, kto je rozumie, w stylu C. „Dwie nazwy tego samego obiektu” jest zarówno bardziej dokładne, jak i mniej mylące.
abarnert
@abarnert dzięki za wyjaśnienie, zaktualizowałem odpowiedź.
jurgenreza
7

w swoim pierwszym przykładzie a = b = c = [1, 2, 3]naprawdę mówisz:

 'a' is the same as 'b', is the same as 'c' and they are all [1, 2, 3]

Jeśli chcesz ustawić „a” na 1, „b” na „2” i „c” na 3, spróbuj tego:

a, b, c = [1, 2, 3]

print(a)
--> 1
print(b)
--> 2
print(c)
--> 3

Mam nadzieję że to pomoże!

Nick Burns
źródło
4

Mówiąc najprościej, w pierwszym przypadku przypisujesz wiele nazw do pliku list. W pamięci tworzona jest tylko jedna kopia listy, a wszystkie nazwy odnoszą się do tej lokalizacji. Zatem zmiana listy przy użyciu dowolnej nazwy faktycznie zmodyfikuje listę w pamięci.

W drugim przypadku w pamięci tworzonych jest wiele kopii tej samej wartości. Zatem każda kopia jest od siebie niezależna.

Vikas
źródło
3

Potrzebujesz tego:

a, b, c = [0,3,5] # Unpack the list, now a, b, and c are ints
a = 1             # `a` did equal 0, not [0,3,5]
print(a)
print(b)
print(c)
pydsigner
źródło
3

Kod, który robi to, czego potrzebuję, może wyglądać następująco:

# test

aux=[[0 for n in range(3)] for i in range(4)]
print('aux:',aux)

# initialization

a,b,c,d=[[0 for n in range(3)] for i in range(4)]

# changing values

a[0]=1
d[2]=5
print('a:',a)
print('b:',b)
print('c:',c)
print('d:',d)

Wynik:

('aux:', [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
('a:', [1, 0, 0])
('b:', [0, 0, 0])
('c:', [0, 0, 0])
('d:', [0, 0, 5])
Marco
źródło