Python, jeśli nie == vs if!!


183

Jaka jest różnica między tymi dwoma wierszami kodu:

if not x == 'val':

i

if x != 'val':

Czy jedno jest bardziej wydajne od drugiego?

Czy lepiej byłoby użyć

if x == 'val':
    pass
else:

101
Lepszy jest ten, który możesz przeczytać. Wątpię, czy wąskie gardło twojego programu będzie tutaj
Thomas Ayoub

1
To pytanie interesuje mnie w przypadku „x nie na liście” i „nie x na liście”
SomethingSomething

5
@SomethingSomehing są interpretowane identycznie.
jonrsharpe

4
@SomethingSomething odniesienia do mojego powyższego komentarza: stackoverflow.com/q/8738388/3001761
jonrsharpe

1
@SomethingCoś też jest takie samo dla tych; to jak interpretowana jest składnia, nie ma znaczenia, jakie są dwa operandy.
jonrsharpe

Odpowiedzi:


229

Używając disdo przeglądania kodu bajtowego wygenerowanego dla dwóch wersji:

not ==

  4           0 LOAD_FAST                0 (foo)
              3 LOAD_FAST                1 (bar)
              6 COMPARE_OP               2 (==)
              9 UNARY_NOT           
             10 RETURN_VALUE   

!=

  4           0 LOAD_FAST                0 (foo)
              3 LOAD_FAST                1 (bar)
              6 COMPARE_OP               3 (!=)
              9 RETURN_VALUE   

Ten ostatni ma mniej operacji i dlatego może być nieco bardziej wydajny.


W komisjach (dzięki, @Quincunx ) wskazano, że tam, gdzie masz, if foo != bara if not foo == barliczba operacji jest dokładnie taka sama, po prostu COMPARE_OPzmiany i zmiana POP_JUMP_IF_TRUEna POP_JUMP_IF_FALSE:

not ==:

  2           0 LOAD_FAST                0 (foo)
              3 LOAD_FAST                1 (bar)
              6 COMPARE_OP               2 (==)
              9 POP_JUMP_IF_TRUE        16

!=

  2           0 LOAD_FAST                0 (foo)
              3 LOAD_FAST                1 (bar)
              6 COMPARE_OP               3 (!=)
              9 POP_JUMP_IF_FALSE       16

W takim przypadku, chyba że wystąpi różnica w ilości pracy wymaganej do każdego porównania, jest mało prawdopodobne, aby w ogóle dostrzegłaś różnicę w wydajności.


Należy jednak pamiętać, że dwie wersje nie zawsze będą logicznie identyczne , ponieważ będą zależeć od implementacji __eq__i __ne__dla przedmiotowych obiektów. Według dokumentacji modelu danych :

Nie ma żadnych domniemanych relacji między operatorami porównania. Prawda x==ynie sugeruje, że x!=yjest to fałsz.

Na przykład:

>>> class Dummy(object):
    def __eq__(self, other):
        return True
    def __ne__(self, other):
        return True


>>> not Dummy() == Dummy()
False
>>> Dummy() != Dummy()
True

Wreszcie, a być może najważniejsze: ogólnie rzecz biorąc, gdy oba logicznie identyczne, x != yjest o wiele bardziej czytelny niżnot x == y .


29
W praktyce każda klasa __eq__niespójna __ne__jest zepsuta.
Kevin

8
Pamiętaj, że nie zawsze jest prawdą, że not x == yma jeszcze jedną instrukcję. Kiedy wstawiłem kod do if, okazało się, że oba mają tę samą liczbę instrukcji, tylko jedna z nich POP_JUMP_IF_TRUEi druga POP_JUMP_IF_FALSE(to była jedyna różnica między nimi, inna niż użycie innej COMPARE_OP). Kiedy skompilowałem kod bez ifs, dostałem to, co masz.
Justin

1
Innym przykładem, gdzie ==i !=nie wykluczają się wzajemnie jest SQL-like wdrożeniowych obejmujących nullwartości. W SQL nullnie wraca truedo !=żadnej innej wartości, więc implementacje interfejsów SQL w Pythonie mogą mieć ten sam problem.
Joe

Zaczynam żałować, że nie wspomniałem o możliwej różnicy między, not ==i !=wydaje się, że jest to najbardziej interesująca część mojej odpowiedzi! Nie sądzę, że jest to miejsce do zastanowienia się, czy, dlaczego i kiedy ma to sens - patrz np. Dlaczego Python ma __ne__metodę operatora zamiast tylko __eq__?
jonrsharpe

29

@jonrsharpe ma doskonałe wyjaśnienie tego, co się dzieje. Pomyślałem, że pokażę różnicę czasu podczas uruchamiania każdej z 3 opcji 10 000 000 razy (wystarczy, aby pokazać niewielką różnicę).

Zastosowany kod:

def a(x):
    if x != 'val':
        pass


def b(x):
    if not x == 'val':
        pass


def c(x):
    if x == 'val':
        pass
    else:
        pass


x = 1
for i in range(10000000):
    a(x)
    b(x)
    c(x)

A wyniki profilera cProfile:

wprowadź opis zdjęcia tutaj

Widzimy więc, że istnieje bardzo niewielka różnica ~ 0,7% między if not x == 'val':i if x != 'val':. Spośród nich if x != 'val':jest najszybszy.

Co jednak najbardziej zaskakujące, widzimy to

if x == 'val':
        pass
    else:

jest w rzeczywistości najszybszy i bije if x != 'val':o ~ 0,3%. Nie jest to zbyt czytelne, ale myślę, że jeśli chcesz nieznacznej poprawy wydajności, możesz pójść tą drogą.


31
Mam nadzieję, że wszyscy wiedzą, że nie powinni działać na podstawie tych informacji! Wprowadzanie nieczytelnych zmian w celu poprawy o 0,3% - lub nawet o 10% - rzadko jest dobrym pomysłem, a tego rodzaju ulepszenia najprawdopodobniej zanikają (i nie w dobry sposób : bardzo niewielkie zmiany w środowisku wykonawczym Pythona może wyeliminować lub nawet odwrócić jakiekolwiek wzmocnienie
Malvolio

1
@Malvolio Ponadto istnieją różne implementacje języka Python.
Cees Timmerman,

6

W pierwszym Python musi wykonać jeszcze jedną operację więcej niż to konieczne (zamiast po prostu sprawdzić, że nie jest równy, musi sprawdzić, czy nie jest prawdą, że jest równy, a zatem jeszcze jedną operację). Niemożliwe byłoby odróżnienie od jednego wykonania, ale jeśli uruchomione wiele razy, drugie będzie bardziej wydajne. Ogólnie skorzystałbym z drugiego, ale matematycznie są takie same


5
>>> from dis import dis
>>> dis(compile('not 10 == 20', '', 'exec'))
  1           0 LOAD_CONST               0 (10)
              3 LOAD_CONST               1 (20)
              6 COMPARE_OP               2 (==)
              9 UNARY_NOT
             10 POP_TOP
             11 LOAD_CONST               2 (None)
             14 RETURN_VALUE
>>> dis(compile('10 != 20', '', 'exec'))
  1           0 LOAD_CONST               0 (10)
              3 LOAD_CONST               1 (20)
              6 COMPARE_OP               3 (!=)
              9 POP_TOP
             10 LOAD_CONST               2 (None)
             13 RETURN_VALUE

Tutaj możesz zobaczyć, że not x == yma jeszcze jedną instrukcję niż x != y. Różnica w wydajności będzie w większości przypadków bardzo mała, chyba że wykonasz miliony porównań, a nawet wtedy prawdopodobnie nie będzie to przyczyną wąskiego gardła.


5

Dodatkowa uwaga, ponieważ inne odpowiedzi odpowiedziały na twoje pytanie w większości poprawnie, jest to, że jeśli klasa tylko definiuje, __eq__()a nie __ne__(), to COMPARE_OP (!=)będziesz biegał __eq__()i negował to. W tym czasie trzecia opcja może być nieco bardziej wydajna, ale należy ją rozważyć tylko wtedy, gdy POTRZEBUJESZ prędkości, ponieważ trudno ją szybko zrozumieć.


3

Chodzi o twój sposób czytania. notoperator jest dynamiczny, dlatego możesz go zastosować

if not x == 'val':

Ale !=może być odczytany w lepszym kontekście jako operator, który robi coś przeciwnego do tego, co ==robi.


3
Co masz na myśli mówiąc notoperator jest dynamiczny” ?
jonrsharpe

1
@jonrsharpe Myślę, że on ma na myśli, że „nie x” wywoła x .__ bool __ () [python 3 - python 2 używa niezerowej ] i cofnie wynik (patrz docs.python.org/3/reference/datamodel.html#object. __bool__ )
jdferreira

1

Chcę rozwinąć mój komentarz dotyczący czytelności powyżej.

Ponownie całkowicie zgadzam się z czytelnością przesłaniającą inne problemy (nieznaczące w wydajności).

Chciałbym zwrócić uwagę, że mózg interpretuje „pozytywne” szybciej niż „negatywne”. Np. „Stop” vs. „nie idź” (raczej kiepski przykład z powodu różnicy w liczbie słów).

Tak więc wybór:

if a == b
    (do this)
else
    (do that)

jest lepszy niż funkcjonalnie równoważny:

if a != b
    (do that)
else
    (do this)

Mniejsza czytelność / zrozumiałość prowadzi do większej liczby błędów. Być może nie w początkowym kodowaniu, ale zmiany w konserwacji (nie tak mądre jak ty!) ...


1
mózg interpretuje „pozytywny” szybciej niż „negatywny”, czy wynika to z doświadczenia, czy czytasz na ten temat badania? Po prostu pytam, ponieważ w zależności od kodu w (zrób to) lub (zrób to) znajduję a! = B łatwiejsze do zrozumienia.
lafferc
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.