Python si no == vs if! =

183

¿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:
lafferc
fuente
101
Lo mejor es lo que puedes leer, dudo que el cuello de botella de tu programa esté aquí
Thomas Ayoub
1
Esta pregunta me interesa en el caso "x no en la lista" y "no x en la lista"
SomethingSomething
55
@SomethingAlgo que se interpretan de manera idéntica.
jonrsharpe
44
@ Algo Algo de referencia para mi comentario anterior: stackoverflow.com/q/8738388/3001761
jonrsharpe
1
@ Algo Algo es lo mismo para aquellos también; es cómo se interpreta la sintaxis, no importa cuáles sean los dos operandos.
jonrsharpe

Respuestas:

229

Utilizando dispara 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 != barfrente a if not foo == barla cantidad de operaciones es exactamente la misma, es solo que los COMPARE_OPcambios y POP_JUMP_IF_TRUEcambios 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==yno implica que x!=ysea ​​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 != yes mucho más legible quenot x == y .

jonrsharpe
fuente
29
En la práctica, cualquier clase que sea __eq__inconsistente con __ne__está completamente rota.
Kevin
8
Tenga en cuenta que no siempre es cierto que not x == ytenga 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_TRUEy 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 ifs, obtuve lo que tienes.
Justin
1
Otro ejemplo donde ==y !=no son mutuamente excluyentes es una implementación tipo SQL que involucra nullvalores. En SQL nullno se vuelve truea !=comparar con ningún otro valor, por lo que las implementaciones de Python de interfaces SQL también pueden tener el mismo problema.
Joe
Estoy empezando a desear no haberme referido a la posible diferencia entre 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
29

@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:

ingrese la descripción de la imagen aquí

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.

Cambio rojo
fuente
31
¡Espero que todos sepan no actuar sobre esta información! Hacer cambios ilegibles para una mejora del 0.3%, o incluso una mejora del 10%, rara vez es una buena idea, y es muy probable que este tipo de mejora sea evanescente (y no de una buena manera : cambios muy leves en el tiempo de ejecución de Python podría eliminar o incluso revertir cualquier ganancia.
Malvolio
1
@Malvolio Además, hay diferentes implementaciones de Python.
Cees Timmerman
6

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

JediPythonClone
fuente
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

Aquí puedes ver que not x == ytiene 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.

kylie.a
fuente
5

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.

Jacob Zimmerman
fuente
3

Se trata de tu forma de leerlo. notel 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.

Himanshu Mishra
fuente
3
¿Qué quiere decir "el notoperador es dinámico" ?
jonrsharpe
1
@jonrsharpe Creo que quiere decir que "no x" llamará a x .__ bool __ () [python 3 - python 2 usa distinto de cero ] y revierte el resultado (ver docs.python.org/3/reference/datamodel.html#object. __bool__ )
jdferreira
1

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!) ...

Alan Jay Weiner
fuente
1
el cerebro interpreta "positivo" más rápido que "negativo" ¿Es esto por experiencia o ha leído estudios sobre esto? Solo pregunto porque, dependiendo del código en (hacer esto) o (hacer eso), encuentro que a! = B es más fácil de entender.
lafferc 01 de