Jak działają „i” i „lub” z wartościami innymi niż boolowskie?

101

Próbuję nauczyć się Pythona i natknąłem się na kod, który jest ładny i krótki, ale nie do końca ma sens

kontekst był następujący:

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

Rozumiem, co robi, ale dlaczego Python to robi - tj. Zwraca wartość, a nie True / False?

10 and 7-2

zwraca 5. Podobnie, zmiana i na lub spowoduje zmianę funkcjonalności. Więc

10 or 7 - 2

Zwróciłbym 10.

Czy to legalny / niezawodny styl, czy są w tym jakieś problemy?

Marcin
źródło
1
and(jak również or) nie ogranicza się do pracy z wartościami logicznymi ani do zwracania ich.
cs95
1
IMNSHO: to nieco mylący sposób pisania tego; Nie mogę od razu stwierdzić, czy ma zwrócić wartość logiczną (czy istnieje wyraźna wartość min i maks), czy liczbę (jaka jest różnica między min i max). Jeśli to drugie, pojawia się również pytanie, czy sensowne jest podanie tej różnicy listy o zerowej długości jako liczby. (Zamiast Nonelub wyjątek)
ilkkachu
7
Działa, jak wyjaśniły inne osoby, jednak jednym z możliwych problemów jest to, że jeśli zwraca 0, nie można stwierdzić, czy argsbył pusty, czy niepusty, ale miał wszystkie elementy równe.
Zwłaszcza Lime
@EspicularLime: dokładnie. Wspomniałem o tym w mojej odpowiedzi .
Eric Duminil

Odpowiedzi:

138

TL; DR

Zaczynamy od podsumowania dwóch zachowań dwóch operatorów logicznych andi or. Te idiomy będą stanowić podstawę naszej dyskusji poniżej.

and

Zwróć pierwszą wartość Falsy, jeśli takie istnieją, w przeciwnym razie zwróć ostatnią wartość w wyrażeniu.

or

Zwróć pierwszą wartość Truthy, jeśli istnieje, w przeciwnym razie zwróć ostatnią wartość w wyrażeniu.

Zachowanie jest również podsumowane w dokumentach , zwłaszcza w tej tabeli:

wprowadź opis obrazu tutaj

Jedynym operatorem zwracającym wartość logiczną niezależnie od jej operandów jest notoperator.


Oceny „Prawdy” i „Prawdy”

Wyrok

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

Jest bardzo pytonicznym zwięzłym (i prawdopodobnie mniej czytelnym) sposobem powiedzenia „jeśli argsnie jest puste, zwróć wynik max(args) - min(args)”, w przeciwnym razie zwróć 0. Ogólnie jest to bardziej zwięzła reprezentacja if-elsewyrażenia. Na przykład,

exp1 and exp2

Powinien (z grubsza) przetłumaczyć na:

r1 = exp1
if r1:
    r1 = exp2

Lub równoważnie

r1 = exp2 if exp1 else exp1

Podobnie,

exp1 or exp2

Powinien (z grubsza) przetłumaczyć na:

r1 = exp1
if not r1:
    r1 = exp2

Lub równoważnie

r1 = exp1 if exp1 else exp2

Gdzie exp1i exp2są dowolnymi obiektami Pythona lub wyrażeniami, które zwracają jakiś obiekt. Kluczem do zrozumienia zastosowań logiki andi oroperatorów jest zrozumienie, że nie ograniczają się one do operowania lub zwracania wartości boolowskich. Każdy obiekt, który ma wartość prawdziwości, może zostać tutaj przetestowany. Obejmuje to int, str, list, dict, tuple, set, NoneType, i zdefiniowane przez użytkownika obiektów. Nadal obowiązują zasady dotyczące zwarć.

Ale czym jest prawdomówność?
Odnosi się do sposobu oceny obiektów, gdy są używane w wyrażeniach warunkowych. @Patrick Haugh ładnie podsumowuje prawdomówność w tym poście .

Wszystkie wartości są uważane za „prawdziwe” z wyjątkiem następujących, które są „fałszywe”:

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] - pusty list
  • {} - pusty dict
  • () - pusty tuple
  • '' - pusty str
  • b'' - pusty bytes
  • set() - pusty set
  • pusty range, jakrange(0)
  • obiekty, dla których
    • obj.__bool__() zwroty False
    • obj.__len__() zwroty 0

Wartość „prawdziwa” spełni sprawdzenie dokonane przez instrukcje iflub while. Używamy „prawda” i „fałsz”, aby odróżnić boolwartości Truei False.


Jak anddziała

Opieramy się na pytaniu OP jako o przejściu do dyskusji na temat tego, jak ci operatorzy w tych przypadkach.

Biorąc pod uwagę funkcję z definicją

def foo(*args):
    ...

Jak zwrócić różnicę między wartością minimalną i maksymalną na liście zawierającej zero lub więcej argumentów?

Znalezienie minimum i maksimum jest łatwe (skorzystaj z wbudowanych funkcji!). Jedynym problemem jest odpowiednia obsługa przypadku narożnika, w którym lista argumentów może być pusta (na przykład wywołanie foo()). Możemy to zrobić w jednej linii dzięki andoperatorowi:

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

foo()
# 0

Ponieważ andjest używane, drugie wyrażenie również musi zostać ocenione, jeśli jest to pierwsze True. Zauważ, że jeśli pierwsze wyrażenie jest oceniane jako prawdziwe, wartość zwracana jest zawsze wynikiem drugiego wyrażenia . Jeśli pierwsze wyrażenie zostanie ocenione jako Falsy, zwrócony wynik jest wynikiem pierwszego wyrażenia.

W powyższej funkcji If foo otrzymuje jeden lub więcej argumentów, len(args)jest większe niż 0(liczba dodatnia), więc zwracany wynik to max(args) - min(args). OTOH, jeśli nie zostaną przekazane żadne argumenty, len(args)to 0jest Falsy i 0jest zwracane.

Zauważ, że alternatywnym sposobem zapisania tej funkcji byłoby:

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

Lub, bardziej zwięźle,

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

Jeśli oczywiście żadna z tych funkcji nie sprawdza typów, więc jeśli nie ufasz całkowicie dostarczonym danym wejściowym, nie polegaj na prostocie tych konstrukcji.


Jak ordziała

Wyjaśniam działanie or w podobny sposób na wymyślonym przykładzie.

Biorąc pod uwagę funkcję z definicją

def foo(*args):
    ...

Jak byś ukończył foozwracanie wszystkich liczb9000 ?

Używamy tutaj ordo obsługi skrzynki narożnej. Definiujemy foojako:

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 przeprowadza filtrację na liście, aby zachować wszystkie liczby 9000 . Jeśli istnieją takie liczby, wynikiem zrozumienia listy jest niepusta lista, która jest Prawdą, więc jest zwracana (zwarcie w działaniu tutaj). Jeśli nie ma takich liczb, wynikiem zestawienia listy []jest Falsy. Zatem drugie wyrażenie jest teraz oceniane (niepusty łańcuch) i jest zwracane.

Korzystając z warunków warunkowych, możemy ponownie napisać tę funkcję jako,

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

Tak jak poprzednio, ta struktura jest bardziej elastyczna pod względem obsługi błędów.

cs95
źródło
33
Poświęcenie całej przejrzystości na rzecz zwięzłości nie jest „pytoniczne”, co moim zdaniem ma miejsce w tym przypadku. To nie jest prosta konstrukcja.
DBedrenko
11
Myślę, że należy zauważyć, że wyrażenia warunkowe w Pythonie sprawiły, że ta składnia jest mniej powszechna. Z pewnością wolę max (args) - min (args) if len (args) else 0 od oryginału.
richardb
3
Innym częstym problemem, który na początku jest mylący, jest przypisanie wartości, jeśli jej nie ma: „some_var = arg or 3”
Erik
12
@Baldrickk, zanim ludzie zaczną walić w tę składnię na korzyść operatorów trójskładnikowych, pamiętaj, że jeśli chodzi o wyrażenia warunkowe n-ary, operatory trójskładnikowe mogą szybko wymknąć się spod kontroli. Na przykład if ... else (if ... else (if ... else (if ... else ...)))może być równie dobrze przepisany, jak ... and ... and ... and ... and ...iw tym momencie naprawdę trudno jest argumentować o czytelności w obu przypadkach.
cs95
4
Poświęcanie przejrzystości na rzecz zwięzłości nie jest pythonowe, ale tak się nie dzieje. To dobrze znany idiom. To idiom, którego musisz się nauczyć, jak każdy inny idiom, ale nie jest to „poświęcenie jasności”.
Miles Rout
18

Cytowanie z Python Docs

Zauważ, że ani andnie or ograniczają się wartość i wpisać wrócą do Falsea True, lecz zwróci ostatnią oceniana argumentu . Jest to czasami przydatne, np. Jeśli sjest to ciąg znaków, który powinien zostać zastąpiony wartością domyślną, jeśli jest pusty, wyrażenie s or 'foo'zwraca żądaną wartość.

Tak więc zaprojektowano Python do oceny wyrażeń boolowskich, a powyższa dokumentacja daje nam wgląd w to, dlaczego to zrobili.

Aby uzyskać wartość logiczną, po prostu wpisz ją.

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

Czemu?

Zwarcie.

Na przykład:

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

To samo dotyczy orrównież, to znaczy, że zwróci wyrażenie, które jest Prawdą gdy tylko je znajdzie, ponieważ ocena reszty wyrażenia jest zbędna.

Zamiast zwracać hardcore Truelub False, Python zwraca Truthy lub Falsey , które i tak zostaną ocenione na Truelub False. Możesz użyć wyrażenia bez zmian i nadal będzie działać.


Aby dowiedzieć się, czym jest Truthy i Falsey , sprawdź odpowiedź Patricka Haugha

Amit Joki
źródło
7

i i lub wykonują logikę boolowską, ale zwracają jedną z rzeczywistych wartości podczas porównywania. W przypadku używania i wartości są oceniane w kontekście logicznym od lewej do prawej. 0, „”, [], (), {} i None są fałszywe w kontekście logicznym; wszystko inne jest prawdą.

Jeśli wszystkie wartości są prawdziwe w kontekście boolowskim i zwraca ostatnią wartość.

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

Jeśli jakakolwiek wartość jest fałszywa w kontekście logicznym i zwraca pierwszą fałszywą wartość.

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

Więc kod

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

zwraca wartość, max(args)-min(args)gdy istnieją argumenty , w przeciwnym razie zwraca wartość len(args)0.

Nithin Varghese
źródło
5

Czy to legalny / niezawodny styl, czy są w tym jakieś problemy?

To jest uzasadnione, jest to ocena zwarcia podczas której zwracana jest ostatnia wartość.

Dajesz dobry przykład. Funkcja zwróci0 jeśli nie zostaną przekazane żadne argumenty, a kod nie musi sprawdzać, czy nie ma specjalnego przypadku braku przekazanych argumentów.

Innym sposobem użycia tego jest domyślne ustawienie argumentów None na zmienny prymityw, taki jak pusta lista:

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

Jeśli przekazana jest jakaś nieprawdziwa wartość alist, domyślnie jest to pusta lista, wygodny sposób na uniknięcie ifinstrukcji i pułapki na zmienny domyślny argument

salparadise
źródło
3

Gotchas

Tak, jest kilka pułapek.

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

Po pierwsze, jeśli fnzwraca 0, nie możesz wiedzieć, czy został wywołany bez żadnego parametru, z jednym parametrem, czy z wieloma równymi parametrami:

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

Co to fnznaczy?

W takim razie Python jest językiem dynamicznym. Nigdzie nie jest określone, co fnrobi, jakie powinno być jego wejście i jak powinno wyglądać jego wyjście. Dlatego bardzo ważne jest, aby poprawnie nazwać funkcję. Podobnie argumenty nie muszą być wywoływane args. delta(*numbers)lub calculate_range(*numbers)lepiej opisać, co funkcja ma robić.

Błędy argumentów

Wreszcie andoperator logiczny ma zapobiegać niepowodzeniu funkcji, jeśli zostanie wywołana bez żadnego argumentu. Jednak nadal zawodzi, jeśli jakiś argument nie jest liczbą:

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

Możliwa alternatywa

Oto sposób na zapisanie funkcji zgodnie z opisem w sekcji „Łatwiej prosić o przebaczenie niż o pozwolenie”. zasada :

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

Jako przykład:

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

Jeśli naprawdę nie chcesz zgłaszać wyjątku, gdy deltajest wywoływany bez żadnego argumentu, możesz zwrócić pewną wartość, która nie byłaby możliwa w inny sposób (np. -1Lub 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
Eric Duminil
źródło
0

Czy to legalny / niezawodny styl, czy są w tym jakieś problemy?

Chciałbym dodać do tego pytania, że ​​jest to nie tylko legalne i niezawodne, ale także ultra praktyczne. Oto prosty przykład:

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

Dlatego naprawdę możesz go wykorzystać na swoją korzyść. Aby być spójnym, widzę to tak:

Or operator

orOperator Pythona zwraca pierwszą wartość Prawdy-y lub ostatnią wartość i zatrzymuje się

And operator

andOperator Pythona zwraca pierwszą wartość False-y lub ostatnią wartość i zatrzymuje się

Za kulisami

W Pythonie wszystkie liczby są interpretowane jako Truez wyjątkiem 0. Dlatego mówiąc:

0 and 10 

jest taki sam jak:

False and True

Co jest oczywiste False. Dlatego logiczne jest, że zwraca 0

scharette
źródło
0

Tak. To jest prawidłowe zachowanie i porównanie.

Przynajmniej w Pythonie A and Bpowraca Bjeśli Azasadniczo Truew tym, jeśli Ajest niezerowy NIE NoneNIE pusty pojemnik (takie jak puste list, dictitp). Ajest zwracany IFF Ajest zasadniczo Falselub Nonelub Empty lub Null.

Z drugiej strony, A or Bpowraca Ajeśli Ajest w istocie Truetym, jeśli Ato nie jest pusta, NIE NoneNIE pusty pojemnik (taki jak pusta list, dictitp), w przeciwnym wypadku zwraca B.

Łatwo jest nie zauważyć (lub przeoczyć) tego zachowania, ponieważ w Pythonie każdy non-nullniepusty obiekt oceniany jako True jest traktowany jak wartość logiczna.

Na przykład wszystkie poniższe wydruki będą miały wartość „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"

Z drugiej strony we wszystkich następnych zostanie wydrukowany „Fałsz”

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

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

if set ([]): 
    print "True"
else: 
    print "False"
emmanuelsa
źródło
Dziękuję Ci. W Aistocie chciałem napisać True. Poprawione.
emmanuelsa
0

zrozumieć w prosty sposób,

I : if first_val is False return first_val else second_value

na przykład:

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

ale,

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

i => jeśli ktoś jest fałszywy, będzie fałszywy. jeśli jedno i drugie jest prawdą, tylko to się stanie

LUB: if first_val is False return second_val else first_value

powodem jest to, że jeśli pierwsza jest fałszywa, sprawdź, czy 2 jest prawdziwe, czy nie.

na przykład:

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

ale,

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

lub => jeśli ktoś jest fałszywy, będzie to prawda. więc jeśli pierwsza wartość jest fałszywa, bez względu na to, jaka ma być wartość 2. więc zwraca drugą wartość, czymkolwiek może być.

jeśli ktoś jest prawdziwy, to się stanie. jeśli oba są fałszywe, stanie się fałszywe.

Mohideen bin Mohammed
źródło