Wyrażenie vs oświadczenie w Pythonie: Jaka jest różnica?
Po pewnym czasie pracy z Pythonem w końcu natkniesz się na dwa pozornie podobne terminy: wyrażenie i instrukcja. Przeglądając oficjalną dokumentację lub przeglądając wątki dotyczące Pythona na forum internetowym, możesz odnieść wrażenie, że ludzie używają tych terminów zamiennie. Często jest to prawdą, ale co dość mylące, zdarzają się przypadki, gdy rozróżnienie między wyrażeniem a stwierdzeniem staje się ważne.
Jaka jest więc różnica między wyrażeniami a stwierdzeniami w Pythonie?
Krótko mówiąc: wyrażenia mają wartości i stwierdzenia powodują skutki uboczne
Gdy otworzysz słownik języka Python, znajdziesz następujące dwie definicje:
Wyrażenie: fragment składni, który może zostać oceniony jako pewna wartość. (…) (Źródło)
stwierdzenie: stwierdzenie jest częścią apartamentu („blok” kodu). Oświadczenie jest albo wyrażeniem lub jednym z kilku konstrukcji ze słowem kluczowym (…) (źródło)
Cóż, to nie jest szczególnie pomocne, prawda? Na szczęście możesz podsumować najważniejsze fakty dotyczące wyrażeń i stwierdzeń w zaledwie trzech punktach:
- Wszystkie instrukcje w Pythonie należą do szerokiej kategorii instrukcji.
- Zgodnie z tą definicją wszystkie wyrażenia są również instrukcjami — czasami nazywanymi instrukcjami wyrażeń.
- Nie każde stwierdzenie jest wyrażeniem.
Z technicznego punktu widzenia każda linia lub blok kodu jest instrukcją w Pythonie. Obejmuje to wyrażenia, które reprezentują specjalny rodzaj instrukcji. Co sprawia, że wyrażenie jest wyjątkowe? Dowiesz się teraz.
Wyrażenia: Oświadczenia z wartościami
Zasadniczo możesz zastąpić wszystkie wyrażenia w kodzie obliczonymi wartościami, które wygenerują w czasie wykonywania, bez zmiany ogólnego zachowania programu. Z drugiej strony instrukcji nie można zastąpić równoważnymi wartościami, chyba że są wyrażeniami.
Rozważ następujący fragment kodu:
>>> x = 42
>>> y = x + 8
>>> print(y)
50
W tym przykładzie wszystkie trzy wiersze kodu zawierają instrukcje. Pierwsze dwa to instrukcje przypisania, podczas gdy trzecia to wywołanie funkcji
Kiedy spojrzysz na każdą linię bardziej, możesz rozpocząć demontaż odpowiedniego instrukcji na podskładniki. Na przykład operator przypisania (=
) składa się z części po lewej i po prawej stronie. Część po lewej stronie znaku równego wskazuje nazwę zmiennej, taką jak x
lub y
, a część po prawej stronie jest wartością przypisaną do tej zmiennej.
Słowo wartość jest tutaj kluczem. Zauważ, że zmiennej x
przypisano wartość dosłowną 42
, która jest wbudowana bezpośrednio w Twój kod. Natomiast poniższy wiersz przypisuje wyrażenie arytmetyczne x + 8
do zmiennej y
. Python musi najpierw obliczyć lub ocenić takie wyrażenie, aby określić ostateczną wartość zmiennej podczas działania programu.
Wyrażenia arytmetyczne to tylko jeden z przykładów wyrażeń w Pythonie. Inne obejmują wyrażenia logiczne, wyrażenia warunkowe i nie tylko. To, co je wszystkie łączy, to wartość, według której oceniają, chociaż każda wartość będzie na ogół inna. W rezultacie możesz bezpiecznie zastąpić dowolne wyrażenie odpowiednią wartością:
>>> x = 42
>>> y = 50
>>> print(y)
50
Ten krótki program daje taki sam wynik jak poprzednio i jest funkcjonalnie identyczny z poprzednim. Obliczyłeś wyrażenie arytmetyczne ręcznie i wstawiłeś wynikową wartość w jego miejsce.
Zauważ, że możesz ocenić x + 8
, ale nie możesz zrobić tego samego z przypisaniem
Oświadczenia: instrukcje z skutkami ubocznymi
Oświadczenia, które nie są wyrażeniami, powodują skutki uboczne, które zmieniają stan twojego programu lub wpływają na zasób zewnętrzny, takie jak plik na dysku. Na przykład, gdy przypisujesz wartość do zmiennej, definiujesz lub na nowo zdefiniujesz tę zmienną gdzieś w pamięci Pythona. Podobnie, gdy wywołujesz print()
, skutecznie piszesz do standardowego strumienia wyjściowego (stDout), który domyślnie wyświetla tekst na ekranie.
Uwaga: Podczas gdy instrukcje obejmują wyrażenia, większość osób używa słowa nieformalnie, gdy odnoszą się one do czystych instrukcji lub instrukcji bez wartości.
Dobra. Omówiłeś oświadczenia, które są wyrażeniami i stwierdzeniami, które nie są wyrażeniami. Odtąd możesz nazywać je odpowiednio jako czystymi wyrażeniami i czystymi stwierdzeniami . Ale okazuje się, że jest tu środkowy grunt.
Niektóre instrukcje mogą mieć wartość i jednocześnie powodować skutki uboczne. Innymi słowy, są to wyrażenia z efektami ubocznymi lub równoważnie, instrukcjami o wartości. Najwyższym przykładem tego byłoby funkcja Pythona następnego() wbudowana w język:
>>> fruit = iter(["apple", "banana", "orange"])
>>> next(fruit)
'apple'
>>> next(fruit)
'banana'
>>> next(fruit)
'orange'
Tutaj definiujesz obiekt iteratora o nazwie fruit
, który umożliwia iterację po liście nazw owoców. Za każdym razem, gdy wywołujesz next()
na tym obiekcie, modyfikujesz jego stan wewnętrzny, przesuwając iterator do następnego elementu na liście. To twój efekt uboczny. Jednocześnie next()
zwraca odpowiednią nazwę owocu, która jest częścią równania stanowiącą wartość.
Ogólnie rzecz biorąc, za najlepszą praktykę uważa się, aby w jednym wyrażeniu nie mieszać wartości ze skutkami ubocznymi. Programowanie funkcjonalne zachęca do używania czystych funkcji, podczas gdy języki proceduralne rozróżniają funkcje zwracające wartość i procedury, które tego nie robią.
Uwaga: W Pythonie wszystkie funkcje zwracają wartość. Nawet gdy jedna z twoich funkcji nie obejmuje instrukcji return
, Python sprawia, że funkcja domyślnie zwraca Brak
.
I wreszcie, choć na pierwszy rzut oka może to wydawać się sprzeczne z intuicją, w Pythonie znajduje się instrukcja, która nie daje żadnej wartości ani nie powoduje żadnych skutków ubocznych. Czy potrafisz zgadnąć, co to jest i kiedy chcesz go użyć? Aby dać ci małą wskazówkę, w informatyce jest to powszechnie znane jako no-op, co jest skrótem od brak operacji.
Zgadnij co? To instrukcja pass
Pythona! Często używasz go jako symbolu zastępczego w miejscach, gdzie składniowo wymagane jest oświadczenie, ale nie chcesz podejmować żadnych działań. Możesz użyć tych symboli zastępczych w pustych definicjach funkcji lub pętlach na początkowych etapach programowania. Zauważ, że chociaż Ellipsis
(...
) Pythona może służyć podobnemu celowi, ma wartość, dzięki czemu Ellipsis
jest wyrażeniem.
Podsumowanie wyrażeń kontra stwierdzenia
Aby poprowadzić punkt do domu, spójrz na następujący schemat. Pomoże Ci lepiej zrozumieć różne rodzaje wyrażeń i stwierdzeń w Pythonie:
Podsumowując, stwierdzenie może być dowolną instrukcją Pythona, niezależnie od tego, czy jest to pojedyncza linia, czy blok kodu. Wszystkie cztery ćwiartki na powyższym schemacie przedstawiają stwierdzenia. Jednak termin ten jest często używany w odniesieniu do czystych stwierdzeń , które powodują jedynie skutki uboczne bez posiadania wartości.
W przeciwieństwie do tego czysty wyrażenie jest szczególnym rodzajem stwierdzenia, które ocenia tylko pewną wartość bez powodowania skutków ubocznych. Ponadto możesz napotkać wyrażenia z efektami ubocznymi , które są stwierdzeniami o wartości. Są to wyrażenia, które wytwarzają wartość, jednocześnie powodując skutki uboczne. Wreszcie, no-op to stwierdzenie, które nie ma-nie wytwarza wartości ani nie powoduje żadnych skutków ubocznych.
Następnie nauczysz się, jak je rozpoznać na wolności.
Jak sprawdzić, czy instrukcja Pythona jest wyrażeniem kontra stwierdzenie?
W tym momencie już wiesz, że każda instrukcja Pythona jest technicznie zawsze stwierdzeniem. Tak więc bardziej szczegółowym pytaniem, które możesz zadać sobie pytanie, jest to, czy masz do czynienia z wyrażeniem, czy oświadczenie czyste . Odpowiesz na to pytanie dwojakie: ręcznie, a następnie programowo za pomocą Pythona.
Sprawdzanie ręcznie w reprezentacji pythona
Aby szybko ustalić odpowiedź na pytanie zadane w tej sekcji, możesz wykorzystać moc reprezentacji Pythona, która ocenia wyrażenia podczas ich pisania. Jeśli instrukcja jest wyrażeniem, natychmiast zobaczysz domyślną reprezentację ciągu jej wartości w wyjściu:
>>> 42 + 8
50
Jest to wyrażenie arytmetyczne, którego wynikiem jest 50
. Ponieważ nie przechwyciłeś jego wartości, na przykład przez przypisanie wyrażenia do zmiennej lub przekazanie go do funkcji jako argument, Python wyświetli obliczony wynik. Gdyby ten sam wiersz kodu znajdował się w skrypcie Pythona, interpreter zignorowałby obliczoną wartość, która zostałaby utracona.
Natomiast wykonywanie czystej instrukcji w reprezentacji nie pokazuje niczego w wyjściu:
>>> import math
>>>
To jest instrukcja import
, która nie ma odpowiadającej wartości. Zamiast tego ładuje określony pakiet Pythona do bieżącej przestrzeni nazw jako efekt uboczny.
Należy jednak zachować ostrożność przy takim podejściu. Czasami domyślna reprezentacja obliczonej wartości w postaci ciągu może wprowadzać w błąd. Rozważmy ten przykład:
>>> fruit = {"name": "apple", "color": "red"}
>>> fruit.get("taste")
>>> fruit.get("color")
'red'
Definiujesz słownik Pythona, który reprezentuje owoc. Przy pierwszym wywołaniu .get()
nie ma widocznych wyników. Ale potem wywołujesz ponownie funkcję .get()
z innym kluczem, która zwraca wartość powiązaną z tym kluczem, czyli 'czerwony'
.
W pierwszym przypadku .get()
zwraca wartość None
, aby wskazać brakującą parę klucz-wartość, ponieważ słownik nie zawiera klucza "taste"
. Jeśli jednak użyjesz istniejącego klucza, np. „color”
, metoda zwróci odpowiednią wartość.
REPL Pythona nigdy nie wyświetla Brak
, chyba że zostanie to wyraźnie wydrukowane, co może czasami prowadzić do zamieszania, jeśli nie jesteś świadomy tego zachowania. W razie wątpliwości zawsze możesz wywołać funkcję print()
na wyniku, aby ujawnić jego prawdziwą wartość:
>>> fruit.get("taste")
>>> print(fruit.get("taste"))
None
Chociaż to działa, istnieje bardziej niezawodny sposób sprawdzenia, czy instrukcja jest wyrażeniem w Pythonie.
Możesz przypisać instrukcję do zmiennej, aby sprawdzić, czy jest to wartość R. W przeciwnym razie, jeśli jest to czyste stwierdzenie, nie będziesz mógł go używać jako wartości dla swojej zmiennej i otrzymasz ten błąd:
>>> x = import math
File "<python-input-0>", line 1
x = import math
^^^^^^
SyntaxError: invalid syntax
Python zgłasza błąd SyntaxError
, aby poinformować Cię, że nie możesz przypisać instrukcji import
do zmiennej, ponieważ instrukcja ta nie ma żadnej konkretnej wartości.
Nieco szczególnym przypadkiem jest przypisanie łańcucha, które początkowo może wyglądać tak, jakbyś próbował przypisać y=42
do zmiennej
>>> x = y = 42
To jednak tworzy wiele zmiennych - y
W tym przypadku - odniesienie do tej samej wartości, 42
. Jest to notacja skrótem do wykonania dwóch oddzielnych przypisań, y=42
.
Innym sposobem stwierdzenia, czy kawałek kodu Pythona jest wyrażenie, czy czyste stwierdzenie polega na owijaniu go w nawiasach . Tegoszki zwykle pomagają grupować warunki do zmiany domyślnej kolejności operacji określonych przez pierwszeństwo operatora. Ale możesz zawinąć tylko wyrażenia, a nie stwierdzenia:
>>> (2 + 2)
4
>>> (x = 42)
File "<python-input-7>", line 1
(x = 42)
^^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
Linia 1 zawiera wyrażenie, które ocenia do czterech, podczas gdy wiersz 4 prowadzi do błędu składni, ponieważ Python bezskutecznie próbuje ocenić oświadczenie przypisania. Wyświetlany komunikat o błędzie wyświetla monit o zmianę operatora przypisania ( ==
) lub operatora Walrus (:=
).
Do tej pory porównywasz wyrażenia i stwierdzenia ręcznie w reprezentacji Pythona. W następnej sekcji dowiesz się, jak to robić programowo, co może pomóc, jeśli jest to powtarzalne zadanie, które chcesz zautomatyzować.
Budowanie wyrażenia Python vs Detector
Aby ustalić, czy instrukcja jest programowo wyrażeniem, czy instrukcją, możesz wywołać Python's eval()
i exec() wbudowane funkcje. Pierwsza funkcja pozwala dynamicznie ocenić wyrażenia, a druga może wykonać dowolny kod z ciągu:
>>> eval("2 + 2")
4
>>> exec("x = 42")
>>> print(x)
42
Ciąg "2 + 2"
zawiera wyrażenie Pythona, które eval()
ocenia na liczbę całkowitą 4
. Jeśli chcesz, możesz przypisać wartość zwróconą przez eval()
do zmiennej. Z drugiej strony exec()
niczego nie zwraca, a jedynie powoduje skutki uboczne. Zwróć uwagę, jak możesz uzyskać dostęp do zmiennej zdefiniowanej w ciągu znaków przekazanym do exec()
po wywołaniu tej funkcji w tym samym zakresie.
Obie funkcje zgłaszają błąd składni, gdy ciąg wejściowy nie jest prawidłowym wyrażeniem lub instrukcją:
>>> eval("2 +")
Traceback (most recent call last):
...
SyntaxError: invalid syntax
>>> exec("x = 2 +")
Traceback (most recent call last):
...
SyntaxError: invalid syntax
Dodatkowo exec()
:
>>> eval("x = 2 + 2")
Traceback (most recent call last):
...
SyntaxError: invalid syntax
>>> exec("x = 2 + 2")
Tę rozbieżność można wykorzystać do odróżnienia wyrażeń od czystych instrukcji. Pamiętaj jednak, aby wywołać eval()
przed exec()
, aby uniknąć fałszywych alarmów. Ponieważ wszystkie wyrażenia są instrukcjami, funkcja exec()
prawie zawsze zakończy się sukcesem, niezależnie od tego, czy otrzyma jako argument wyrażenie, czy instrukcję:
>>> eval("print('Hello, World!')")
Hello, World!
>>> exec("print('Hello, World!')")
Hello, World!
Obie funkcje dają identyczny wynik: Hello World!
. Jeśli wywołasz funkcję exec()
i zatrzymasz się w tym miejscu, możesz błędnie dojść do wniosku, że kod jest instrukcją, choć może to być prawidłowe wyrażenie. Tymczasem wszystkie wywołania funkcji w Pythonie są technicznie wyrażeniami i jako takie powinny być klasyfikowane. Oto, jak możesz temu zaradzić.
Aby uniknąć powielania kodu, możesz połączyć eval()
i exec()
w jedną funkcję, która deleguje do ast.parse()
z odpowiednim trybem:
>>> import ast
>>> def valid(code, mode):
... try:
... ast.parse(code, mode=mode)
... return True
... except SyntaxError:
... return False
...
>>> valid("x = 2 + 2", mode="eval")
False
>>> valid("x = 2 + 2", mode="exec")
True
Ta funkcja akceptuje kod Pythona i parametr , który może wziąć jedną z dwóch wartości: „eval”
lub exec "
. Funkcja zwraca Fałsz
, gdy wyrażenie lub instrukcja jest nieprawidłowa składniowo. W przeciwnym razie zwraca true
, gdy kod uruchomi się pomyślnie w określonym trybie.
Podsumowując, możesz napisać kolejną funkcję pomocnika, która zapewni reprezentację tekstową dla danego fragmentu kodu:
>>> def describe(code):
... if valid(code, mode="eval"):
... return "expression"
... elif valid(code, mode="exec"):
... return "statement"
... else:
... return "invalid"
...
>>> describe("x = 2 + 2")
'statement'
>>> describe("2 + 2")
'expression'
>>> describe("2 +")
'invalid'
Najpierw sprawdzasz, czy podany kod jest wyrażeniem. Gdy tak jest, zwracasz ciąg "expression"
. Jeśli nie, możesz sprawdzić, czy kod jest instrukcją. Jeśli kwalifikuje się jako instrukcja, zwracany jest ciąg "instrukcja"
. Na koniec, jeśli żaden z warunków nie jest spełniony, dochodzi do wniosku, że kod jest „nieprawidłowy”
.
Takie podejście może pomóc, gdy trzeba wykonać taki test programowy z jakiegokolwiek powodu. Być może budujesz własną reprezentację Pythona przy użyciu dopasowania wzoru strukturalnego w Pythonie i musisz zdecydować, kiedy wyświetlić oceniane wyrażenie.
Uwaga: Czy kawałek składni jest wyrażeniem, czy oświadczenie nie jest ustawione w kamieniu. Godnym uwagi przykładem tego jest funkcja print()
, która kiedyś była instrukcja print
w Legacy Python 2.
Do tej pory powinieneś bardziej intuicyjnie zrozumieć różnicę między wyrażeniami i stwierdzeniami w Pythonie. Powinieneś także być w stanie powiedzieć, który jest. Kolejnym ważnym pytaniem jest to, czy jest to jedynie semantyczne rozróżnienie dla purystów, czy też ma znaczenie w codziennej praktyce kodowania. Dowiesz się teraz!
Czy ta różnica ma znaczenie w Twoim codziennym programowaniu?
W większości przypadków nie musisz zbytnio zastanawiać się, czy pracujesz z wyrażeniem, czy instrukcją w Pythonie. To powiedziawszy, istnieją dwa godne uwagi wyjątki, o których warto wspomnieć:
- Wyrażenia
lambda
Astert Instrukcje
Zaczniesz od pierwszego, czyli wyrażenia lambda
.
Wyrażenie lambda
Słowo kluczowe Pythona Lambda
pozwala zdefiniować funkcję anonimową , która może być przydatna dla operacji jednorazowych, takich jak określenie klucza sortowania lub warunku do filtrowania:
>>> fruits = [("apple", 2), ("banana", 0), ("orange", 3)]
>>> sorted(fruits, key=lambda item: item[1])
[('banana', 0), ('apple', 2), ('orange', 3)]
>>> list(filter(lambda item: item[1] > 0, fruits))
[('apple', 2), ('orange', 3)]
Tutaj definiujesz listę dwuelementowych krotek zawierających nazwę owocu i jego odpowiednią ilość. Następnie sortujesz tę listę w kolejności rosnącej na podstawie ilości. Na koniec filtrujesz listę, pozostawiając tylko te owoce, które mają co najmniej jedną jednostkę.
Jest to wygodne podejście, o ile nie musisz odwoływać się do takich funkcji wbudowanych poza ich natychmiastowe użycie. Chociaż zawsze możesz przypisać wyrażenie lambda do zmiennej do późniejszego użycia, nie jest ona składniowa równoważna regularnej funkcji zdefiniowanej za pomocą def
.
Definicja funkcji rozpoczyna nowy blok kodu reprezentujący ciało funkcji. W Pythonie praktycznie każdy blok kodu należy do instrukcji złożonej, więc nie można go ocenić.
Uwaga: dowiesz się więcej o instrukcjach prostych i złożonych w następnej sekcji.
Ponieważ instrukcje nie mają wartości, nie możesz przypisać definicji funkcji do zmiennej, jak możesz z wyrażeniem Lambda:
>>> inline = lambda: 42
>>> regular = (
... def function():
... return 42
... )
...
File "<python-input-1>", line 2
def function():
^^^
SyntaxError: invalid syntax
Zmienna wbudowana
zawiera odniesienie do funkcji anonimowej zdefiniowanej jako Lambda , podczas gdy regularne jest nieudaną próbą przypisania nazwanej definicji funkcji do a zmienny.
Jednak możesz przypisać referencję do funkcji , która została już zdefiniowana w innym miejscu:
>>> def function():
... return 42
...
>>> alias = function
Zwróć uwagę na inne znaczenie def Function():
i funkcji odniesienia
, które pojawia się pod nią. Pierwszy to plan tego, co robi funkcja, a druga to adres funkcji, który można przekazać bez faktycznego wywoływania funkcji.
Uwaga: możliwość traktowania funkcji jako wartości jest kluczowym aspektem paradygmatu programowania funkcjonalnego. Pozwala na kompozycję funkcji i tworzenie funkcji wyższego rzędu.
Funkcja zdefiniowana za pomocą słowa kluczowego Lambda musi zawsze zawierać dokładnie jedno wyrażenie w jego ciele. Zostanie on oceniany i zwrócony domyślnie bez konieczności włączenia instrukcji return . W rzeczywistości korzystanie z instrukcji w funkcjach Lambda
jest wprost zabronione. Jeśli spróbujesz ich użyć, spowodujesz błąd składni:
>>> lambda: pass
File "<python-input-0>", line 1
lambda: pass
^^^^
SyntaxError: invalid syntax
Wiesz, że pass
jest instrukcją, więc nie ma na nią miejsca w wyrażeniu lambda
. Istnieje jednak obejście tego problemu. Zawsze możesz zawinąć jedną lub więcej instrukcji w funkcję i wywołać tę funkcję w wyrażeniu lambda, w ten sposób:
>>> import tkinter as tk
>>> def on_click(age):
... if age > 18:
... print("You're an adult.")
... else:
... print("You're a minor.")
...
>>> window = tk.Tk()
>>> button = tk.Button(window, text="Click", command=lambda: on_click(42))
>>> button.pack(padx=10, pady=10)
>>> window.mainloop()
Jest to minimalna aplikacja Python z graficznym interfejsem użytkownika (GUI) zbudowanym z Tkinter. Podświetlona linia rejestruje słuchacz zdarzenia kliknięcia przycisku. Prowadzący zdarzenie jest zdefiniowany jako wyrażenie lambda wbudowane, które nazywa funkcję opakowania, która otacza instrukcję warunkową. Nie można wyrazić tak złożonej logiki wyłącznie z wyrażeniem Lambda.
Twierdzenie Oświadczenie
Jeśli chodzi o instrukcję assert
, wokół niej panuje powszechne zamieszanie, które wynika z jej nieco mylącej składni. Zbyt łatwo zapomnieć, że assert
jest instrukcją i nie zachowuje się jak zwykłe wywołanie funkcji. Może to czasami powodować niezamierzone zachowanie, jeśli nie jesteś ostrożny.
W swojej najbardziej podstawowej formie, Słowo kluczowe Assert musi następować logiczne predykat lub wyrażenie, które ocenia wartość logiczną:
>>> assert 18 < int(input("What's your age? "))
What's your age? 42
>>> assert 18 < int(input("What's your age? "))
What's your age? 15
Traceback (most recent call last):
...
AssertionError
Jeśli wyrażenie stanie się prawdziwe, nic się nie stanie. Jeśli wyrażenie jest fałszywe, Python zgłasza AssertionError
, pod warunkiem, że nie wyłączyłeś całkowicie asercji za pomocą opcji wiersza poleceń lub zmiennej środowiskowej.
Opcjonalnie możesz dołączyć niestandardowy komunikat o błędzie, który będzie wyświetlany wraz z zgłoszonym błędem potwierdzenia. Aby to zrobić, umieść przecinek po predykacie i dołącz dosłowny ciąg znaków — lub dowolne wyrażenie, którego wynikiem jest jeden:
>>> assert 18 < int(input("What's your age? ")), "You're a minor"
What's your age? 15
Traceback (most recent call last):
...
AssertionError: You're a minor
Na razie w porządku. Ale włączenie dłuższego komunikatu o błędzie może prowadzić do mniej czytelnego kodu, kusząc cię w jakiś sposób złamanie tej linii.
Python oferuje schludną funkcję o nazwie niejawna kontynuacja linii, która pozwala podzielić długą instrukcję na wiele linii bez użycia jawnego backslash (
>>> assert (
... 18 < int(input("What's your age? ")),
... "You're a minor"
... )
<python-input-0>:1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
assert (
What's your age? 15
>>>
Zwykle to działa, ale nie w tym przypadku. Jak widać, współczesne wersje Pythona wyświetlają nawet ostrzeżenie, aby poinformować Cię, że prawdopodobnie coś jest nie tak.
Pamiętaj, że instrukcja Assert
oczekuje wyrażenia zaraz po słowach kluczowych. Kiedy otaczasz swój predykat i niestandardowy komunikat nawiasami, zasadniczo definiujesz krotkę, który jest traktowany jako pojedyncze wyrażenie. Python ocenia takie nieokreślone sekwencje na true . Tak więc instrukcja Assert
zawsze przechodzi, niezależnie od faktycznego warunku, który próbujesz sprawdzić.
Aby uniknąć tego problemu, lepiej będzie zastosować wyraźną kontynuację linii, jeśli chcesz przerwać długą linię:
>>> assert 18 < int(input("What's your age? ")), \
... "You're a minor"
What's your age? 15
Traceback (most recent call last):
...
AssertionError: You're a minor
Teraz ponownie obserwujesz oczekiwane zachowanie, ponieważ predykat został poprawnie oceniony.
Wcześniej dowiedziałeś się, że bloki kodu w Pythonie są częścią instrukcji złożonych. W następnej kolejności poznasz różnice między instrukcjami prostymi i złożonymi w Pythonie.
Jakie są proste i złożone stwierdzenia w Pythonie?
Oświadczenia są podstawowymi elementami konstrukcyjnymi twoich programów Python. Pozwalają ci kontrolować przepływ wykonania i wykonywać działania, takie jak przypisanie wartości do zmiennych. Możesz podzielić instrukcje na dwa typy podstawowe:
- Proste stwierdzenia
- Stwierdzenia złożone
Proste instrukcje mogą zmieścić się w jednym wierszu, podczas gdy instrukcje złożone obejmują inne instrukcje, które zazwyczaj obejmują wiele wierszy kodu z wcięciem, po których następuje znak nowej linii.
Poniższa tabela przedstawia niektóre typowe przykłady obu rodzajów stwierdzeń:
assert age > 18
jeśli wiek > 18 lat: ...
import math
Podczas gdy true: ... return 42
dla _ w zakresie(3): ...
pass
TRUD: ... x = 42 + 8
def add (x, y): ...
Jak widać, proste stwierdzenia powodują szczególny efekt uboczny - z wyjątkiem instrukcji pass
, który nie. Z drugiej strony instrukcje złożone obejmują tradycyjne konstrukty przepływu kontrolnego, takie jak pętle, warunki warunkowe i definicje funkcji.
Kiedy przyjrzysz się bliżej instrukcji złożonych, przekonasz się, że składają się one z jednego lub więcej klauzul , z których każdy zawiera nagłówek i apartament . Zastanów się nad następującym stwierdzeniem warunkowym jako przykładem:
if age > 18:
print("Welcome!")
elif age < 18:
raise ValueError("You're too young to be here.")
else:
import sys
sys.exit(1)
Podświetlone linie przedstawiają nagłówki klauzul instrukcji złożonej, podczas gdy pozostałe linie przedstawiają odpowiadające im zestawy. Na przykład klauzula if sprawdza, czy wiek jest większy niż osiemnaście lat i warunkowo wywołuje funkcję print()
w swoim zestawie.
Wszystkie nagłówki klauzul w instrukcji złożonej są wyrównane na tym samym poziomie wcięcia. Rozpoczynają się słowem kluczowym Pythona, takim jak if
, elif
lub else
i kończą dwukropkiem (:
), który wyznacza początek bloku kodu pakietu. Zestaw to zbiór instrukcji regulowanych odpowiednią klauzulą. W tym przypadku trzy klauzule określają, który z zestawów należy wykonać w oparciu o warunek wieku.
Istnieje specjalny rodzaj złożonego stwierdzenia znanego jako lista stwierdzeń , która składa się z sekwencji prostych stwierdzeń. Chociaż każdy z nich musi być umieszczony na jednej linii, możesz wcisnąć wiele prostych stwierdzeń w jedną linię. Czy wiesz, jak?
Jak umieścić wiele stwierdzeń na jednej linii?
W większości przypadków ze względu na czytelność lepiej jest umieścić tylko jedną instrukcję lub wyrażenie w każdym wierszu. Jeśli jednak nalegasz na umieszczenie więcej niż jednego wiersza w tym samym wierszu, możesz użyć średnika (;
) jako separatora instrukcji.
Popularny przypadek użycia, w którym może to być pomocne, polega na uruchomieniu programu jedno -liniowego w wierszu poleceń za pomocą opcji Python -C
:
$ python -c 'import sys; print(sys.version)'
3.13.0 (main, Oct 19 2024, 15:05:58) [GCC 13.2.0]
Dzięki temu możesz szybko testować pomysły w formie krótkich fragmentów kodu. Jednak obecnie większość terminali umożliwia bezproblemowe rozłożenie kodu na wiele wierszy:
$ python -c '
> import sys
> print(sys.version)
> '
3.13.0 (main, Oct 19 2024, 15:05:58) [GCC 13.2.0]
Polecenie, którego oczekuje interpreter Pythona, może składać się z wielu wierszy.
Innym częstym przypadkiem użycia średnika jest wstawienie punktu przerwania do kodu. Przed wersją Pythona 3.7, w której wprowadzono wbudowaną funkcję breakpoint()
, do debugera można było przejść za pomocą następującego idiomatycznego wiersza kodu:
import pdb; pdb.set_trace()
Gdy interpreter naciśnie pdb.set_trace()
, wstrzyma wykonywanie i przejdziesz do interaktywnej sesji debugowania za pomocą debugera Pythona (pdb).
Ponadto możesz spróbować użyć tej sztuczki, aby celowo zaciemnić lub zminimalizować kod Pythona. Jednak nie będziesz w stanie obejść pewnych ograniczeń składniowych, więc lepsze wyniki uzyskasz dzięki zewnętrznym narzędziom, takim jak Pyarmor.
Przed zamknięciem tego samouczka musisz odpowiedzieć na ostatnie pytanie dotyczące wyrażeń i instrukcji w Pythonie. Takiego, który da ci podstawy do stawienia czoła bardziej zaawansowanym wyzwaniom programistycznym.
Czy wypowiedzi mogą mieć podwójną naturę w Pythonie?
Ponieważ wszystkie instrukcje w Pythonie są instrukcjami, możesz wykonać wyrażenie, które ma skutki uboczne, bez uwzględnienia obliczonej wartości. Dzieje się tak często, gdy wywołujesz funkcję lub metodę, ale ignorujesz zwracaną przez nią wartość:
>>> with open("file.txt", mode="w", encoding="utf-8") as file:
... file.write("Hello, World!")
...
13
W powyższym przykładzie wywołujesz metodę obiektu pliku "Hello, World!"
jako argument. Chociaż ta metoda zwraca liczbę pisanych znaków, która wynosi trzynaście, lekceważycie je, nie przechwytując zwróconej wartości na zmienną lub w jakikolwiek sposób przetwarzając ją. Zauważ jednak, że powtórzenie Pythona automatycznie wyświetla wynik ostatniego ocenianego wyrażenia w tej sytuacji.
Dobra. Możesz więc skorzystać z wyrażeń wyłącznie ze względu na ich działania niepożądane, jeśli je mają. Ale co z odwrotnie? Czy możesz ocenić stwierdzenia? Zaraz dowiesz się!
Zadania
Niektóre języki programowania zacierają granice między instrukcjami i wyrażeniami. Na przykład możesz ocenić instrukcję przypisania w C lub C++:
#include <stdio.h>
int main() {
int x;
while (x = fgetc(stdin)) {
if (x == EOF)
break;
putchar(x);
}
return 0;
}
Ten krótki program odtwarza wszystko, co użytkownik napisze na klawiaturze. Zwróć uwagę na podświetloną linię, która pobiera kolejny znak ze standardowego wejścia i przypisuje go do zmiennej lokalnej x
. Jednocześnie to przypisanie jest oceniane jako wyrażenie logiczne i używane jako warunek kontynuacji pętli while
. Innymi słowy, dopóki liczba porządkowa znaku wejściowego jest różna od zera, pętla jest kontynuowana.
Jest to znane źródło błędów, które od wieków nękają programy C i C++. Programiści często błędnie używaliby w takich warunkach operatora przypisania (=
) zamiast zamierzonego operatora testu równości (==
) ze względu na ich wizualne podobieństwo. Chociaż w powyższym przykładzie celowo zastosowano tę funkcję, zazwyczaj było to wynikiem błędu ludzkiego, który mógł prowadzić do nieoczekiwanego zachowania.
Przez długi czas główni programiści wzbraniali się przed implementacją podobnej funkcji w Pythonie z powodu obaw związanych z potencjalnym zamieszaniem. Tak było aż do wersji Python 3.8, w której wprowadzono operator morsa (:=
), aby umożliwić wyrażenia przypisania:
MAX_BYTES = 1024
buffer = bytearray()
with open("audio.wav", mode="rb") as file:
while chunk := file.read(MAX_BYTES):
buffer.extend(chunk)
W tym fragmencie kodu przyrostowo czytasz plik WAV w binarnych fragmentach, aż potkniesz się na bajcie sterowania końcowego pliku, który jest wskazany przez pustą część. Jednak zamiast wyraźnie sprawdzać, czy fragment zwrócony przez .Read()
nie jest pusty-lub jeśli ocenia true
-Wykorzystujesz wyrażenie przypisania, aby wykonać i oceniać i ocenić zadanie w jednym kroku.
Oszczędza to kilka linii kodu, który w innym przypadku wyglądałby tak:
MAX_BYTES = 1024
buffer = bytearray()
with open("audio.wav", mode="rb") as file:
while True:
chunk = file.read(MAX_BYTES)
if not chunk:
break
buffer.extend(chunk)
Zamieniłeś pętlę deterministyczną w nieskończoną i dodałeś trzy kolejne instrukcje: instrukcję przypisania, instrukcję warunkową i instrukcję break
.
W przeciwieństwie do przykładu C, nie ma możliwości pomylania wyrażenia przypisania z jego instrukcją. Python nadal nie pozwala oświadczenia przypisania w kontekście logicznym. Przy takich okazjach musisz wyraźnie użyć operatora Walrusa, który ładnie dostosowuje się do jednego z aforyzmów z Zen Pythona:
Wyraźne jest lepsze niż ukryte. (Źródło)
Istnieją inne przykłady instrukcji, które mają swoje odpowiedniki wyrażeń w Pythonie. Następnie przyjrzysz się warunkom i wyrażeniom.
Warunki
Wiele języków programowania udostępnia trójskładnikowy operator warunkowy, który łączy w sobie trzy elementy: warunek logiczny, wartość, jeśli warunek ma wartość True
i wartość alternatywną, jeśli warunek ma wartość Fałsz
.
W językach z rodziny C operator trójskładnikowy (?:
) przypomina emotikon Elvisa Presleya z jego charakterystyczną fryzurą. Chociaż w tych językach nazywa się to operatorem Elvisa, Python trzyma się bardziej konserwatywnego terminu wyrażenie warunkowe. Oto jak to wygląda:
>>> def describe(users):
... if not users:
... print("No people")
... else:
... print(len(users), "people" if len(users) > 1 else "person")
...
>>> describe([])
No people
>>> describe(["Alice"])
1 person
>>> describe(["Alice", "Bob"])
2 people
Na pierwszy rzut oka warunkowe wyrażenie Pythona przypomina standardowe stwierdzenie warunkowe skondensowane w pojedynczej linii. Zaczyna się od wyrażenia związanego z wartością prawdy, a następnie warunek sprawdzania, a następnie wyrażenie odpowiadające wartości fałszowania. Umożliwia ocenę logiki warunkowej, która zwykle wymagałaby użycia instrukcji warunkowej.
Zrozumienia
Innym przykładem stwierdzeń zajmujących szary obszar są wszelkiego rodzaju wyrażenia zrozumienia , takie jak rozumienie listy lub wyrażenie generatora, z których można skorzystać, aby uniknąć jawnych pętli:
>>> fruits = [("apple", 2), ("banana", 0), ("orange", 3)]
>>> sorted(name.title() for name, quantity in fruits if quantity > 0)
['Apple', 'Orange']
Ponownie składnia jest podobna do pętli for
i instrukcji if
, ale zwijasz je w jedną linię i zmieniasz kolejność ich poszczególnych elementów. Działa to dobrze tylko wtedy, gdy warunek i wyrażenie do obliczenia są stosunkowo małe, więc można je zmieścić w jednej lub dwóch liniach. W przeciwnym razie otrzymasz zaśmiecony fragment kodu, który będzie trudny do odczytania.
Wreszcie, generatory Pythona zasługują na wspomnienie tutaj, ponieważ używają składni, która może być jednocześnie wyrażeniem, jak i stwierdzeniem.
Generatory
Słowa kluczowe yield
i yield from
pojawiają się wewnątrz funkcji generatora, które umożliwiają wydajną obsługę dużych strumieni danych lub definiowanie współprogramów.
Pozwalasz Pythonowi wykonywać yield
jako instrukcję, gdy chcesz wygenerować wartości z funkcji generatora:
>>> import random
>>> def generate_noise(size):
... for _ in range(size):
... yield 2 * random.random() - 1
...
>>> list(generate_noise(3))
[0.7580438973021972, 0.5273057193944659, -0.3041263216813208]
Ten generator jest producentem wartości losowych między -1 a 1. Używanie wydajność
tutaj jest nieco podobny do używania instrukcji return
. Przekazuje szereg wartości dzwoniącego jako efekt uboczny, ale zamiast całkowicie zakończyć funkcję, instrukcja plon zawiesza wykonywanie funkcji, umożliwiając jej wznowienie i kontynuowanie później.
Teraz możesz opcjonalnie ocenić yield
jako wyrażenie, aby zamienić swój generator w prosumenta (producenta i konsumenta) z potencjalnie wieloma punktami wejścia i wyjścia. Każde wyrażenie yield
może dostarczać i odbierać wartości w obie strony. Otrzymane wartości reprezentują moc wyjściową generatora, natomiast wartości przesłane z powrotem do generatora stanowią dane wejściowe:
>>> def lowpass_filter():
... a = yield
... b = yield a
... while True:
... a, b = b, (yield (a + b) / 2)
...
Pierwsze wyrażenie Pet
domyślnie nie daje nic ( Brak
), ale odbiera wartość ze świata zewnętrznego, gdy jest oceniane jako wyrażenie. Ta wartość jest następnie przypisywana do zmiennej lokalnej o nazwie
.
Drugie wyrażenie yield
daje a
, jednocześnie przechowując inną wartość w b
. W tym momencie filtr zostaje nasycony i gotowy do przetwarzania przychodzących danych. Zdolność do pochłaniania i wytwarzania wartości w każdym punkcie zawieszenia (yield
) umożliwia przepychanie sygnału przez filtr tak, jak prąd przepływa przez obwód.
Ostatnie wyrażenie
za pomocą b
, a następnie przypisuje nową wartość do b
.
UWAGA: nawiasy wokół wyrażenia wydajności jako wyrażenia, które należy ocenić przed przypisaniem.
Możesz połączyć ze sobą oba generatory, wywołując .send()
na filtrze dolnoprzepustowym, który oblicza dwupunktową średnią ruchomą, która wygładza sygnał:
>>> lf = lowpass_filter()
>>> lf.send(None)
>>> for value in generate_noise(10):
... print(f"{value:>5.2f}: {lf.send(value):>5.2f}")
...
0.66: 0.66
-0.01: 0.32
0.30: 0.15
-0.10: 0.10
-0.98: -0.54
0.79: -0.10
0.31: 0.55
-0.10: 0.11
-0.25: -0.18
0.57: 0.16
Liczby w lewej kolumnie pochodzą prosto z generatora szumu i są podawane bezpośrednio do filtra dolnoprzepustowego. Prawa kolumna zawiera wyjście tego filtra. Zauważ, że musisz najpierw wyprzedzić swojego prosumera, wysyłając Brak
lub wywołując następny()
, aby awansował do pierwszego wyrażenia wydajności
.
Wniosek
W tym samouczku poznałeś podstawowe różnice między wyrażeniami i instrukcjami w Pythonie. Nauczyłeś się, że wyrażenia to fragmenty składni, których wartością jest wartość, natomiast instrukcje to instrukcje, które mogą powodować skutki uboczne. Jednocześnie istnieje między nimi szara strefa.
Zrozumienie tego rozróżnienia ma kluczowe znaczenie dla programistów Pythona, ponieważ wpływa na sposób pisania i strukturę kodu. Teraz, gdy masz już te umiejętności, możesz pisać bardziej precyzyjny i wyrazisty kod w języku Python, w pełni wykorzystując wyrażenia i instrukcje do tworzenia solidnych i wydajnych programów.