Python якщо ні == vs якщо! =


183

Яка різниця між цими двома рядками коду:

if not x == 'val':

і

if x != 'val':

Чи одна ефективніша за іншу?

Було б краще використовувати

if x == 'val':
    pass
else:

101
Краще той, який ви можете прочитати, я сумніваюся, що вузьке місце у вашій програмі буде тут
Thomas Ayoub

1
Це питання мене цікавить у справі "x не в списку" та "не x у списку"
SomethingSomething

5
@SomethingSomething вони трактуються однаково.
jonrsharpe

4
@SomethingSomething посилання на мій вище коментар: stackoverflow.com/q/8738388/3001761
jonrsharpe

1
@SomethingSomething те ж саме і для тих; це інтерпретація синтаксису, неважливо, що це два операнди.
jonrsharpe

Відповіді:


229

Використовується disдля перегляду байт-коду, згенерованого для двох версій:

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   

Останній має менше операцій, і тому, ймовірно, буде дещо ефективнішим.


Було відзначено, в commments (спасибі, @Quincunx ) , що якщо у вас є if foo != barпроти if not foo == barкількість операцій точно так же, це тільки те , що COMPARE_OPзміни і POP_JUMP_IF_TRUEперемикається в 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

У цьому випадку, якщо б не було різниці в обсязі роботи, необхідної для кожного порівняння, навряд чи ви побачите різницю в продуктивності.


Однак зауважте, що дві версії не завжди будуть логічно однаковими , оскільки це залежатиме від реалізації __eq__та __ne__для об'єктів, про які йдеться. Відповідно до документації щодо моделі даних :

Між операторами порівняння не маються на увазі зв’язки. Істина x==yне означає, що x!=yце помилково.

Наприклад:

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


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

Нарешті, і, мабуть, найголовніше: загалом там, де вони логічно однакові, x != yнабагато читабельніше, ніжnot x == y .


29
На практиці будь-який клас, який не __eq__відповідає __ne__, розбивається на плоскість.
Кевін

8
Зверніть увагу, що це не завжди правда, що not x == yє ще одна інструкція. Коли я вклав код у if, виявилося, що вони обоє мають однакову кількість інструкцій, просто одна мала, POP_JUMP_IF_TRUEа друга POP_JUMP_IF_FALSE(це була єдина різниця між ними, окрім використання іншого COMPARE_OP). Коли я склав код без ifs, я отримав те, що у вас є.
Джастін

1
Інший приклад, коли ==і !=не є взаємовиключними, - це SQL-подібна реалізація, що включає nullзначення. У SQL nullне повертається trueдо !=порівняно з будь-яким іншим значенням, тому реалізація python інтерфейсів SQL також може мати ту саму проблему.
Джо

Я починаю бажати, щоб я не звертався до можливої ​​різниці між, not ==і !=, здається, це найцікавіша частина моєї відповіді! Я не думаю, що тут не варто зупинятися, якщо, чому і коли це має сенс - див. Наприклад, чому Python має __ne__метод оператора, а не просто __eq__?
jonrsharpe

29

@jonrsharpe чудово пояснює, що відбувається. Я думав, що я просто покажу різницю у часі, коли запускати кожен із трьох варіантів 1000000000 разів (достатньо, щоб показати невелику різницю).

Використовуваний код:

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)

І результати профайлера cProfile:

введіть тут опис зображення

Таким чином, ми можемо бачити, що між if not x == 'val':і між ними є різниця у хвилинах ~ 0.7% if x != 'val':. З них if x != 'val':найшвидший.

Однак, що найдивніше, ми можемо це побачити

if x == 'val':
        pass
    else:

насправді найшвидший і б'є if x != 'val':на ~ 0,3%. Це не дуже читабельно, але, мабуть, якби ви хотіли незначного поліпшення продуктивності, можна було б піти по цьому маршруту.


31
Я сподіваюся, що всі знають, що не діяти на цю інформацію! Внесення нечитабельних змін для покращення на 0,3% - або навіть 10% покращення - рідко є доброю ідеєю, і подібне поліпшення, швидше за все, може бути похитним (і не дуже хорошим способом : дуже незначні зміни в процесі виконання Python може усунути або навіть відмінити будь-який виграш
Мальволіо

1
@Malvolio Крім того, існують різні реалізації Python.
Cees Timmerman

6

У першому Python повинен виконати ще одну операцію, ніж потрібно (замість того, щоб просто перевірити, що не дорівнює, він повинен перевірити, чи не правда, що він рівний, таким чином, ще одну операцію). Неможливо було б визначити різницю від одного виконання, але якщо запустити багато разів, друге було б більш ефективним. Я б взагалі використовував другий, але математично вони однакові


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

Тут ви можете побачити, що це not x == yще одна інструкція, ніж x != y. Тому різниця в продуктивності в більшості випадків буде дуже невеликою, якщо ви не робите мільйони порівнянь, і навіть тоді це, швидше за все, не стане причиною вузького місця.


5

Додаткове зауваження, оскільки інші відповіді відповідали на ваше питання здебільшого правильно, полягає в тому, що якщо клас визначає лише, __eq__()а не __ne__(), то ваш COMPARE_OP (!=)запустить __eq__()і заперечить його. У той час ваш третій варіант, ймовірно, буде крихітним, більш ефективним, але його слід враховувати лише в тому випадку, якщо вам ПОТРІБНА швидкість, оскільки це важко зрозуміти швидко.


3

Йдеться про ваш спосіб його читання. notОператор динамічний, тому ви можете його застосувати

if not x == 'val':

Але !=можна сприймати в кращому контексті як оператор, який робить протилежне тому, що ==робить.


3
Що ви маєте на увазі " notоператор динамічний" ?
jonrsharpe

1
@jonrsharpe Я думаю, що він означає, що "не x" зателефонує x .__ bool __ () [python 3 - python 2 використовує ненульовий характер ] і поверне результат (див. docs.python.org/3/reference/datamodel.html#object. __bool__ )
jdferreira

1

Я хочу розкрити свій коментар з читабельністю вище.

Знову ж таки, я цілком погоджуюся з читабельністю, що переважає інші (неістотні) ефекти.

Я хотів би зазначити, що мозок інтерпретує "позитив" швидше, ніж "негативно". Наприклад, "стоп" проти "не йди" (досить хитрий приклад через різницю в кількості слів).

Тож надано вибір:

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

є кращим для функціонально-еквівалентного:

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

Менша читабельність / зрозумілість призводить до збільшення помилок. Можливо, не в початковому кодуванні, але (не настільки розумний, як ви!) Зміна обслуговування ...


1
мозок інтерпретує «позитив» швидше, ніж це робить «негатив» - це це з досвіду чи ви читали з цього приводу дослідження? Я просто запитую, тому що, залежно від коду в (зробіть це) або (зробіть це), я вважаю, що! = B простіше зрозуміти.
lafferc
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.