>>> timeit.timeit("'x' in ('x',)")
0.04869917374131205
>>> timeit.timeit("'x' == 'x'")
0.06144205736110564
Também funciona para tuplas com vários elementos, ambas as versões parecem crescer linearmente:
>>> timeit.timeit("'x' in ('x', 'y')")
0.04866674801541748
>>> timeit.timeit("'x' == 'x' or 'x' == 'y'")
0.06565782838087131
>>> timeit.timeit("'x' in ('y', 'x')")
0.08975995576448526
>>> timeit.timeit("'x' == 'y' or 'x' == 'y'")
0.12992391047427532
Com base nisso, acho que devo começar a usar totalmentein
em qualquer lugar, em vez de ==
!
python
performance
python-3.x
python-internals
Markus Meskanen
fonte
fonte
in
qualquer lugar, em vez de==
. É uma otimização prematura que prejudica a legibilidade.x ="!foo"
x in ("!foo",)
ex == "!foo"
in
em vez de==
é mudar para C.Respostas:
Como mencionei a David Wolever, há mais do que aparenta; ambos os métodos são despachados para
is
; você pode provar isso fazendoO primeiro só pode ser tão rápido porque verifica por identidade.
Para descobrir por que um levaria mais tempo que o outro, vamos rastrear a execução.
Ambos começam
ceval.c
,COMPARE_OP
desde que é o bytecode envolvidoIsso exibe os valores da pilha (tecnicamente, apenas um)
e executa a comparação:
cmp_outcome
é isto:É aqui que os caminhos se dividem. O
PyCmp_IN
ramo fazObserve que uma tupla é definida como
Então o ramo
serão tomadas e
*sqm->sq_contains
, qual é a função(objobjproc)tuplecontains
, serão tomadas.Isso faz
... Espere, não foi isso
PyObject_RichCompareBool
que o outro ramo levou? Não, foi issoPyObject_RichCompare
.Esse caminho do código era curto, portanto, provavelmente se resume à velocidade desses dois. Vamos comparar.
O caminho do código
PyObject_RichCompareBool
termina quase imediatamente. PoisPyObject_RichCompare
, fazO
Py_EnterRecursiveCall
/Py_LeaveRecursiveCall
combo não é utilizado no caminho anterior, mas essas são macros relativamente rápidas que entrarão em curto-circuito após incrementar e decrementar algumas globais.do_richcompare
faz:Isso faz algumas verificações rápidas para chamar
v->ob_type->tp_richcompare
qual équal faz
Ou seja, esses atalhos em
left == right
... mas somente depois de fazerEm todos os caminhos, é algo parecido com isto (recursivamente manualmente recursivamente, desenrolando e podando ramificações conhecidas)
vs
Agora,
PyUnicode_Check
ePyUnicode_READY
são muito baratos, pois apenas checam alguns campos, mas deve ser óbvio que o primeiro é um caminho de código menor, possui menos chamadas de função, apenas uma instrução de switch e é um pouco mais fino.TL; DR:
Ambos despacham para
if (left_pointer == right_pointer)
; a diferença é quanto trabalho eles fazem para chegar lá.in
apenas faz menos.fonte
Há três fatores em jogo aqui que, combinados, produzem esse comportamento surpreendente.
Primeiro: o
in
operador pega um atalho e verifica a identidade (x is y
) antes de verificar a igualdade (x == y
):Segundo: devido à internação de strings do Python, os dois
"x"
s in"x" in ("x", )
serão idênticos:(grande aviso: este é um comportamento específico da implementação!
is
deveriam nunca mais ser usado para comparar strings porque ele vai dar respostas surpreendentes, às vezes, por exemplo"x" * 100 is "x" * 100 ==> False
)Terceiro: conforme detalhado na fantástica resposta de Veedrac ,
tuple.__contains__
(x in (y, )
é aproximadamente equivalente a(y, ).__contains__(x)
) chega ao ponto de executar a verificação de identidade mais rapidamente do questr.__eq__
(novamente,x == y
é aproximadamente equivalente ax.__eq__(y)
).Você pode ver evidências disso, porque
x in (y, )
é significativamente mais lento que o equivalente logicamentex == y
:O
x in (y, )
caso é mais lento porque, depois que ais
comparação falha, oin
operador volta à verificação de igualdade normal (ou seja, usando==
), portanto, a comparação leva aproximadamente o mesmo tempo que==
, tornando a operação inteira mais lenta devido à sobrecarga de criação da tupla , caminhando por seus membros, etc.Observe também que
a in (b, )
é apenas mais rápido quandoa is b
:(por que é
a in (b, )
mais rápido quea is b or a == b
? Meu palpite seria menos instruções da máquina virtual -a in (b, )
são apenas ~ 3 instruções, ondea is b or a == b
haverá mais algumas instruções da VM)A resposta de Veedrac - https://stackoverflow.com/a/28889838/71522 - vai para muito mais detalhes sobre especificamente o que acontece durante cada um
==
ein
e vale bem a pena a leitura.fonte
X in [X,Y,Z]
que funcione corretamente semX
,Y
ouZ
sem a necessidade de definir métodos de igualdade (ou melhor, a igualdade padrão éis
, portanto, poupa a necessidade de chamar__eq__
objetos sem definição definida pelo usuário__eq__
eis
ser verdadeira deve implicar valor -igualdade).float('nan')
é potencialmente enganador. É uma propriedade denan
que não é igual a si mesmo. Isso pode mudar o tempo.in
dos testes de associação. Vou mudar o nome da variável para esclarecer.tuple.__contains__
é implementado otuplecontains
qual chamaPyObject_RichCompareBool
e que retorna imediatamente em caso de identidade.unicode
temPyUnicode_RichCompare
sob o capô, que tem o mesmo atalho para identidade."x" is "x"
não será necessariamenteTrue
.'x' in ('x', )
sempre seráTrue
, mas pode não parecer mais rápido que==
.