2015-06-25 7 views
6

Właśnie przeprowadził interesujący Test:Dlaczego pyton nie korzysta z __iadd__ dla operatorów sumowanych i przykutych?

~$ python3 # I also conducted this on python 2.7.6, with the same result 
Python 3.4.0 (default, Apr 11 2014, 13:05:11) 
[GCC 4.8.2] on linux 
Type "help", "copyright", "credits" or "license" for more information. 
>>> class Foo(object): 
...  def __add__(self, other): 
...   global add_calls 
...   add_calls += 1 
...   return Foo() 
...  def __iadd__(self, other): 
...   return self 
... 
>>> add_calls = 0 
>>> a = list(map(lambda x:Foo(), range(6))) 
>>> a[0] + a[1] + a[2] 
<__main__.Foo object at 0x7fb588e6c400> 
>>> add_calls 
2 
>>> add_calls = 0 
>>> sum(a, Foo()) 
<__main__.Foo object at 0x7fb588e6c4a8> 
>>> add_calls 
6 

Oczywiście metoda __iadd__ jest bardziej skuteczne niż metody __add__, nie wymagając przydział nowej klasy. Jeśli moje obiekty byłyby wystarczająco skomplikowane, tworzyłoby to niepotrzebne nowe obiekty, potencjalnie tworząc ogromne wąskie gardła w moim kodzie.

Spodziewam się, że w a[0] + a[1] + a[2] pierwsza operacja będzie wywoływać __add__, a druga operacja będzie wywoływać __iadd__ na nowo utworzonym obiekcie.

Dlaczego pyton nie optymalizuje tego?

+1

'Suma' jest najczęściej używana dla niezmiennych typów liczbowych, które nie mają metody' __iadd__'. Wydaje mi się, że implementacja specjalnej logiki w celu sprawdzenia metody "__iadd__" spowolniłaby sprawę. –

Odpowiedz

3

Metoda może zwrócić inny typ obiektu, podczas gdy __iadd__ powinien, jeśli używa się semantyki lokalnej, zwrócić self. Nie muszą zwracać tego samego typu obiektu, więc nie należy polegać na specjalnej semantyce __iadd__.

Można użyć functools.reduce() function zaimplementowanie wymaganej funkcjonalności siebie:

from functools import reduce 

sum_with_inplace_semantics = reduce(Foo.__iadd__, a, Foo()) 

Demo:

>>> from functools import reduce 
>>> class Foo(object): 
...  def __add__(self, other): 
...   global add_calls 
...   add_calls += 1 
...   return Foo() 
...  def __iadd__(self, other): 
...   global iadd_calls 
...   iadd_calls += 1 
...   return self 
... 
>>> a = [Foo() for _ in range(6)] 
>>> result = Foo() 
>>> add_calls = iadd_calls = 0 
>>> reduce(Foo.__iadd__, a, result) is result 
True 
>>> add_calls, iadd_calls 
(0, 6) 
+0

Niekoniecznie prawda. OP prosi o użycie pierwszych tymczasowych, a nie przekazanych argumentów. To jest bardziej jak C++ 11-ruch-semantyka. – deets

+0

OP zajął się tym w swoim pytaniu: "Spodziewam się, że ... pierwsza operacja wywoła" __add__ ", a druga operacja wywoła" __iadd__ "na nowo utworzonym obiekcie." –

+0

@deets: Python * nie może wiedzieć * że pierwszy obiekt jest tymczasowy –

0

Martjin's answer zapewnia doskonałą obejście, ale czuję potrzebę podsumowania kawałki odpowiedzi rozproszone w komentarzach:

Funkcja sum to przede wszystkim nas ed dla niezmiennych typów. Wykonanie wszystkich dodatków z wyjątkiem pierwszego na miejscu spowodowałoby poprawę wydajności obiektów z metodą __iadd__, ale sprawdzenie metody __iadd__ spowodowałoby utratę wydajności w bardziej typowym przypadku. Special cases aren't special enough to break the rules.

ja również stwierdził, że __add__ prawdopodobnie powinien być wywołana tylko raz w a + b + c, gdzie a + b tworzy zmienną tymczasową, a następnie wywołuje tmp.__iadd__(c) przed wpuszczeniem go. Naruszałoby to jednak zasadę najmniejszego zaskoczenia.

0

Ponieważ piszesz swoją klasę mimo wszystko, wiesz, że to __add__ może zwrócić ten sam obiekt, dobrze, prawda?

A zatem można zrobić swoją Zmiękczanie zoptymalizowany kod do uruchomienia zarówno operatora + i wbudowany sum:

>>> class Foo(object): 
...  def __add__(self, other): 
...   global add_calls 
...   add_calls += 1 
...   return self 

(Tylko uważaj przekazując swój kod do funkcji osób trzecich, które oczekują „+” być nowym obiektem)

+0

Wolałbym, aby mój kod był semantycznie poprawny. Gdybym to zrobił, prawdopodobnie bym i inni ludzie z mojej drużyny zdecydowanie, zapomnieli o tej "funkcji" i skończyliby z problemami. Powyższa odpowiedź Martjina może nie być tak krótka, ale jest bardziej gadatliwa. – Jack