Come si comportano "and" e "or" con valori non booleani?


97

Sto cercando di imparare Python e mi sono imbattuto in un codice che è carino e breve ma non ha del tutto senso

il contesto era:

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

Capisco cosa sta facendo, ma perché Python lo fa, ovvero restituisce il valore invece di True / False?

10 and 7-2

restituisce 5. Allo stesso modo, la modifica di e in o comporterà una modifica della funzionalità. Così

10 or 7 - 2

Ritornerei 10.

È questo stile legittimo / affidabile o ci sono trucchi su questo?


1
and(così come or) non si limita a lavorare o restituire valori booleani.
cs95

1
IMNSHO: questo è un modo un po 'confuso di scriverlo; Non posso dire immediatamente se deve restituire un booleano (c'è un minimo e un massimo distinti) o un numero (qual è la differenza tra minimo e massimo). In quest'ultimo caso, c'è anche la domanda se abbia senso dare quella differenza di una lista di lunghezza zero come numero. (Invece di Noneo un'eccezione)
ilkkachu

7
Funziona, come altre persone hanno spiegato, tuttavia un possibile problema è che se ritorna 0non puoi dire se argsera vuoto o non era vuoto ma aveva tutti gli elementi uguali.
Soprattutto Lime

@ Specialmente Lime: esattamente. L'ho menzionato nella mia risposta .
Eric Duminil

Risposte:


135

TL; DR

Iniziamo riassumendo i due comportamenti dei due operatori logici ande or. Questi idiomi formeranno la base della nostra discussione di seguito.

and

Restituisce il primo valore Falsy se ce ne sono, altrimenti restituisce l'ultimo valore nell'espressione.

or

Restituisce il primo valore Truthy se ce ne sono, altrimenti restituisce l'ultimo valore nell'espressione.

Il comportamento è anche riassunto nei documenti , soprattutto in questa tabella:

inserisci qui la descrizione dell'immagine

L'unico operatore che restituisce un valore booleano indipendentemente dai suoi operandi è l' notoperatore.


Valutazioni "Verità" e "Verità"

La dichiarazione

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

È un molto divinatorio conciso (e probabilmente meno leggibile) modo di dire "se argsnon è vuota, restituisce il risultato di max(args) - min(args)", altrimenti restituisce 0. In generale, è una rappresentazione più concisa di if-elseun'espressione. Per esempio,

exp1 and exp2

Dovrebbe (approssimativamente) tradurre in:

r1 = exp1
if r1:
    r1 = exp2

O, equivalentemente,

r1 = exp1 if exp1 else exp2

Allo stesso modo,

exp1 or exp2

È equivalente a,

r1 = exp1
if not r1:
    r1 = exp2

Dove exp1e exp2sono oggetti Python arbitrari o espressioni che restituiscono un oggetto. La chiave per comprendere gli usi degli operatori logici ande orqui è capire che non sono limitati a operare o restituire valori booleani. Qualsiasi oggetto con un valore di veridicità può essere testato qui. Questo include int, str, list, dict, tuple, set,NoneType , e l'utente oggetti definiti. Anche le regole di cortocircuito si applicano.

Ma cos'è la verità?
Si riferisce al modo in cui gli oggetti vengono valutati quando utilizzati nelle espressioni condizionali. @Patrick Haugh riassume bene la verità in questo post .

Tutti i valori sono considerati "veritieri" tranne i seguenti, che sono "falsi":

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] - un vuoto list
  • {} - un vuoto dict
  • () - un vuoto tuple
  • '' - un vuoto str
  • b'' - un vuoto bytes
  • set() - un vuoto set
  • un vuoto range, comerange(0)
  • oggetti per i quali
    • obj.__bool__() ritorna False
    • obj.__len__() ritorna 0

Un valore "veritiero" soddisferà il controllo eseguito da ifo while . Usiamo "truthy" e "falsy" per differenziarci dai boolvalori Truee False.


Come andfunziona

Partiamo dalla domanda di OP come seguito in una discussione su come questi operatori in questi casi.

Data una funzione con la definizione

def foo(*args):
    ...

Come restituisco la differenza tra il valore minimo e massimo in un elenco di zero o più argomenti?

Trovare il minimo e il massimo è facile (usa le funzioni integrate!). L'unico intoppo qui è gestire in modo appropriato il caso d'angolo in cui l'elenco degli argomenti potrebbe essere vuoto (ad esempio, la chiamata foo()). Possiamo fare entrambe le cose in un'unica linea grazie andall'operatore:

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

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

foo()
# 0

Poiché andviene utilizzata, la seconda espressione deve essere valutata anche se la prima è True. Nota che, se la prima espressione viene valutata come vera, il valore restituito è sempre il risultato della seconda espressione . Se la prima espressione viene valutata come falsa, il risultato restituito è il risultato della prima espressione.

Nella funzione precedente, If fooriceve uno o più argomenti, len(args)è maggiore di 0(un numero positivo), quindi il risultato restituito è max(args) - min(args). OTOH, se vengono passati argomenti, len(args)è 0che è Falsy, e 0viene restituito.

Nota che un modo alternativo per scrivere questa funzione sarebbe:

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

    return max(args) - min(args)

O, più concisamente,

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

Ovviamente nessuna di queste funzioni esegue alcun controllo del tipo, quindi a meno che non ti fidi completamente dell'input fornito, non fare affidamento sulla semplicità di questi costrutti.


Come orfunziona

Spiego il funzionamento di orin modo simile con un esempio artificioso.

Data una funzione con la definizione

def foo(*args):
    ...

Come completeresti fooper restituire tutti i numeri 9000?

Usiamo orper gestire il caso d'angolo qui. Definiamo foocome:

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!'

fooesegue un filtraggio nell'elenco per mantenere tutti i numeri 9000. Se esistono tali numeri, il risultato della comprensione della lista è un elenco non vuoto che è Vero, quindi viene restituito (cortocircuito in azione qui). Se non esistono tali numeri, il risultato della lista comp è []che è Falsy. Quindi la seconda espressione viene ora valutata (una stringa non vuota) e viene restituita.

Usando i condizionali, potremmo riscrivere questa funzione come,

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

    return r

Come prima, questa struttura è più flessibile in termini di gestione degli errori.


33
Non è "pitonico" sacrificare tutta la chiarezza per la brevità, cosa che penso sia il caso qui. Non è un costrutto semplice.
DBedrenko

11
Penso che si dovrebbe notare che le espressioni condizionali di Python hanno reso questa sintassi meno comune. Certamente preferisco max (args) - min (args) if len (args) else 0 all'originale.
richardb

3
Un altro comune che all'inizio crea confusione è l'assegnazione di un valore se non ne esiste nessuno: "some_var = arg or 3"
Erik

12
@Baldrickk prima che le persone inizino a criticare questa sintassi a favore degli operatori ternari, tieni presente che quando si tratta di espressioni di condizioni n-ariche, gli operatori ternari possono sfuggire di mano rapidamente. Per esempio,if ... else (if ... else (if ... else (if ... else ...))) può essere riscritto altrettanto bene ... and ... and ... and ... and ...ea quel punto diventa davvero difficile sostenere la leggibilità per entrambi i casi.
cs95

4
Non è pitonico sacrificare la chiarezza per la brevità, ma questo non lo fa. È un idioma ben noto. È un idioma che devi imparare, come qualsiasi altro idioma, ma difficilmente "sacrifica la chiarezza".
Miles Rout

18

Citando da Python Docs

Nota che né andor restringono il valore e il tipo a cui ritornano Falsee True, ma piuttosto restituiscono l' ultimo argomento valutato . Questo a volte è utile, ad esempio, se sè una stringa che deve essere sostituita da un valore predefinito se è vuota, l'espressione s or 'foo'restituisce il valore desiderato.

Quindi, questo è il modo in cui Python è stato progettato per valutare le espressioni booleane e la documentazione di cui sopra ci dà un'idea del motivo per cui lo hanno fatto.

Per ottenere un valore booleano è sufficiente digitarlo.

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

Perché?

Cortocircuito.

Per esempio:

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

Lo stesso vale oranche per , cioè, restituirà l'espressione che è Vero non appena la trova, perché valutare il resto dell'espressione è ridondante.

Invece di restituire hardcore Trueo False, Python restituisce Truthy o Falsey , che comunque valuteranno in Trueo False. Puoi usare l'espressione così com'è e continuerà a funzionare.


Per sapere cosa sono Truthy and Falsey , controlla la risposta di Patrick Haugh


7

e e o eseguire logica booleana, ma ritornano uno dei valori effettivi quando si stanno confrontando. Quando si utilizza e , i valori vengono valutati in un contesto booleano da sinistra a destra. 0, "", [], (), {} e Nessuno sono false in un contesto booleano; tutto il resto è vero.

Se tutti i valori sono veri in un contesto booleano e restituisce l'ultimo valore.

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

Se un valore è falso in un contesto booleano e restituisce il primo valore falso.

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

Quindi il codice

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

restituisce il valore di max(args)-min(args)quando c'è args altrimenti restituisce len(args)che è 0.


5

È questo stile legittimo / affidabile o ci sono trucchi su questo?

Questo è legittimo, è un file valutazione di cortocircuito in cui viene restituito l'ultimo valore.

Fornisci un buon esempio. La funzione tornerà0 se non vengono passati argomenti e il codice non deve controllare un caso speciale di nessun argomento passato.

Un altro modo per usarlo è impostare come predefinito Nessuno argomenti su una primitiva mutabile, come un elenco vuoto:

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

Se viene passato un valore non veritiero, il valore alistpredefinito è un elenco vuoto, un modo pratico per evitare ifun'istruzione e il trabocchetto dell'argomento predefinito mutabile


3

Trabocchetti

Sì, ci sono alcuni trucchi.

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

Innanzitutto, se fnritorna 0, non puoi sapere se è stato chiamato senza alcun parametro, con un parametro o con più parametri uguali:

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

Cosa fnsignifica?

Quindi, Python è un linguaggio dinamico. Non è specificato da nessuna parte cosa fnfa, quale dovrebbe essere il suo input e come dovrebbe essere il suo output. Pertanto, è molto importante denominare correttamente la funzione. Allo stesso modo, non è necessario chiamare gli argomenti args. delta(*numbers)o calculate_range(*numbers)potrebbe descrivere meglio cosa dovrebbe fare la funzione.

Errori di argomento

Infine, l' andoperatore logico dovrebbe impedire alla funzione di fallire se chiamata senza alcun argomento. Tuttavia, fallisce ancora se qualche argomento non è un numero:

>>> 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'

Possibile alternativa

Ecco un modo per scrivere la funzione in base a "Più facile chiedere perdono che permesso". principio :

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

Come esempio:

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

Se davvero non vuoi sollevare un'eccezione quando deltaviene chiamato senza alcun argomento, potresti restituire un valore che non sarebbe possibile altrimenti (ad esempio -1o 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

0

È questo stile legittimo / affidabile o ci sono trucchi su questo?

Vorrei aggiungere a questa domanda che non solo è legittimo e affidabile, ma è anche ultra pratico. Qui c'è un semplice esempio:

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

Quindi puoi davvero usarlo a tuo vantaggio. Per essere conscio ecco come la vedo io:

Or operatore

L' oroperatore di Python restituisce il primo valore Truth-y, o l'ultimo valore, e si ferma

And operatore

L' andoperatore di Python restituisce il primo valore False-y, o l'ultimo valore, e si ferma

Dietro le quinte

In Python, tutti i numeri vengono interpretati come Truetranne 0. Pertanto, dicendo:

0 and 10 

equivale a:

False and True

Il che è chiaramente False. È quindi logico che restituisca 0


0

Sì. Questo è il comportamento corretto e il confronto.

Almeno in Python, A and Brestituisce Bse Aè essenzialmente Truecompresi, se Anon è nullo, NON NoneNON un contenitore vuoto (come un vuoto list, dicte così via). Aviene restituito IFF Aè essenzialmente FalseoNone o Empty o Null.

D'altra parte, A or Bi ritorni Ase Aè essenzialmente Truecomprendendo se Anon è nullo, NON NoneNON un contenitore vuoto (come un vuoto list, dictecc), altrimenti restituisce B.

È facile non notare (o trascurare) questo comportamento perché, in Python, qualsiasi non-null oggetto non vuoto valutato come True viene trattato come un booleano.

Ad esempio, tutto quanto segue stamperà "Vero"

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"

D'altra parte, tutto quanto segue verrà stampato "False"

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

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

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

Grazie. Volevo scrivere Aè essenzialmente True. Corretto.
emmanuelsa

0

capire in modo semplice,

E : if first_val is False return first_val else second_value

per esempio:

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

ma,

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

e => se qualcuno è falso, sarà falso. se entrambi sono veri, allora solo diventerà vero

O : if first_val is False return second_val else first_value

la ragione è che se first è falso controlla se 2 è vero o no.

per esempio:

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

ma,

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

o => se qualcuno è falso, sarà vero. quindi se il primo valore è falso, non importa quale sia il valore 2. quindi restituisce il secondo valore qualunque cosa possa essere.

se qualcuno è vero, allora diventerà vero. se entrambi sono falsi, diventerà falso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.