web-dev-qa-db-fra.com

Utilisation étrange des opérateurs "et" / "ou"

J'essaie d'apprendre python et suis tombé sur du code agréable et court mais qui n'a pas vraiment de sens

le contexte était:

def fn(*args):
    return len(args) and max(args)-min(args)

Je comprends ce qu’il fait, mais pourquoi python fait ceci - c’est-à-dire renvoie la valeur plutôt que Vrai/Faux?

10 and 7-2

renvoie 5. De même, si vous modifiez les fonctions and et ou, les fonctionnalités changent. Alors

10 or 7 - 2

Je reviendrais 10.

Ce style est-il légitime/fiable ou y at-il des pièges à ce sujet?

80
Marcin

TL; DR

Nous commençons par résumer les deux comportements des deux opérateurs logiques and et or. Ces idiomes constitueront la base de notre discussion ci-dessous.

and

Renvoie la première valeur Falsy, le cas échéant, sinon renvoie la dernière valeur de l'expression.

or

Renvoie la première valeur Truthy, le cas échéant, sinon renvoie la dernière valeur de l'expression.

Le comportement est également résumé dans the docs , en particulier dans ce tableau:

enter image description here

L'opérateur not est le seul opérateur à renvoyer une valeur booléenne indépendamment de ses opérandes.


Évaluations de "vérité" et de "vérité"

La déclaration

len(args) and max(args) - min(args)

Est-ce qu'un très Pythonique manière concise (et sans doute moins lisible) de dire "si args n'est pas vide, renvoyer le résultat de max(args) - min(args)", sinon renvoyer 0. En général, il s'agit d'une représentation plus concise d'une expression if-else. Par exemple,

exp1 and exp2

Devrait (approximativement) traduire en:

r1 = exp1
if not r1:
    r1 = exp2

Ou équivalent,

r1 = exp1 if exp1 else exp2

exp1 Et exp2 Sont des objets arbitraires python, ou des expressions renvoyant un objet. La clé pour comprendre les utilisations des opérateurs logiques and et or est la suivante: Comprenez qu'ils ne sont pas limités à l'utilisation ou à la restitution de valeurs booléennes. Tout objet avec une valeur de véracité peut être testé ici. Cela inclut les objets int, str, list, dict, Tuple, set et NoneType, ainsi que les règles définies par l'utilisateur. toujours appliquer aussi bien.

Mais qu'est-ce que la véracité?
Il fait référence à la façon dont les objets sont évalués lorsqu'ils sont utilisés dans des expressions conditionnelles. @ Patrick Haugh résume bien la vérité dans ce post .

Toutes les valeurs sont considérées comme "vérités", à l'exception des suivantes, qui sont "fausseté":

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] - un list vide
  • {} - un dict vide
  • () - un Tuple vide
  • '' - un str vide
  • b'' - un bytes vide
  • set() - un set vide
  • un range vide, comme range(0)
  • objets pour lesquels
    • obj.__bool__() renvoie False
    • obj.__len__() renvoie 0

Une valeur "vérité" satisfera à la vérification effectuée par les instructions if ou while. Nous utilisons les termes "vérité" et "fausseté" pour différencier les valeurs bool des valeurs True et False.


Comment fonctionne and

Nous nous basons sur la question de OP pour passer ensuite à une discussion sur la manière dont ces opérateurs se trouvent dans ces cas.

Étant donné une fonction avec la définition

def foo(*args):
    ...

Comment puis-je renvoyer la différence entre la valeur minimale et maximale dans une liste de zéro ou plusieurs arguments?

Il est facile de trouver le minimum et le maximum (utilisez les fonctions intégrées!). Le seul problème ici concerne le cas où la liste des arguments pourrait être vide (par exemple, en appelant foo()). Nous pouvons faire les deux en une seule ligne grâce à l'opérateur and:

def foo(*args):
     return len(args) and max(args) - min(args)
foo(1, 2, 3, 4, 5)
# 4

foo()
# 0

Puisque and est utilisé, la deuxième expression doit également être évaluée si la première est True. Notez que, si la première expression est évaluée comme étant la vérité, la valeur de retour est toujours le résultat de seconde expression. Si la première expression est évaluée comme étant Falsy, le résultat renvoyé est le résultat de la première expression.

Dans la fonction ci-dessus, si foo reçoit un ou plusieurs arguments, len(args) est supérieur à 0 (Nombre positif). Le résultat renvoyé est donc max(args) - min(args). OTOH, si aucun argument n'est passé, len(args) est 0 Qui est Falsy et 0 Est renvoyé.

Notez qu'une autre manière d'écrire cette fonction serait:

def foo(*args):
    if not len(args):
        return 0

    return max(args) - min(args)

Ou, plus concement,

def foo(*args):
    return 0 if not args else max(args) - min(args)

Bien sûr, aucune de ces fonctions n’effectue de vérification de type. Par conséquent, à moins que vous n’ayez entièrement confiance en l’entrée fournie, ne le faites pas comptez sur la simplicité de ces constructions.


Comment fonctionne or

J'explique le fonctionnement de or de manière similaire avec un exemple artificiel.

Étant donné une fonction avec la définition

def foo(*args):
    ...

Comment compléteriez-vous foo pour renvoyer tous les nombres supérieurs à 9000?

Nous utilisons or pour gérer le cas de coin ici. Nous définissons foo comme:

def foo(*args):
     return [x for x in args if x > 9000] or 'No number over 9000!'

foo(9004, 1, 2, 500)
# [9004]

foo(1, 2, 3, 4)
# 'No number over 9000!'

foo effectue un filtrage sur la liste pour conserver tous les nombres supérieurs à 9000. S'il existe de tels nombres, le résultat de la compréhension de la liste est une liste non vide qui est Vérité, elle est donc renvoyée (court-circuit en action ici). S'il n'existe pas de tels nombres, le résultat de la liste compilée est [], Ce qui est Falsy. Ainsi, la deuxième expression est maintenant évaluée (une chaîne non vide) et est renvoyée.

En utilisant des conditions, nous pourrions ré-écrire cette fonction en tant que,

def foo(*args):
    r = [x for x in args if x > 9000]
    if not r:
        return 'No number over 9000!' 

    return r

Comme précédemment, cette structure est plus flexible en termes de gestion des erreurs.

112
cs95

Citant de Python Docs

Notez que ni and ni or ne restreignent la valeur et tapez ils retournent à False et True, mais renvoient plutôt le dernier argument évalué . Ceci est parfois utile, par exemple, si s est une chaîne qui doit être remplacée par une valeur par défaut si elle est vide, l'expression s or 'foo' donne la valeur souhaitée.

C’est ainsi que Python a été conçu pour évaluer les expressions booléennes et la documentation ci-dessus nous donne une idée de la raison pour laquelle ils l’ont fait.

Pour obtenir une valeur booléenne, il suffit de la transtyper.

return bool(len(args) and max(args)-min(args))

Pourquoi?

Court-circuit.

Par exemple:

2 and 3 # Returns 3 because 2 is Truthy so it has to check 3 too
0 and 3 # Returns 0 because 0 is Falsey and there's no need to check 3 at all

Il en va de même pour or également, c'est-à-dire qu'il renverra l'expression qui est Truthy dès qu'elle le trouvera, car l'évaluation du reste de l'expression est redondante.

Au lieu de renvoyer hardcore True ou False, Python renvoie vérité ou Falsey , qui vont malgré tout évaluer True ou False. Vous pouvez utiliser l'expression telle quelle et cela fonctionnera toujours.


Pour savoir ce qui est Vérité et Falsey, vérifiez réponse de Patrick Haugh

14
Amit Joki

et et o exécutent une logique booléenne, mais renvoient l'une des valeurs réelles lors de la comparaison. Lors de l'utilisation de et, les valeurs sont évaluées dans un contexte booléen de gauche à droite. , '', [], (), {}, et Aucun sont faux dans un contexte booléen; tout le reste est vrai.

Si toutes les valeurs sont vraies dans un contexte booléen, et renvoie la dernière valeur.

>>> 2 and 5
5
>>> 2 and 5 and 10
10

Si une valeur est fausse dans un contexte booléen, and renvoie la première fausse valeur.

>>> '' and 5
''
>>> 2 and 0 and 5
0

Donc le code

return len(args) and max(args)-min(args)

retourne la valeur de max(args)-min(args) quand il y a args sinon il retourne len(args) qui vaut 0.

7
Nithin Varghese

Ce style est-il légitime/fiable ou y at-il des pièges à ce sujet?

C'est légitime, c'est un évaluation de court-circuit où la dernière valeur est retournée.

Vous donnez un bon exemple. La fonction retournera 0 si aucun argument n'est transmis, et que le code ne doit pas rechercher un cas particulier d'absence d'argument.

Une autre façon de l'utiliser consiste à utiliser les arguments Aucun par défaut d'une primitive modifiable, comme une liste vide:

def fn(alist=None):
    alist = alist or []
    ....

Si une valeur non véridique est transmise à alist, sa liste par défaut est une liste vide, un moyen pratique d'éviter une instruction if et le piège d'argument par défaut mutable

5
salparadise

Gotchas

Oui, il y a quelques pièges.

fn() == fn(3) == fn(4, 4)

Premièrement, si fn renvoie 0, Vous ne pouvez pas savoir s'il a été appelé sans paramètre, avec un paramètre ou avec plusieurs paramètres égaux:

>>> fn()
0
>>> fn(3)
0
>>> fn(3, 3, 3)
0

Que signifie fn?

Ensuite, Python est un langage dynamique. N’est spécifié nulle part ce que fn fait, ce que son entrée devrait être et à quoi devrait ressembler sa sortie. Par conséquent, il est très important de nommer De même, il n’est pas nécessaire d’appeler les arguments args. delta(*numbers) ou calculate_range(*numbers) pourrait mieux décrire ce que la fonction est censée faire.

Erreurs d'argument

Enfin, l'opérateur logique and est censé empêcher la fonction d'échouer s'il est appelé sans aucun argument. Il échoue toujours si un argument n'est pas un nombre, cependant:

>>> fn('1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: unsupported operand type(s) for -: 'str' and 'str'
>>> fn(1, '2')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: '>' not supported between instances of 'str' and 'int'
>>> fn('a', 'b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: unsupported operand type(s) for -: 'str' and 'str'

Alternative possible

Voici un moyen d'écrire la fonction selon le principe "Plus facile de demander pardon que permission." :

def delta(*numbers):
    try:
        return max(numbers) - min(numbers)
    except TypeError:
        raise ValueError("delta should only be called with numerical arguments") from None
    except ValueError:
        raise ValueError("delta should be called with at least one numerical argument") from None

Par exemple:

>>> delta()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in delta
ValueError: delta should be called with at least one numerical argument
>>> delta(3)
0
>>> delta('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta('a', 'b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta('a', 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta(3, 4.5)
1.5
>>> delta(3, 5, 7, 2)
5

Si vous ne voulez vraiment pas déclencher une exception lorsque delta est appelé sans aucun argument, vous pouvez retourner une valeur qui ne pourrait pas être possible autrement (par exemple, -1 Ou None.) :

>>> def delta(*numbers):
...     try:
...         return max(numbers) - min(numbers)
...     except TypeError:
...         raise ValueError("delta should only be called with numerical arguments") from None
...     except ValueError:
...         return -1 # or None
... 
>>> 
>>> delta()
-1
3
Eric Duminil

Oui. C'est le comportement correct de et de comparaison.

Au moins en Python, A and B renvoie B si A est essentiellement True, même si A n’est PAS Null, NOT None PAS un conteneur vide (tel que un vide list, dict, etc.). A est renvoyé. IFF A est essentiellement False ou None ou vide ou vide.

D'autre part, A or B renvoie A si A est essentiellement True, même si A n’est PAS Null, NOT None PAS un conteneur vide (tel que un list vide, dict, etc), sinon il retourne B.

Il est facile de ne pas remarquer (ou d’ignorer) ce comportement car, en Python, tout non-null l'objet non vide est évalué à True est traité comme un booléen.

Par exemple, tout ce qui suit va afficher "True"

if [102]: 
    print "True"
else: 
    print "False"

if "anything that is not empty or None": 
    print "True"
else: 
    print "False"

if {1, 2, 3}: 
    print "True"
else: 
    print "False"

Par contre, tout ce qui suit affichera "Faux"

if []: 
    print "True"
else: 
    print "False"

if "": 
    print "True"
else: 
    print "False"

if set ([]): 
    print "True"
else: 
    print "False"
0
emmanuelsa

Ce style est-il légitime/fiable ou y at-il des pièges à ce sujet?

Je voudrais ajouter à cette question qu’il est non seulement légitime et fiable, mais aussi ultra pratique. Voici un exemple simple:

>>>example_list = []
>>>print example_list or 'empty list'
empty list

Par conséquent, vous pouvez vraiment l'utiliser à votre avantage. Pour être conscient, voici comment je vois les choses:

Or opérateur

L'opérateur or de Python renvoie la première valeur Truth-y ou la dernière valeur et arrête

And opérateur

L'opérateur and de Python renvoie la première valeur False-y ou la dernière valeur et arrête

Dans les coulisses

En python, tous les nombres sont interprétés comme True sauf 0. Par conséquent, dites:

0 and 10 

est le même que:

False and True

Ce qui est clairement False. Il est donc logique qu'il renvoie 0

0
scharette

comprendre de manière simple,

AND:if first_val is False return first_val else second_value

par exemple:

1 and 2 # here it will return 2 because 1 is not False

mais,

0 and 2 # will return 0 because first value is 0 i.e False

et => si quelqu'un est faux, ce sera faux. si les deux sont vrais alors seulement cela deviendra vrai

OR:if first_val is False return second_val else first_value

la raison en est que, si le premier est faux, il vérifie si 2 est vrai ou non.

par exemple:

1 or 2 # here it will return 1 because 1 is not False

mais,

0 or 2 # will return 2 because first value is 0 i.e False

ou => si quelqu'un est faux, ce sera vrai. Donc, si la première valeur est fausse, peu importe ce que la valeur 2 suppose être. il renvoie donc la deuxième valeur, quelle qu’elle soit.

si quelqu'un est vrai, cela deviendra vrai. si les deux sont faux, cela deviendra faux.

0