¿Cuál es la diferencia entre estas dos líneas de código?
if not x == 'val':
y
if x != 'val':
¿Es uno más eficiente que el otro?
¿Sería mejor usar
if x == 'val':
pass
else:
¿Cuál es la diferencia entre estas dos líneas de código?
if not x == 'val':
y
if x != 'val':
¿Es uno más eficiente que el otro?
¿Sería mejor usar
if x == 'val':
pass
else:
Respuestas:
Utilizando dis
para mirar el bytecode generado para las dos versiones:
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 tiene menos operaciones y, por lo tanto, es probable que sea un poco más eficiente.
Se señaló en los comentarios (gracias, @Quincunx ) que donde tienes if foo != bar
frente a if not foo == bar
la cantidad de operaciones es exactamente la misma, es solo que los COMPARE_OP
cambios y POP_JUMP_IF_TRUE
cambios a 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
En este caso, a menos que haya una diferencia en la cantidad de trabajo requerido para cada comparación, es poco probable que vea alguna diferencia de rendimiento.
Sin embargo, tenga en cuenta que las dos versiones no siempre serán lógicamente idénticas , ya que dependerá de las implementaciones de __eq__
y __ne__
para los objetos en cuestión. Según la documentación del modelo de datos :
No hay relaciones implícitas entre los operadores de comparación. La verdad de
x==y
no implica quex!=y
sea falso.
Por ejemplo:
>>> class Dummy(object):
def __eq__(self, other):
return True
def __ne__(self, other):
return True
>>> not Dummy() == Dummy()
False
>>> Dummy() != Dummy()
True
Finalmente, y quizás lo más importante: en general, donde los dos son lógicamente idénticos, x != y
es mucho más legible quenot x == y
.
__eq__
inconsistente con __ne__
está completamente rota.
not x == y
tenga una instrucción más. Cuando puse el código en un if
, resultó que ambos tienen la misma cantidad de instrucciones, solo una tenía POP_JUMP_IF_TRUE
y la otra POP_JUMP_IF_FALSE
(esa era la única diferencia entre ellas, aparte de usar una diferente COMPARE_OP
). Cuando compilé el código sin el if
s, obtuve lo que tienes.
==
y !=
no son mutuamente excluyentes es una implementación tipo SQL que involucra null
valores. En SQL null
no se vuelve true
a !=
comparar con ningún otro valor, por lo que las implementaciones de Python de interfaces SQL también pueden tener el mismo problema.
not ==
y !=
, ¡parece ser la parte más interesante de mi respuesta! No creo que este sea el lugar en el que pensar si, por qué y cuándo tiene sentido, ver, por ejemplo, ¿__ne__
__eq__
@jonrsharpe tiene una excelente explicación de lo que está sucediendo. Pensé que solo mostraría la diferencia en el tiempo al ejecutar cada una de las 3 opciones 10,000,000 de veces (suficiente para mostrar una ligera diferencia).
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)
Y los resultados del perfilador cProfile:
Entonces podemos ver que hay una diferencia muy pequeña de ~ 0.7% entre if not x == 'val':
y if x != 'val':
. De estos, if x != 'val':
es el más rápido.
Sin embargo, lo más sorprendente es que podemos ver que
if x == 'val':
pass
else:
es, de hecho, el más rápido y late if x != 'val':
en ~ 0.3%. Esto no es muy legible, pero supongo que si quisieras una mejora insignificante del rendimiento, uno podría seguir esta ruta.
En el primero, Python tiene que ejecutar una operación más de la necesaria (en lugar de simplemente verificar que no sea igual, tiene que verificar si no es cierto que sea igual, por lo tanto, una operación más). Sería imposible distinguir la diferencia de una ejecución, pero si se ejecuta muchas veces, la segunda sería más eficiente. En general, usaría el segundo, pero matemáticamente son lo mismo
>>> 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
Aquí puedes ver que not x == y
tiene una instrucción más que x != y
. Por lo tanto, la diferencia de rendimiento será muy pequeña en la mayoría de los casos, a menos que esté haciendo millones de comparaciones e incluso entonces esto probablemente no sea la causa de un cuello de botella.
Una nota adicional, dado que las otras respuestas respondieron su pregunta en su mayoría correctamente, es que si una clase solo define __eq__()
y no __ne__()
, entonces la COMPARE_OP (!=)
ejecutará __eq__()
y la negará. En ese momento, es probable que su tercera opción sea un poco más eficiente, pero solo debe considerarse si NECESITA la velocidad, ya que es difícil de entender rápidamente.
Se trata de tu forma de leerlo. not
el operador es dinámico, por eso puede aplicarlo en
if not x == 'val':
Pero !=
podría leerse en un mejor contexto como un operador que hace lo contrario de lo que ==
hace.
not
operador es dinámico" ?
Quiero ampliar mi comentario de legibilidad anterior.
Nuevamente, estoy completamente de acuerdo con la legibilidad que anula otras preocupaciones (insignificantes en cuanto al rendimiento).
Lo que me gustaría señalar es que el cerebro interpreta "positivo" más rápido que "negativo". Por ejemplo, "parar" frente a "no ir" (un ejemplo bastante pésimo debido a la diferencia en el número de palabras).
Entonces, dado una opción:
if a == b
(do this)
else
(do that)
es preferible al funcionalmente equivalente:
if a != b
(do that)
else
(do this)
Menos legibilidad / comprensibilidad conduce a más errores. Quizás no en la codificación inicial, pero los cambios de mantenimiento (¡no tan inteligentes como usted!) ...