Pourquoi l'expression 0 <0 == 0 renvoie False en Python?

136

En regardant dans Queue.py dans Python 2.6, j'ai trouvé cette construction que j'ai trouvée un peu étrange:

def full(self):
    """Return True if the queue is full, False otherwise
    (not reliable!)."""
    self.mutex.acquire()
    n = 0 < self.maxsize == self._qsize()
    self.mutex.release()
    return n

Si la valeur maxsizeest 0, la file d'attente n'est jamais pleine.

Ma question est de savoir comment cela fonctionne pour ce cas? Comment 0 < 0 == 0est-il considéré comme faux?

>>> 0 < 0 == 0
False
>>> (0) < (0 == 0)
True
>>> (0 < 0) == 0
True
>>> 0 < (0 == 0)
True
Marcelo Santos
la source
Est-ce que 0 <True est égal à False en python?
Marino Šimić
3
@Marino Šimić: D'après le deuxième exemple montré dans la question du PO >>> (0) < (0 == 0), ce n'est clairement pas le cas.
martineau
3
Une raison pour laquelle vous ne devriez pas écrire de code comme n = 0 < self.maxsize == self._qsize()au départ, dans aucune langue. Si vos yeux doivent faire des allers-retours sur la ligne plusieurs fois pour comprendre ce qui se passe, ce n'est pas une ligne bien écrite. Il suffit de le diviser en plusieurs lignes.
BlueRaja - Danny Pflughoeft
2
@Blue: Je suis d'accord pour ne pas écrire une telle comparaison de cette façon, mais la diviser en lignes séparées va un peu exagérer pour deux comparaisons. J'espère que vous voulez dire, divisez-le en comparaisons séparées. ;)
Jeff Mercado
2
@Blue: Je ne l'ai pas écrit, il est en Python 2.6. J'essayais juste de comprendre ce qui se passait.
Marcelo Santos

Réponses:

113

Je pense que Python a une gestion spéciale des cas pour les séquences d'opérateurs relationnels afin de rendre les comparaisons de plages faciles à exprimer. C'est beaucoup plus agréable de pouvoir dire 0 < x <= 5que de dire (0 < x) and (x <= 5).

Celles-ci sont appelées des comparaisons en chaîne . Et c'est un lien vers la documentation pour eux.

Avec les autres cas dont vous parlez, les parenthèses forcent un opérateur relationnel à être appliqué avant l'autre, et donc ce ne sont plus des comparaisons enchaînées. Et depuis Trueet Falseque les valeurs sont des entiers, vous obtenez les réponses que vous faites à partir des versions entre parenthèses.

Très varié
la source
il est intéressant d'essayer certaines de ces comparaisons et de spécifier int () et bool (). J'ai réalisé que le bool () de tout non-zéro est 1. Je suppose que je n'aurais même jamais essayé de spécifier directement autre chose que bool (0) ou bool (1) avant cette expérience de pensée
j_syk
Aviez-vous plutôt l'intention de créer un lien vers cette section? docs.python.org/2/reference/expressions.html#comparisons
tavnab
@tavnab - Oui. J'essaierai de me souvenir de le réparer. Je vais également vérifier l'historique des modifications. Cela ne semble pas être une erreur que je ferais. 😕
Omnifarious
42

Car

(0 < 0) and (0 == 0)

est False. Vous pouvez enchaîner les opérateurs de comparaison et ils sont automatiquement développés dans les comparaisons par paires.


EDIT - clarification sur True et False en Python

En Python Trueet ne Falsesont que des instances de bool, qui est une sous-classe de int. En d'autres termes, c'est Truevraiment juste 1.

Le but est que vous pouvez utiliser le résultat d'une comparaison booléenne exactement comme un entier. Cela conduit à des choses déroutantes comme

>>> (1==1)+(1==1)
2
>>> (2<1)<1
True

Mais cela ne se produira que si vous mettez entre parenthèses les comparaisons afin qu'elles soient évaluées en premier. Sinon, Python étendra les opérateurs de comparaison.

Katriel
la source
2
J'ai vu une utilisation intéressante des valeurs booléennes utilisées comme entiers hier. L'expression 'success' if result_code == 0 else 'failure'peut être réécrite comme ('error', 'success')[result_code == 0], avant cela, je n'avais jamais vu de booléen utilisé pour sélectionner un élément dans une liste / un tuple.
Andrew Clark
'bool' a été ajouté quelque temps autour de Python 2.2.
MRAB
18

Le comportement étrange que vous ressentez provient de la capacité des pythons à enchaîner les conditions. Puisqu'il trouve que 0 n'est pas inférieur à 0, il décide que l'expression entière est évaluée à false. Dès que vous divisez cela en conditions distinctes, vous modifiez la fonctionnalité. Il s'agit essentiellement de tester cela a < b && b == cpour votre déclaration originale de a < b == c.

Un autre exemple:

>>> 1 < 5 < 3
False

>>> (1 < 5) < 3
True
Tyler
la source
1
OMG, a < b && b == cest le même que a < b == cOO
Kiril Kirov
9
>>> 0 < 0 == 0
False

Ceci est une comparaison en chaîne. Il renvoie vrai si chaque comparaison par paires est à son tour vraie. C'est l'équivalent de(0 < 0) and (0 == 0)

>>> (0) < (0 == 0)
True

Cela équivaut à 0 < Truequi prend la valeur True.

>>> (0 < 0) == 0
True

Cela équivaut à False == 0qui prend la valeur True.

>>> 0 < (0 == 0)
True

Équivalent à 0 < Truequi, comme ci-dessus, donne la valeur True.

David Heffernan
la source
7

En regardant le démontage (les octets codes) , il est évident pourquoi 0 < 0 == 0estFalse .

Voici une analyse de cette expression:

>>>import dis

>>>def f():
...    0 < 0 == 0

>>>dis.dis(f)
  2      0 LOAD_CONST               1 (0)
         3 LOAD_CONST               1 (0)
         6 DUP_TOP
         7 ROT_THREE
         8 COMPARE_OP               0 (<)
        11 JUMP_IF_FALSE_OR_POP    23
        14 LOAD_CONST               1 (0)
        17 COMPARE_OP               2 (==)
        20 JUMP_FORWARD             2 (to 25)
   >>   23 ROT_TWO
        24 POP_TOP
   >>   25 POP_TOP
        26 LOAD_CONST               0 (None)
        29 RETURN_VALUE

Remarquez les lignes 0-8: Ces lignes vérifient si 0 < 0ce qui renvoie évidemmentFalse sur la pile python.

Remarquez maintenant la ligne 11: JUMP_IF_FALSE_OR_POP 23 cela signifie que si 0 < 0retourneFalse effectuez un saut à la ligne 23.

Maintenant, 0 < 0est False, donc le saut est pris, ce qui laisse la pile avec un Falsequi est la valeur de retour pour l'expression entière 0 < 0 == 0, même si la == 0partie n'est même pas vérifiée.

Donc, pour conclure, la réponse est comme dit dans d'autres réponses à cette question. 0 < 0 == 0a une signification particulière. Le compilateur évalue cela en deux termes: 0 < 0et 0 == 0. Comme pour toutes les expressions booléennes complexes andentre elles, si la première échoue, la seconde n'est même pas vérifiée.

J'espère que cela éclairera un peu les choses, et j'espère vraiment que la méthode que j'ai utilisée pour analyser ce comportement inattendu encouragera les autres à essayer la même chose à l'avenir.

SatA
la source
Ne serait-il pas plus facile de le résoudre à partir des spécifications plutôt que de procéder à une ingénierie inverse d'une implémentation particulière?
David Heffernan le
Non, c'est la réponse courte. Je crois que cela dépend de votre personnalité. Si vous vous rapportez à une vue «boîte noire» et préférez obtenir vos réponses à partir des spécifications et de la documentation, cette réponse ne fera que vous embrouiller. Si vous aimez creuser et révéler les éléments internes, cette réponse est pour vous. Votre remarque selon laquelle cette ingénierie inverse n'est pertinente que pour une implémentation particulière est correcte et doit être soulignée, mais ce n'était pas le but de cette réponse. C'est une démonstration de la facilité avec laquelle il est en python de jeter un coup d'œil «sous le capot» pour ceux qui sont assez curieux.
Sam
1
Le problème de la rétro-ingénierie est son manque de puissance prédictive. Ce n'est pas la manière d'apprendre une nouvelle langue.
David Heffernan
Une autre chose que je dois ajouter, c'est que les spécifications et les documentations ne sont pas toujours complètes et, dans la plupart des cas, ne fourniront pas de réponses pour de tels cas spécifiques. Ensuite, je crois, il ne faut pas avoir peur d'explorer, d'enquêter et d'aller plus loin que nécessaire pour obtenir les réponses.
Sam
2

Comme d'autres l'ont mentionné x comparison_operator y comparison_operator z le sucre syntaxique a pour (x comparison_operator y) and (y comparison_operator z)avantage que y n'est évalué qu'une seule fois.

Donc, votre expression 0 < 0 == 0est vraiment (0 < 0) and (0 == 0), qui évalue False and Truece qui est juste False.

dr jimbob
la source
2

peut-être que cet extrait de la documentation peut vous aider:

Ce sont les méthodes dites de «comparaison riche», appelées de préférence pour les opérateurs de comparaison __cmp__()ci-dessous. La correspondance entre les symboles de l' opérateur et les noms de méthode est la suivante: les x<yappels x.__lt__(y), les x<=yappels x.__le__(y), les x==yappels x.__eq__(y), x!=yet l' x<>y appel x.__ne__(y), les x>yappels x.__gt__(y)et les x>=yappels x.__ge__(y) .

Une méthode de comparaison riche peut renvoyer le singleton NotImplementedsi elle n'implémente pas l'opération pour une paire d'arguments donnée. Par convention, Falseet Truesont renvoyés pour une comparaison réussie. Cependant, ces méthodes peuvent renvoyer n'importe quelle valeur, donc si l'opérateur de comparaison est utilisé dans un contexte booléen (par exemple, dans la condition d'une instruction if), Python appellerabool() la valeur pour déterminer si le résultat est vrai ou faux.

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!=y soit faux. En conséquence, lors de la définition __eq__(), il convient également de définir de __ne__()sorte que les opérateurs se comportent comme prévu. Voir le paragraphe sur__hash__() pour quelques remarques importantes sur la création d'objets hachables qui prennent en charge les opérations de comparaison personnalisées et sont utilisables comme clés de dictionnaire.

Il n'y a pas de versions à argument permuté de ces méthodes (à utiliser lorsque l'argument de gauche ne prend pas en charge l'opération mais que l'argument de droite le fait); plutôt, __lt__()et __gt__() sont le reflet de chacun, __le__() et __ge__()sont le reflet de l'autre, __eq__()et __ne__() sont leur propre reflet.

Les arguments aux méthodes de comparaison riches ne sont jamais forcés.

Il s'agissait de comparaisons, mais puisque vous enchaînez des comparaisons, vous devez savoir que:

Les comparaisons peuvent être chaînées arbitrairement, par exemple, x < y <= zéquivaut à x < y and y <= z, sauf que y est évalué une seule fois (mais dans les deux cas, z n'est pas évalué du tout lorsque x <y s'avère faux).

Formellement, si a, b, c, ..., y, z sont des expressions et op1, op2, ..., opN sont des opérateurs de comparaison, alors a op1 b op2 c ... y opN z est équivalent à a op1 b et b op2 c et ... y opN z, sauf que chaque expression est évaluée au plus une fois.

Marino Šimić
la source
1

Le voici, dans toute sa splendeur.

>>> class showme(object):
...   def __init__(self, name, value):
...     self.name, self.value = name, value
...   def __repr__(self):
...     return "<showme %s:%s>" % (self.name, self.value)
...   def __cmp__(self, other):
...     print "cmp(%r, %r)" % (self, other)
...     if type(other) == showme:
...       return cmp(self.value, other.value)
...     else:
...       return cmp(self.value, other)
... 
>>> showme(1,0) < showme(2,0) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
False
>>> (showme(1,0) < showme(2,0)) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
cmp(<showme 3:0>, False)
True
>>> showme(1,0) < (showme(2,0) == showme(3,0))
cmp(<showme 2:0>, <showme 3:0>)
cmp(<showme 1:0>, True)
True
>>> 
SingleNegationElimination
la source
0

Je pense que Python fait c'est bizarre entre la magie. Identique au 1 < 2 < 3moyen 2 est compris entre 1 et 3.

Dans ce cas, je pense que [middle 0] est supérieur à [left 0] et égal à [right 0]. Le milieu 0 n'est pas supérieur à 0 gauche, il est donc évalué à faux.

mpen
la source