В чем разница между этими двумя строками кода:
if not x == 'val':
и
if x != 'val':
Один эффективнее другого?
Было бы лучше использовать
if x == 'val':
pass
else:
В чем разница между этими двумя строками кода:
if not x == 'val':
и
if x != 'val':
Один эффективнее другого?
Было бы лучше использовать
if x == 'val':
pass
else:
Ответы:
Использование 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
.
__eq__
несоответствие __ne__
, полностью разрушен.
not x == y
есть еще одна инструкция. Когда я поместил код в an if
, оказалось, что у них обоих одинаковое количество инструкций, только у одной POP_JUMP_IF_TRUE
и другой POP_JUMP_IF_FALSE
(это было единственное различие между ними, кроме использования разных COMPARE_OP
). Когда я скомпилировал код без if
s, я получил то, что вы получили.
==
и !=
не являются взаимоисключающими, является SQL-подобная реализация, включающая null
значения. В SQL null
не возвращается true
по !=
сравнению с любым другим значением, поэтому реализации Python интерфейсов SQL также могут иметь такую же проблему.
not ==
и !=
, похоже, это самая интересная часть моего ответа! Я не думаю, что это место, на котором стоит останавливаться, если, почему и когда это имеет смысл - см., Например, Почему в Python есть __ne__
метод оператора, а не просто __eq__
?
У @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%. Это не очень читабельно, но, думаю, если вы хотите незначительного улучшения производительности, можно пойти по этому пути.
В первом случае Python должен выполнить на одну операцию больше, чем необходимо (вместо того, чтобы просто проверять, что он не равен, он должен проверять, не является ли он истинным, что он равен, то есть еще одну операцию) Было бы невозможно отличить одно выполнение от другого, но если его запускать много раз, второе будет более эффективным. В целом я бы использовал второй, но математически они одинаковы
>>> 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
. Таким образом, разница в производительности будет очень мала в большинстве случаев, если вы не проводите миллионы сравнений, и даже тогда это, скорее всего, не станет причиной узкого места.
Дополнительное замечание, поскольку другие ответы в основном правильно ответили на ваш вопрос: если класс только определяет, __eq__()
а что нет __ne__()
, то вы COMPARE_OP (!=)
его запустите __eq__()
и отрицаете. В то время ваш третий вариант, вероятно, будет чуть более эффективным, но его следует рассматривать только в том случае, если вам НУЖНА скорость, поскольку его сложно понять быстро.
Это о том, как ты это читаешь. not
Оператор динамический, поэтому вы можете применить его в
if not x == 'val':
Но !=
может быть прочитан в лучшем контексте как оператор, который делает противоположное тому, что ==
делает.
not
оператор динамический" ?
Я хочу расширить мой комментарий читабельности выше.
Опять же, я полностью согласен с тем, что читаемость перекрывает другие (незначительные по производительности) проблемы.
Я хотел бы отметить, что мозг интерпретирует «положительное» быстрее, чем «отрицательное». Например, «стоп» против «не уходи» (довольно паршивый пример из-за разницы в количестве слов).
Итак, дан выбор:
if a == b
(do this)
else
(do that)
предпочтительнее функционально-эквивалентного:
if a != b
(do that)
else
(do this)
Меньшая читаемость / понятность приводит к большему количеству ошибок. Возможно, не в первоначальном кодировании, но (не так умно, как вы!) Изменения в обслуживании ...