Qual é a diferença entre essas duas linhas de código:
if not x == 'val':
e
if x != 'val':
Um é mais eficiente que o outro?
Seria melhor usar
if x == 'val':
pass
else:
Qual é a diferença entre essas duas linhas de código:
if not x == 'val':
e
if x != 'val':
Um é mais eficiente que o outro?
Seria melhor usar
if x == 'val':
pass
else:
Respostas:
Utilizando dis
para olhar o bytecode gerado para as duas versões:
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
Este último possui menos operações e, portanto, provavelmente será um pouco mais eficiente.
Foi indicado nos comentários (obrigado, @Quincunx ) que onde você tem if foo != bar
vs. if not foo == bar
o número de operações é exatamente o mesmo, é só que as COMPARE_OP
alterações e as mudanças POP_JUMP_IF_TRUE
para 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
Nesse caso, a menos que haja uma diferença na quantidade de trabalho necessária para cada comparação, é improvável que você veja alguma diferença de desempenho.
No entanto, observe que as duas versões nem sempre serão logicamente idênticas , pois dependerão das implementações __eq__
e __ne__
dos objetos em questão. Por a documentação do modelo de dados :
Não há relacionamentos implícitos entre os operadores de comparação. A verdade de
x==y
não implica quex!=y
seja falso.
Por exemplo:
>>> class Dummy(object):
def __eq__(self, other):
return True
def __ne__(self, other):
return True
>>> not Dummy() == Dummy()
False
>>> Dummy() != Dummy()
True
Finalmente, e talvez o mais importante: em geral, onde os dois são logicamente idênticos, x != y
é muito mais legível do quenot x == y
.
__eq__
inconsistente __ne__
é completamente quebrada.
not x == y
há mais uma instrução. Quando eu coloquei o código em um if
, descobriu-se que os dois tinham o mesmo número de instruções, apenas um tinha POP_JUMP_IF_TRUE
e o outro POP_JUMP_IF_FALSE
(essa era a única diferença entre eles, além de usar um diferente COMPARE_OP
). Quando compilei o código sem os if
s, obtive o que você conseguiu.
==
e !=
não são mutuamente exclusivos é uma implementação semelhante a SQL envolvendo null
valores. Em SQL null
não retorna true
a !=
comparação com qualquer outro valor, então implementações de python de interfaces SQL também podem ter o mesmo problema.
not ==
e !=
, parece ser a parte mais interessante da minha resposta! Eu não acho que este seja o lugar para pensar se, por que e quando isso faz sentido - veja, por exemplo, por que o Python tem um __ne__
método de operador em vez de apenas __eq__
?
@jonrsharpe tem uma excelente explicação do que está acontecendo. Eu pensei em mostrar a diferença no tempo ao executar cada uma das três opções 10.000.000 de vezes (o suficiente para mostrar uma pequena diferença).
Código usado:
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)
E o perfil do cProfile resulta:
Portanto, podemos ver que há uma diferença muito pequena de ~ 0,7% entre if not x == 'val':
e if x != 'val':
. Destes, if x != 'val':
é o mais rápido.
Surpreendentemente, porém, podemos ver que
if x == 'val':
pass
else:
é de fato o mais rápido e supera if x != 'val':
em ~ 0,3%. Isso não é muito legível, mas acho que se você quiser uma melhoria insignificante de desempenho, poderá seguir esse caminho.
No primeiro, o Python precisa executar mais uma operação do que o necessário (em vez de apenas verificar não igual a ele, deve verificar se não é verdade que é igual, portanto, mais uma operação). Seria impossível distinguir a diferença de uma execução, mas se executada muitas vezes, a segunda seria mais eficiente. No geral, eu usaria o segundo, mas matematicamente eles são os mesmos
>>> 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
Aqui você pode ver que not x == y
tem mais uma instrução que x != y
. Portanto, a diferença de desempenho será muito pequena na maioria dos casos, a menos que você esteja fazendo milhões de comparações e mesmo assim isso provavelmente não será a causa de um gargalo.
Uma observação adicional, uma vez que as outras respostas responderam à sua pergunta principalmente corretamente, é que, se uma classe apenas define __eq__()
e não __ne__()
, então você COMPARE_OP (!=)
a executará __eq__()
e a negará. Nesse momento, sua terceira opção provavelmente será um pouco mais eficiente, mas só deve ser considerada se você precisar da velocidade, pois é difícil entender rapidamente.
É sobre a sua maneira de lê-lo. not
operador é dinâmico, é por isso que você pode aplicá-lo
if not x == 'val':
Mas !=
poderia ser lido em um contexto melhor como um operador que faz o oposto do que ==
faz.
not
operador é dinâmico" ?
Quero expandir meu comentário de legibilidade acima.
Mais uma vez, concordo plenamente com a legibilidade, substituindo outras preocupações (insignificantes de desempenho).
O que eu gostaria de salientar é que o cérebro interpreta "positivo" mais rapidamente do que "negativo". Por exemplo, "parar" vs. "não seguir" (um exemplo bastante ruim devido à diferença no número de palavras).
Então, dada uma escolha:
if a == b
(do this)
else
(do that)
é preferível ao funcionalmente equivalente:
if a != b
(do that)
else
(do this)
Menos legibilidade / compreensão leva a mais erros. Talvez não na codificação inicial, mas a manutenção (não tão inteligente quanto você!) Muda ...