Python sinon == vs si! =

183

Quelle est la différence entre ces deux lignes de code:

if not x == 'val':

et

if x != 'val':

L'un est-il plus efficace que l'autre?

Serait-il préférable d'utiliser

if x == 'val':
    pass
else:
Lafferc
la source
101
Le mieux est celui que vous pouvez lire, je doute que le goulot d'étranglement de votre programme soit ici
Thomas Ayoub
1
Cette question m'intéresse dans le cas "x pas dans la liste" et "pas x dans la liste"
SomethingSomething
5
@SomethingSomething ils sont interprétés de la même manière.
jonrsharpe
4
@SomethingSomething référence pour mon commentaire ci-dessus: stackoverflow.com/q/8738388/3001761
jonrsharpe
1
@SomethingSomething, c'est la même chose pour ceux-là aussi; c'est la façon dont la syntaxe est interprétée, peu importe ce que sont les deux opérandes.
jonrsharpe

Réponses:

229

Utilisation dispour regarder le bytecode généré pour les deux versions:

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   

Ce dernier a moins d'opérations et est donc susceptible d'être légèrement plus efficace.


Il a été souligné dans les commentaires (merci, @Quincunx ) que là où vous avez if foo != barvs if not foo == barle nombre d'opérations est exactement le même, c'est juste que les COMPARE_OPchangements et les POP_JUMP_IF_TRUEcommutations vers 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

Dans ce cas, à moins qu'il n'y ait une différence dans la quantité de travail requise pour chaque comparaison, il est peu probable que vous voyiez une différence de performance.


Cependant, notez que les deux versions ne seront pas toujours logiquement identiques , car cela dépendra des implémentations de __eq__et __ne__pour les objets en question. Selon la documentation du modèle de données :

Il n'y a pas de relations implicites entre les opérateurs de comparaison. La vérité de x==yn'implique pas que ce x!=ysoit faux.

Par exemple:

>>> class Dummy(object):
    def __eq__(self, other):
        return True
    def __ne__(self, other):
        return True


>>> not Dummy() == Dummy()
False
>>> Dummy() != Dummy()
True

Enfin, et peut-être le plus important: en général, là où les deux sont logiquement identiques, x != yest beaucoup plus lisible quenot x == y .

Jonrsharpe
la source
29
En pratique, toute classe qui n'est __eq__pas cohérente avec __ne__est complètement cassée.
Kevin
8
Veuillez noter qu'il n'est pas toujours vrai qu'il y not x == yait une instruction de plus. Lorsque j'ai mis le code dans un if, il s'est avéré qu'ils avaient tous les deux le même nombre d'instructions, une seule avait POP_JUMP_IF_TRUEet l'autre POP_JUMP_IF_FALSE(c'était la seule différence entre eux, à part utiliser un autre COMPARE_OP). Quand j'ai compilé le code sans le ifs, j'ai ce que vous avez.
Justin
1
Un autre exemple où ==et !=ne s'excluent pas mutuellement est une implémentation de type SQL impliquant des nullvaleurs. Dans SQL nullne revient pas trueà !=par rapport à toute autre valeur, de sorte que les implémentations python des interfaces SQL peuvent également avoir le même problème.
Joe
Je commence à regretter de ne pas avoir évoqué la différence possible entre not ==et !=, cela semble être la partie la plus intéressante de ma réponse! Je ne pense pas que ce soit l'endroit sur lequel s'attarder si, pourquoi et quand cela a du sens - voir par exemple Pourquoi Python a-t-il une __ne__méthode d'opérateur au lieu de juste __eq__?
jonrsharpe
29

@jonrsharpe a une excellente explication de ce qui se passe. Je pensais simplement montrer la différence de temps lors de l'exécution de chacune des 3 options 10 000 000 fois (assez pour qu'une légère différence apparaisse).

Code utilisé:

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)

Et les résultats du profileur cProfile:

entrez la description de l'image ici

Nous pouvons donc voir qu'il y a une très infime différence de ~ 0,7% entre if not x == 'val':et if x != 'val':. De ceux-ci, if x != 'val':est le plus rapide.

Cependant, le plus surprenant, nous pouvons voir que

if x == 'val':
        pass
    else:

est en fait le plus rapide et bat if x != 'val':d'environ 0,3%. Ce n'est pas très lisible, mais je suppose que si vous vouliez une amélioration négligeable des performances, vous pourriez emprunter cette voie.

Maj rouge
la source
31
J'espère que tout le monde sait ne pas agir sur cette information! Faire des changements illisibles pour une amélioration de 0,3% - voire une amélioration de 10% - est rarement une bonne idée, et ce type d'amélioration est très susceptible d'être évanescent (et pas dans le bon sens : très légers changements dans le runtime Python pourrait éliminer ou même inverser tout gain.
Malvolio
1
@Malvolio De plus, il existe différentes implémentations de Python.
Cees Timmerman
6

Dans le premier, Python doit exécuter une opération de plus que nécessaire (au lieu de vérifier simplement qu'il n'est pas égal, il doit vérifier si ce n'est pas vrai qu'il est égal, donc une opération de plus). Il serait impossible de faire la différence entre une exécution, mais si elle était exécutée plusieurs fois, la seconde serait plus efficace. Dans l'ensemble, j'utiliserais le second, mais mathématiquement, ce sont les mêmes

JediPythonClone
la source
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

Ici, vous pouvez voir qu'il y not x == ya une instruction de plus que x != y. Ainsi, la différence de performances sera très faible dans la plupart des cas, à moins que vous ne fassiez des millions de comparaisons et même dans ce cas, cela ne sera probablement pas la cause d'un goulot d'étranglement.

kylie.a
la source
5

Une note supplémentaire, étant donné que les autres réponses ont généralement répondu correctement à votre question, est que si une classe définit uniquement __eq__()et non __ne__(), alors vous COMPARE_OP (!=)l'exécuterez __eq__()et la nierez. À ce moment-là, votre troisième option sera probablement un peu plus efficace, mais ne devrait être envisagée que si vous avez BESOIN de la vitesse, car elle est difficile à comprendre rapidement.

Jacob Zimmerman
la source
3

Il s'agit de votre façon de le lire. notl'opérateur est dynamique, c'est pourquoi vous pouvez l'appliquer dans

if not x == 'val':

Mais !=pourrait être lu dans un meilleur contexte comme un opérateur qui fait le contraire de ce que ==fait.

Himanshu Mishra
la source
3
Que voulez-vous dire "l' notopérateur est dynamique" ?
jonrsharpe
1
@jonrsharpe Je pense qu'il veut dire que "not x" appellera x .__ bool __ () [python 3 - python 2 utilise une valeur différente de zéro ] et inversera le résultat (voir docs.python.org/3/reference/datamodel.html#object. __bool__ )
jdferreira
1

Je souhaite développer mon commentaire sur la lisibilité ci-dessus.

Encore une fois, je suis entièrement d'accord avec le fait que la lisibilité l'emporte sur d'autres préoccupations (non significatives en termes de performances).

Ce que je voudrais souligner, c'est que le cerveau interprète «positif» plus vite que «négatif». Par exemple, "arrêter" contre "ne pas y aller" (un exemple plutôt moche en raison de la différence de nombre de mots).

Alors donné le choix:

if a == b
    (do this)
else
    (do that)

est préférable à l'équivalent fonctionnel:

if a != b
    (do that)
else
    (do this)

Moins de lisibilité / compréhensibilité entraîne plus de bogues. Peut-être pas dans le codage initial, mais la maintenance (pas aussi intelligente que vous!) Change ...

Alan Jay Weiner
la source
1
le cerveau interprète «positif» plus vite que «négatif» est-ce par expérience ou avez-vous lu des études à ce sujet? Je demande simplement parce que selon le code dans (faites ceci) ou (faites cela), je trouve a! = B plus facile à comprendre.
lafferc