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.

Supun Dasantha Kuruppu
źródł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
Bardzo podobne pytanie: stackoverflow.com/questions/58048664/…
sanyash

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.

Karl Knechtel
źródło
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ą

alfasin
źródło
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.

Charif DZ
źródło
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)
Żołądź
źródło
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.

Sven Marnach
źródło
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.

zizzler
źródło
> 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.

Hamidreza
źródło