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?
python
operators
logical-operators
Marcin
źródło
źródło
and
(jak równieżor
) nie ogranicza się do pracy z wartościami logicznymi ani do zwracania ich.None
lub wyjątek)0
, nie można stwierdzić, czyargs
był pusty, czy niepusty, ale miał wszystkie elementy równe.Odpowiedzi:
TL; DR
Zaczynamy od podsumowania dwóch zachowań dwóch operatorów logicznych
and
ior
. Te idiomy będą stanowić podstawę naszej dyskusji poniżej.Zachowanie jest również podsumowane w dokumentach , zwłaszcza w tej tabeli:
Jedynym operatorem zwracającym wartość logiczną niezależnie od jej operandów jest
not
operator.Oceny „Prawdy” i „Prawdy”
Wyrok
len(args) and max(args) - min(args)
Jest bardzo
pytonicznymzwięzłym (i prawdopodobnie mniej czytelnym) sposobem powiedzenia „jeśliargs
nie jest puste, zwróć wynikmax(args) - min(args)
”, w przeciwnym razie zwróć0
. Ogólnie jest to bardziej zwięzła reprezentacjaif-else
wyraż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
exp1
iexp2
są dowolnymi obiektami Pythona lub wyrażeniami, które zwracają jakiś obiekt. Kluczem do zrozumienia zastosowań logikiand
ior
operatoró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 toint
,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 .
Jak
and
działaOpieramy się na pytaniu OP jako o przejściu do dyskusji na temat tego, jak ci operatorzy w tych przypadkach.
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ękiand
operatorowi:def foo(*args): return len(args) and max(args) - min(args)
foo(1, 2, 3, 4, 5) # 4 foo() # 0
Ponieważ
and
jest używane, drugie wyrażenie również musi zostać ocenione, jeśli jest to pierwszeTrue
. 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 tomax(args) - min(args)
. OTOH, jeśli nie zostaną przekazane żadne argumenty,len(args)
to0
jest Falsy i0
jest 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
or
działaWyjaśniam działanie
or
w podobny sposób na wymyślonym przykładzie.Używamy tutaj
or
do obsługi skrzynki narożnej. Definiujemyfoo
jako: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 liczby9000
. 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.
źródło
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.Cytowanie z Python Docs
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
or
ró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
True
lubFalse
, Python zwraca Truthy lub Falsey , które i tak zostaną ocenione naTrue
lubFalse
. 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
źródło
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.źródło
To jest uzasadnione, jest to ocena zwarcia podczas której zwracana jest ostatnia wartość.
Dajesz dobry przykład. Funkcja zwróci
0
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ęcieif
instrukcji i pułapki na zmienny domyślny argumentźródło
Gotchas
Tak, jest kilka pułapek.
fn() == fn(3) == fn(4, 4)
Po pierwsze, jeśli
fn
zwraca0
, 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
fn
znaczy?W takim razie Python jest językiem dynamicznym. Nigdzie nie jest określone, co
fn
robi, 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ływaneargs
.delta(*numbers)
lubcalculate_range(*numbers)
lepiej opisać, co funkcja ma robić.Błędy argumentów
Wreszcie
and
operator 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
delta
jest wywoływany bez żadnego argumentu, możesz zwrócić pewną wartość, która nie byłaby możliwa w inny sposób (np.-1
LubNone
):>>> 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
źródło
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
operatoror
Operator Pythona zwraca pierwszą wartość Prawdy-y lub ostatnią wartość i zatrzymuje sięAnd
operatorand
Operator Pythona zwraca pierwszą wartość False-y lub ostatnią wartość i zatrzymuje sięZa kulisami
W Pythonie wszystkie liczby są interpretowane jako
True
z 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źródło
Tak. To jest prawidłowe zachowanie i porównanie.
Przynajmniej w Pythonie
A and B
powracaB
jeśliA
zasadniczoTrue
w tym, jeśliA
jest niezerowy NIENone
NIE pusty pojemnik (takie jak pustelist
,dict
itp).A
jest zwracany IFFA
jest zasadniczoFalse
lubNone
lub Empty lub Null.Z drugiej strony,
A or B
powracaA
jeśliA
jest w istocieTrue
tym, jeśliA
to nie jest pusta, NIENone
NIE pusty pojemnik (taki jak pustalist
,dict
itp), w przeciwnym wypadku zwracaB
.Łatwo jest nie zauważyć (lub przeoczyć) tego zachowania, ponieważ w Pythonie każdy
non-null
niepusty 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"
źródło
A
istocie chciałem napisaćTrue
. Poprawione.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.
źródło