Python если нет == против если! =

183

В чем разница между этими двумя строками кода:

if not x == 'val':

и

if x != 'val':

Один эффективнее другого?

Было бы лучше использовать

if x == 'val':
    pass
else:
lafferc
источник
101
Чем лучше тот, который вы можете прочитать, я сомневаюсь, что узкое место вашей программы будет здесь
Томас Аюб
1
Этот вопрос меня интересует в случаях «х нет в списке» и «не х в списке»
SomethingSomething
5
@ Что-то они интерпретируются одинаково.
Jonrsharpe
4
Ссылка @SomethingSomething для моего комментария выше: stackoverflow.com/q/8738388/3001761
jonrsharpe
1
@ Что-то тоже самое для тех же; это то, как интерпретируется синтаксис, не имеет значения, что это за два операнда.
Джонршарп

Ответы:

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 .

jonrsharpe
источник
29
На практике любой класс, у которого есть __eq__несоответствие __ne__, полностью разрушен.
Кевин
8
Обратите внимание, что не всегда верно, что not x == yесть еще одна инструкция. Когда я поместил код в an if, оказалось, что у них обоих одинаковое количество инструкций, только у одной POP_JUMP_IF_TRUEи другой POP_JUMP_IF_FALSE(это было единственное различие между ними, кроме использования разных COMPARE_OP). Когда я скомпилировал код без ifs, я получил то, что вы получили.
Джастин
1
Другим примером, когда ==и !=не являются взаимоисключающими, является SQL-подобная реализация, включающая nullзначения. В SQL nullне возвращается trueпо !=сравнению с любым другим значением, поэтому реализации Python интерфейсов SQL также могут иметь такую ​​же проблему.
Джо
Я начинаю сожалеть, что не упомянул о возможной разнице между, not ==и !=, похоже, это самая интересная часть моего ответа! Я не думаю, что это место, на котором стоит останавливаться, если, почему и когда это имеет смысл - см., Например, Почему в Python есть __ne__метод оператора, а не просто __eq__?
Джоншарп
29

У @jonrsharpe есть отличное объяснение того, что происходит. Я думал, что просто покажу разницу во времени при запуске каждого из 3 вариантов 10 000 000 раз (достаточно, чтобы показать небольшую разницу).

Используемый код:

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:

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

Итак, мы можем видеть, что есть очень маленькая разница в ~ 0,7% между if not x == 'val':и if x != 'val':. Из них if x != 'val':самый быстрый.

Однако, что самое удивительное, мы видим, что

if x == 'val':
        pass
    else:

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

Красная Сдвиг
источник
31
Я надеюсь, что все знают, чтобы не действовать на эту информацию! Внесение нечитаемых изменений для улучшения на 0,3% - или даже на 10% - редко является хорошей идеей, и такое улучшение, скорее всего, будет мимолетным (и не очень хорошим способом : очень незначительные изменения во время выполнения Python может устранить или даже обратить вспять любой выигрыш.
Мальволио
1
@Malvolio Кроме того, существуют разные реализации Python.
Сис Тиммерман,
6

В первом случае Python должен выполнить на одну операцию больше, чем необходимо (вместо того, чтобы просто проверять, что он не равен, он должен проверять, не является ли он истинным, что он равен, то есть еще одну операцию) Было бы невозможно отличить одно выполнение от другого, но если его запускать много раз, второе будет более эффективным. В целом я бы использовал второй, но математически они одинаковы

JediPythonClone
источник
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. Таким образом, разница в производительности будет очень мала в большинстве случаев, если вы не проводите миллионы сравнений, и даже тогда это, скорее всего, не станет причиной узкого места.

kylie.a
источник
5

Дополнительное замечание, поскольку другие ответы в основном правильно ответили на ваш вопрос: если класс только определяет, __eq__()а что нет __ne__(), то вы COMPARE_OP (!=)его запустите __eq__()и отрицаете. В то время ваш третий вариант, вероятно, будет чуть более эффективным, но его следует рассматривать только в том случае, если вам НУЖНА скорость, поскольку его сложно понять быстро.

Якоб Циммерман
источник
3

Это о том, как ты это читаешь. notОператор динамический, поэтому вы можете применить его в

if not x == 'val':

Но !=может быть прочитан в лучшем контексте как оператор, который делает противоположное тому, что ==делает.

Химаншу Мишра
источник
3
Что вы имеете в виду " notоператор динамический" ?
Джоншарпе
1
@jonrsharpe Я думаю, он имеет в виду, что «not 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
Мозг интерпретирует «положительный» быстрее, чем «отрицательный», это из опыта или вы читали исследования об этом? Я просто спрашиваю, потому что в зависимости от кода в (сделать это) или (сделать это), я нахожу a! = B легче понять.
lafferc