113 votes

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

Je suis en train d'apprendre le langage python et suis tombé sur un code qui est belle et courte, mais n'est pas totalement sens

le contexte était:

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

J'ai ce qu'il fait, mais pourquoi python ce faire - c'est à dire le retour de la valeur plutôt que de Vrai/Faux?

10 and 7-2

renvoie 5. De même, la modification de la et à, ou entraîne un changement dans la fonctionnalité. Donc

10 or 7 - 2

Serait de retour 10.

Est-ce légitime/fiable de style, ou il y a des astuces à ce sujet?

157voto

coldspeed Points 111053

TL;DR

Nous commençons par résumer les deux comportement des deux opérateurs logiques and et or. Ces langues seront à la base de notre discussion ci-dessous.

and

De retour de la première Falsy valeur si il y en a, d'autre le retour de la dernière la valeur de l'expression.

or

De retour de la première Truthy valeur si il y en a, d'autre le retour de la dernière la valeur de l'expression.

Le comportement est également résumée dans les docs, surtout dans ce tableau:

enter image description here

Le seul opérateur en renvoyant une valeur booléenne, indépendamment de ses opérandes est l' not de l'opérateur.


"Truthiness", et "Truthy" Évaluations

La déclaration

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

Est un très pythonic concis (et sans doute moins lisible) façon de dire "si args n'est pas vide, retourner le résultat d' max(args) - min(args)", sinon retour 0. En général, c'est une représentation concise de l' if-else expression. Par exemple,

exp1 and exp2

Devrait (à peu près) à traduire:

r1 = exp1
if not r1:
    r1 = exp2

Ou, de manière équivalente,

r1 = exp1 if exp1 else exp2

exp1 et exp2 sont arbitraires des objets python, ou des expressions qui renvoient un objet. La clé pour comprendre les usages de la logique and et or opérateurs ici est de comprendre qu'ils ne sont pas limités à l'exploitation, ou de retourner des valeurs booléennes. N'importe quel objet avec un truthiness valeur peut être testé ici. Cela inclut int, str, list, dict, tuple, set, NoneType, et des objets définis par l'utilisateur. Court-circuiter les règles s'appliquent toujours ainsi.

Mais qu'est-ce que truthiness?
Il se réfère à la manière dont les objets sont évalués lorsqu'utilisé dans les expressions conditionnelles. @Patrick Haugh résume truthiness bien dans ce post.

Toutes les valeurs sont considérées comme des "truthy", sauf pour les éléments suivants, qui sont "falsy":

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

Un "truthy" de la valeur de satisfaire à la vérification effectuée par if ou while des déclarations. Nous utilisons des "truthy" et "falsy" pour se démarquer de la bool valeurs True et False.


Comment and Travaux

Nous nous appuyons sur les OP de la question comme un enchaîner sur une discussion sur la façon dont ces opérateurs dans ces instances.

Étant donné une fonction avec la définition

def foo(*args):
    ...

Comment puis-je retourner la différence entre la valeur minimum et maximum dans une liste de zéro ou plusieurs arguments?

Trouver le minimum et le maximum est facile (utilisez la fonction intégrée de fonctions!). Le seul hic ici est de manière appropriée de la manipulation de l'angle cas où l'argument de la liste peut être vide (par exemple, en appelant foo()). Nous pouvons faire les deux en une seule ligne grâce à l' and opérateur:

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

foo(1, 2, 3, 4, 5)
# 4

foo()
# 0

Depuis and est utilisé, la seconde expression doit également être évaluée si le premier est True. Notez que, si la première expression est évaluée à truthy, la valeur de retour est toujours le résultat de la deuxième expression. Si la première expression est évaluée à Falsy, puis le résultat retourné 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 plus grand que 0 (un nombre positif), de sorte que le résultat retourné est - max(args) - min(args). Otoh, que, si aucun argument n'est passé, len(args) est 0 qui est Falsy, et 0 est retourné.

Notez qu'une autre façon d'écrire cette fonction serait:

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

    return max(args) - min(args)

Ou, de façon plus concise,

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

Bien sûr, aucun de ces fonctions effectuer toute vérification de type, de sorte que si vous faites totalement confiance à la, ne pas compter sur la simplicité de ces constructions.


Comment or Travaux

J'explique le fonctionnement de l' or dans un mode similaire avec un exemple artificiel.

Étant donné une fonction avec la définition

def foo(*args):
    ...

Comment voulez-vous compléter foo de retour tous les numéros de plus de 9000?

Nous utilisons or de la poignée le coin le cas 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 une filtration sur la liste de retenir tous les numéros de plus de 9000. S'il existe de tels chiffres, le résultat de la compréhension de liste est non-vide de la liste qui est Truthy, de sorte qu'il est retourné (court-circuit en action ici). Si il n'existe pas de tels chiffres, puis le résultat de la liste comp est - [] qui est Falsy. Ainsi, la deuxième expression est maintenant évalué (une chaîne non vide) et qu'il est retourné.

À l'aide des instructions conditionnelles, on peut ré-écrire cette fonction,

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 souple en termes d'erreur de manipulation.

19voto

Amit Joki Points 16950

Citant Python Docs

Notez que ni and ni or de restreindre la valeur et le type de leur retour d' False et True, mais plutôt de retour de la dernière évaluation de l'argument. Cette est parfois utile, par exemple, si s est une chaîne de caractères qui doit être remplacé par une valeur par défaut si elle est vide, l'expression s or 'foo' rendements de l' valeur désirée.

Donc, c'est comment Python a été conçue pour évaluer les expressions booléennes et la documentation ci-dessus nous donne une idée de pourquoi ils ont fait ça donc.

Pour obtenir une valeur booléenne juste transtypage il.

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

Pourquoi?

De 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 l' or , c'est à dire, ce sera le retour de l'expression qui est Truthy dès qu'il le trouve, cause d'évaluer le reste de l'expression est redondant.

Au lieu de retourner hardcore True ou False, Python renvoie Truthy ou Falsey, qui sont de toute façon va l'évaluer à l' True ou False. Vous pouvez utiliser l'expression en est, et il faudra encore travailler.


Pour savoir ce qu' Truthy et Falsey, vérifiez Patrick Haugh réponse

7voto

Nithin Varghese Points 412

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

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

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

Si une valeur est faux dans un contexte booléen et renvoie la première valeur false.

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

Donc le code

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

renvoie la valeur de max(args)-min(args) quand il y a des arguments sinon il retourne len(args) , qui est de 0.

5voto

salparadise Points 519

Est-ce légitime/fiable de style, ou il y a des astuces à ce sujet?

C'est legit, c'est un court circuit d'évaluation où la dernière valeur est renvoyée.

Vous donnez un bon exemple. La fonction retournera 0 si aucun argument n'est passé, et le code n'a pas à vérifier pour un cas particulier de n arguments passés.

Une autre façon d'utiliser ce, à défaut, Aucune arguments pour une mutable primitif, comme une liste vide:

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

Si certains non-truthy valeur est passée de alist valeur par défaut est une liste vide, pratique pour éviter if déclaration et la mutable argument par défaut piège

3voto

Eric Duminil Points 38857

Pièges

Oui, il y a quelques pièges.

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

Tout d'abord, si fn retours 0, vous ne pouvez pas savoir si elle a été appelée sans paramètre, avec un paramètre ou avec de multiples paramètres égaux :

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

Qu'est - fn moyenne?

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

L'Argument des erreurs

Enfin, la logique and de l'opérateur est censé empêcher la fonction à l'échec si elle est appelée sans argument. Il ne parvient toujours pas si un argument n'est pas un nombre, mais:

>>> 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 une façon d'écrire la fonction en fonction de la "plus Facile de demander pardon que demander la permission." principe:

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

À titre d'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 à soulever une exception lors de l' delta est appelé sans argument, vous pouviez retourner une valeur qui ne peut 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X