Pytanie Python: Dlaczego klasa int nie ma bogatych operatorów porównania, takich jak `__lt __ ()`?


Głównie ciekawy.

Zauważyłem (przynajmniej w 2.6 i 2.7), że float ma wszystkie znane bogate funkcje porównawcze: __lt__(), __gt__, __eq__itp.

>>> (5.0).__gt__(4.5)
True

ale an int nie

>>> (5).__gt__(4)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'int' object has no attribute '__gt__'

Co jest dla mnie dziwne, ponieważ sam operator działa dobrze

>>> 5 > 4
True

Nawet łańcuchy obsługują funkcje porównania

>>> "hat".__gt__("ace")
True

ale wszystkie int ma jest __cmp__()

Wydaje mi się to dziwne, więc zastanawiałem się, dlaczego tak się stało.

Właśnie przetestowany i działa zgodnie z oczekiwaniami w pythonie 3, więc zakładam pewne starsze przyczyny. Nadal chciałbym usłyszeć właściwe wyjaśnienie;)


21
2018-05-30 04:19


pochodzenie




Odpowiedzi:


Jeśli spojrzymy na PEP 207 dla bogatych porównań na końcu jest to interesujące zdanie:

Obecny już wpis, który dotyczy porównań całkowitych, będzie nadal obowiązywał, nie powodując żadnych kosztów wydajności w najczęstszych przypadkach.

Wygląda więc na to, że w wersji 2.x istnieje optymalizacja do porównania liczb całkowitych. Jeśli my spójrz na kod źródłowy możemy to znaleźć:

case COMPARE_OP:
    w = POP();
    v = TOP();
    if (PyInt_CheckExact(w) && PyInt_CheckExact(v)) {
        /* INLINE: cmp(int, int) */
        register long a, b;
        register int res;
        a = PyInt_AS_LONG(v);
        b = PyInt_AS_LONG(w);
        switch (oparg) {
        case PyCmp_LT: res = a <  b; break;
        case PyCmp_LE: res = a <= b; break;
        case PyCmp_EQ: res = a == b; break;
        case PyCmp_NE: res = a != b; break;
        case PyCmp_GT: res = a >  b; break;
        case PyCmp_GE: res = a >= b; break;
        case PyCmp_IS: res = v == w; break;
        case PyCmp_IS_NOT: res = v != w; break;
        default: goto slow_compare;
        }
        x = res ? Py_True : Py_False;
        Py_INCREF(x);
    }
    else {
      slow_compare:
        x = cmp_outcome(oparg, v, w);
    }

Wygląda więc na to, że w wersji 2.x istniała optymalizacja wydajności - pozwalając kodowi C bezpośrednio porównywać liczby całkowite - które nie zostałyby zachowane, gdyby wdrożono operatory porównania.

Teraz w Pythonie 3 __cmp__ nie jest już obsługiwany, więc operatory porównania muszą tam być. Teraz nie jest tak, jak można stwierdzić, spowodować trafienia wydajności. Na przykład porównaj:

Python 2.7.1 (r271:86832, Jun 16 2011, 16:59:05) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>> timeit.timeit("2 < 1")
0.06980299949645996

do:

Python 3.2.3 (v3.2.3:3d0686d90f55, Apr 10 2012, 11:25:50) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>> timeit.timeit("2 < 1")
0.06682920455932617

Wygląda więc na to, że istnieją podobne optymalizacje, ale domyślam się, że wyrok sądu był taki, że umieszczenie ich wszystkich w gałęzi 2.x byłoby zbyt wielką zmianą, gdyby uwzględniono kompatybilność wsteczną.

W wersji 2.x, jeśli chcesz czegoś takiego jak bogate metody porównywania, możesz uzyskać do nich dostęp za pośrednictwem operator moduł:

>>> import operator
>>> operator.gt(2,1)
True

21
2018-05-30 05:09





__cmp__() jest staroświeckim sposobem dokonywania porównań i jest przestarzałe na korzyść bogatych operatorów (__lt__, __le__ itp.), które zostały wprowadzone tylko w Pythonie 2.1. Prawdopodobnie przejście nie było zakończone od wersji 2.7.x - podczas gdy w języku Python 3.x __cmp__ jest całkowicie usunięty.

Haskell ma najbardziej elegancką implementację, jaką widziałem - być Ord (porządkowy) typ danych, wystarczy określić sposób < i = działa, a sama czcionka dostarcza domyślne implementacje dla <=, > i >= pod względem tych dwojga (które są mile widziane, aby określić siebie, jeśli chcesz). Możesz napisać taką klasę samodzielnie w Pythonie, nie wiesz, dlaczego nie jest to domyślne; prawdopodobnie z powodów wydajności.


5
2018-05-30 04:29





Jak powiedział hircus, the __cmp__ Porównania stylu są przestarzałe na korzyść bogatych operatorów (__lt__, ...) w Pythonie 3. Pierwotnie zaimplementowano porównania za pomocą __cmp__, ale są pewne typy / sytuacje, w których proste __cmp__ operator nie wystarcza (na przykład mogą obsługiwać wystąpienia klasy Color == i !=, ale nie < lub >), więc dodano operatorów bogatych porównania, pozostawiając __cmp__ w miejscu dla kompatybilności wstecznej. Podążając za pythonową filozofią "Powinno istnieć jeden - a najlepiej tylko jeden - w oczywisty sposób to zrobić"1 starsza obsługa została usunięta w Pythonie 3, gdy zgodność wsteczna mogła zostać poświęcona.

W języku Python 2, natomiast int wciąż używa __cmp__ aby nie przerwać kompatybilności wstecznej, nie wszystkie liczby zmiennoprzecinkowe są mniejsze niż, większe niż lub równe innym liczbom zmiennoprzecinkowym (np. (float('nan') < 0.0, float('nan') == 0.0, float('nan') > 0.0) ocenia na (False, False, False), więc co powinno float('nan').__cmp__(0.0) wrócić?), więc float musi korzystać z nowszych operatorów porównania bogatych.

1: Spróbuj wpisać "zaimportuj to" do powłoki Pythona.


1
2018-05-30 05:15