Dlaczego b + = (4,) działa, a b = b + (4,) nie działa, gdy b jest listą?


77

Jeśli weźmiemy b = [1,2,3]i spróbujemy:b+=(4,)

Zwraca b = [1,2,3,4], ale jeśli spróbujemy b = b + (4,)to zrobić, to nie zadziała.

b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can't add tuples and lists

Spodziewałem b+=(4,)się niepowodzenia, ponieważ nie można dodać listy i krotki, ale zadziałało. Próbowałem więc b = b + (4,)oczekiwać takiego samego rezultatu, ale to nie zadziałało.


4
Wierzę, że odpowiedź można znaleźć tutaj .
— jochen


Na początku źle to odczytałem i próbowałem zamknąć jako zbyt szeroki, a następnie wycofałem. Potem pomyślałem, że to musi być duplikat, ale nie tylko nie mogłem ponownie oddać głosu, ale wyciągnąłem włosy, próbując znaleźć inne odpowiedzi na te pytania. : /
— Karl Knechtel

Odpowiedzi:


70

Problem z pytaniami „dlaczego” polega na tym, że zwykle mogą oznaczać wiele różnych rzeczy. Postaram się odpowiedzieć na każde, o czym myślę, że masz na myśli.

„Dlaczego to może działać inaczej?” na co odpowiada np . ten . Zasadniczo +=próbuje użyć różnych metod obiektu: __iadd__(który jest zaznaczony tylko po lewej stronie), vs __add__i __radd__(„dodaj wstecz”, zaznaczony po prawej stronie, jeśli strona po lewej nie ma __add__) dla +.

„Co dokładnie robi każda wersja?” Krótko mówiąc, list.__iadd__metoda robi to samo co list.extend(ale z powodu projektu językowego nadal istnieje przypisanie z powrotem).

Oznacza to również na przykład, że

>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the `4` added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the `5`
[1, 2, 3, 4]

+, oczywiście tworzy nowy obiekt, ale jawnie wymaga innej listy zamiast próbować wyciągać elementy z innej sekwencji.

„Dlaczego warto to zrobić + = Jest to bardziej wydajne; extendmetoda nie musi tworzyć nowego obiektu. Oczywiście ma to czasem zaskakujące efekty (jak wyżej), a generalnie Python tak naprawdę nie dotyczy wydajności , ale te decyzje zostały podjęte dawno temu.

„Dlaczego nie zezwalam na dodawanie list i krotek za pomocą +?” Zobacz tutaj (dzięki, @ splash58); jednym z pomysłów jest to, że (krotka + lista) powinna dawać ten sam typ co (lista + krotka) i nie jest jasne, jaki typ powinien być wynik. +=nie ma tego problemu, ponieważ a += boczywiście nie powinien zmieniać typu a.


2
Ooo, całkiem dobrze. I listy nie używają |, więc to trochę rujnuje mój przykład. Jeśli później wymyślę wyraźniejszy przykład, zamienię go.
— Karl Knechtel

1
Btw |dla zestawów jest operatorem dojeżdżającym, ale +dla list nie jest. Z tego powodu nie sądzę, aby argument o niejednoznaczności typu był szczególnie silny. Ponieważ operator nie dojeżdża do pracy, dlaczego wymaga tego samego dla typów? Można po prostu zgodzić się, że wynik ma typ lhs. Z drugiej strony, ograniczając list + iterator, zachęca się programistę do wyraźniejszego wyrażenia swoich zamiarów. Jeśli chcesz utworzyć nową listę, która zawiera materiał z audzielonych przez rzeczy z btam już sposobem, aby to zrobić: new = a.copy(); new += b. To jeszcze jedna linia, ale krystalicznie czysta.
— a_guest

Powodem, dla którego a += bzachowuje się inaczej niż a = a + bnie jest wydajność. W rzeczywistości Guido uznał, że wybrane zachowanie jest mniej mylące. Wyobraź sobie, że funkcja odbiera listę ajako argument, a następnie robi a += [1, 2, 3]. Ta składnia z pewnością wygląda na to, że modyfikuje listę, zamiast tworzyć nową, więc zdecydowano, że powinna ona zachowywać się zgodnie z intuicją większości ludzi na temat oczekiwanego wyniku. Jednak mechanizm musiał również działać dla niezmiennych typów, takich jak ints, co doprowadziło do obecnego projektu.
— Sven Marnach,

Osobiście uważam, że projekt jest bardziej mylący niż zwykłe tworzenie a += bskrótu a = a + b, jak to zrobiła Ruby, ale rozumiem, jak się tam dostaliśmy.
— Sven Marnach,

21

Nie są równoważne:

b += (4,)

jest skrótem od:

b.extend((4,))

podczas +konkatenacji list, więc przez:

b = b + (4,)

próbujesz połączyć krotkę z listą


14

Kiedy to zrobisz:

b += (4,)

jest konwertowany na to:

b.__iadd__((4,)) 

Pod maską woła b.extend((4,)), extendakceptuje iterator i dlatego to również działa:

b = [1,2,3]
b += range(2)  # prints [1, 2, 3, 0, 1]

ale kiedy to zrobisz:

b = b + (4,)

jest konwertowany na to:

b = b.__add__((4,)) 

akceptuj tylko obiekt listy.


4

Z oficjalnych dokumentów, dla zmiennych typów sekwencji zarówno:

s += t
s.extend(t)

są zdefiniowane jako:

rozszerza się swraz z zawartościąt

Który jest inny niż zdefiniowany jako:

s = s + t    # not equivalent in Python!

Oznacza to również, że będzie działać dowolny typ sekwencjit , w tym krotka jak w twoim przykładzie.

Ale działa również w przypadku zakresów i generatorów! Na przykład możesz także:

s += range(3)

3

„Rozszerzone” operatory przypisania, takie jak, +=zostały wprowadzone w Pythonie 2.0, który został wydany w październiku 2000 r. Projekt i uzasadnienie opisano w PEP 203 . Jednym z deklarowanych celów tych operatorów było wsparcie operacji na miejscu. Pisanie

a = [1, 2, 3]
a += [4, 5, 6]

ma aktualizować listę a na miejscu . Ma to znaczenie, jeśli istnieją inne odwołania do listy a, np. Kiedy aotrzymano ją jako argument funkcji.

Jednak operacja nie zawsze może mieć miejsce, ponieważ wiele typów Pythona, w tym liczby całkowite i łańcuchy, są niezmienne , więc np. i += 1Dla liczb całkowitych inie można operować w miejscu.

Podsumowując, operatorzy rozszerzonego przypisania mieli działać, gdy to możliwe, i tworzyć nowy obiekt w inny sposób. Aby ułatwić osiągnięcie tych celów projektowych, określono wyrażenie, które x += ybędzie zachowywać się w następujący sposób:

  • Jeśli x.__iadd__jest zdefiniowane, x.__iadd__(y)jest oceniane.
  • W przeciwnym razie, jeśli x.__add__jest implementowany, x.__add__(y)jest oceniany.
  • W przeciwnym razie, jeśli y.__radd__jest implementowany, y.__radd__(x)jest oceniany.
  • W przeciwnym razie podnieś błąd.

Pierwszy wynik uzyskany w tym procesie zostanie przypisany z powrotem x(chyba że wynikiem jest NotImplementedsingleton, w którym to przypadku wyszukiwanie jest kontynuowane w następnym kroku).

Ten proces pozwala na wdrożenie typów obsługujących modyfikacje w miejscu __iadd__(). Typy, które nie obsługują modyfikacji w miejscu, nie muszą dodawać żadnych nowych magicznych metod, ponieważ Python automatycznie do nich wróci x = x + y.

W końcu przejdźmy do twojego rzeczywistego pytania - dlaczego możesz dodać krotkę do listy za pomocą rozszerzonego operatora przypisania. Z pamięci historia tego była mniej więcej taka: list.__iadd__()Metoda została zaimplementowana, aby po prostu wywołać już istniejącą list.extend()metodę w Pythonie 2.0. Kiedy iteratory zostały wprowadzone w Pythonie 2.1, list.extend()metoda została zaktualizowana, aby akceptować dowolne iteratory. Efektem końcowym tych zmian było, że my_list += my_tupledziałało począwszy od Python 2.1. Jednak list.__add__()metoda ta nigdy nie powinna wspierać arbitralnych iteratorów jako argumentu po prawej stronie - uważano to za nieodpowiednie dla silnie typowanego języka.

Osobiście uważam, że implementacja rozszerzonych operatorów okazała się w Pythonie zbyt skomplikowana. Ma wiele zaskakujących skutków ubocznych, np. Ten kod:

t = ([42], [43])
t[0] += [44]

Druga linia podnosi się TypeError: 'tuple' object does not support item assignment, ale operacja i tak jest pomyślnie wykonana - tnastąpi ([42, 44], [43])po wykonaniu linii, która podnosi błąd.


Brawo! Odniesienie do PEP jest szczególnie przydatne. Dodałem link na drugim końcu do poprzedniego pytania SO dotyczącego zachowania list-in-tuple. Kiedy patrzę wstecz na to, co było jak przed Python 2.3 lub tak, wydaje się praktycznie bezużyteczny w porównaniu do dzisiaj ... (i nadal mam mgliste wspomnienie i braku próbuje dostać 1,5 zrobić coś pożytecznego na bardzo starych komputerach Mac)
— Karl Knechtel,

2

Większość ludzi spodziewałaby się, że X + = Y będzie równoważne X = X + Y. Rzeczywiście, Python Pocket Reference (wydanie 4) Mark Lutz mówi na stronie 57 „Poniższe dwa formaty są w przybliżeniu równoważne: X = X + Y, X + = Y ”. Jednak osoby, które określiły Python, nie uczyniły ich równoważnymi. Być może był to błąd, który spowoduje godziny debugowania przez sfrustrowanych programistów tak długo, jak długo Python pozostaje w użyciu, ale teraz taki jest właśnie Python. Jeśli X jest mutowalnym typem sekwencji, X + = Y jest równoważne X.extend (Y), a nie X = X + Y.


> Być może był to błąd, który spowoduje godziny debugowania przez sfrustrowanych programistów tak długo, jak długo Python pozostaje w użyciu <- czy rzeczywiście cierpiałeś z tego powodu? Wygląda na to, że rozmawiasz z doświadczenia. Bardzo chciałbym usłyszeć twoją historię.
— Veky,

1

Jak wyjaśniono tutaj , jeśli arraynie implementuje __iadd__metody, b+=(4,)byłoby to tylko skrótem, b = b + (4,)ale oczywiście nie jest, podobnie arrayjak __iadd__metoda implementacji . Najwyraźniej implementacja __iadd__metody wygląda mniej więcej tak:

def __iadd__(self, x):
    self.extend(x)

Wiemy jednak, że powyższy kod nie jest faktyczną implementacją __iadd__metody, ale możemy założyć i zaakceptować, że istnieje coś takiego jak extendmetoda, która akceptuje tuppledane wejściowe.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.