zaoszczędź 35% od ceny katalogowej * powiązanej książki lub wieloformatowego ebooka (EPUB + MOBI + PDF) z artykułem z kodem rabatowym.
* Zobacz informit.com/terms
- pozycja 18: zmniejsz szum wizualny za pomocą zmiennych argumentów pozycyjnych
- rzeczy do zapamiętania
- pozycja 19: zapewnienie opcjonalnego zachowania z argumentami kluczowymi
- rzeczy do zapamiętania
- poz. 20: Użyj None i Docstrings, aby określić dynamiczne domyślne argumenty
- rzeczy do zapamiętania
- pozycja 21: wymuszanie przejrzystości za pomocą argumentów tylko dla słów kluczowych
- argumenty tylko dla słów kluczowych w Pythonie 2
- rzeczy do zapamiętania
pozycja 18: zmniejsz szum wizualny za pomocą zmiennych argumentów pozycyjnych
akceptowanie opcjonalnych argumentów pozycyjnych (często nazywanych gwiazdkowymi argami w odniesieniu do konwencjonalnej nazwy parametru *args) może sprawić, że wywołanie funkcji będzie bardziej wyraźne i usunie szum wizualny.
na przykład, powiedz, że chcesz zalogować niektóre informacje debugowania. Z ustaloną liczbą argumentów, potrzebujesz funkcji, która pobiera wiadomość i listę wartości.
def
log(message, values):if not
values:else
: values_str=
', '
.join(str
(x)for
xin
values)'%s: %s'
%
(message, values_str))log('My numbers are'
, )log('Hi there'
, )>>>My numbers are: 1, 2Hi there
przekazywanie pustej listy, gdy nie ma wartości do logowania, jest uciążliwe i hałaśliwe. Lepiej byłoby całkowicie pominąć drugi argument. Możesz to zrobić w Pythonie, poprzedzając nazwę ostatniego parametru pozycyjnego *. Pierwszy parametr dla wiadomości log jest wymagany, podczas gdy dowolna liczba kolejnych argumentów pozycyjnych jest opcjonalna. Ciało funkcji nie musi się zmieniać, tylko dzwoniący.
def
log(message,*
values):# The only difference
if not
values:else
: values_str=
', '
.join(str
(x)for
xin
values)'%s: %s'
%
(message, values_str))log('My numbers are'
,1
,2
)log('Hi there'
)# Much better
>>>My numbers are: 1, 2Hi there
Jeśli masz już listę i chcesz wywołać funkcję argumentu zmiennej, taką jak log, możesz to zrobić za pomocą operatora*. To instruuje Pythona, aby przekazywał elementy z sekwencji jako argumenty pozycyjne.
favorites=
log('Favorite colors'
,*
favorites)>>>Favorite colors: 7, 33, 99
istnieją dwa problemy z akceptacją zmiennej liczby argumentów pozycyjnych.
pierwsza sprawa polega na tym, że argumenty zmiennych są zawsze zamieniane w krotkę, zanim zostaną przekazane do funkcji. Oznacza to, że jeśli wywołujący twoją funkcję używa operatora * na generatorze, będzie on iterowany, dopóki nie zostanie wyczerpany. Wynikająca krotka będzie zawierać każdą wartość z generatora, która może pochłonąć dużo pamięci i spowodować awarię programu.
def
my_generator():for
iin
range
(10
):yield i
def
my_func(*
args):=
my_generator()my_func(*
it)>>>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
funkcje akceptujące *args są najlepsze w sytuacjach, gdy wiadomo, że liczba wejść na liście argumentów będzie stosunkowo mała. Jest to idealne rozwiązanie dla wywołań funkcji, które przekazują wiele literałów lub nazw zmiennych razem. Jest to przede wszystkim dla wygody programisty i czytelności kodu.
drugi problem z *args polega na tym, że nie można dodawać nowych argumentów pozycyjnych do funkcji w przyszłości bez migracji każdego wywołującego. Jeśli spróbujesz dodać argument pozycyjny na początku listy argumentów, istniejące wywołania zostaną subtelnie złamane, jeśli nie zostaną zaktualizowane.
def
log(sequence, message,*
values):if not
values:'%s: %s'
%
(sequence, message))else
: values_str=
', '
.join(str
(x)for
xin
values)'%s: %s: %s'
%
(sequence, message, values_str))log(1
,'Favorites'
,7
,33
)# New usage is OK
log('Favorite numbers'
,7
,33
)# Old usage breaks
>>>1: Favorites: 7, 33Favorite numbers: 7: 33
problem polega na tym, że drugie wywołanie logu używało 7 jako parametru wiadomości, ponieważ nie podano argumentu sekwencji. Błędy takie jak ten są trudne do wyśledzenia, ponieważ kod nadal działa bez żadnych WYJĄTKÓW. Aby całkowicie uniknąć tej możliwości, powinieneś użyć argumentów tylko dla słów kluczowych, gdy chcesz rozszerzyć funkcje, które akceptują *args (patrz pozycja 21: “wymuszaj jasność za pomocą argumentów tylko dla słów kluczowych”).
rzeczy do zapamiętania
- funkcje mogą przyjmować zmienną liczbę argumentów pozycyjnych za pomocą *args w instrukcji def.
- możesz użyć elementów z sekwencji jako argumentów pozycyjnych dla funkcji z operatorem*.
- użycie operatora * z generatorem może spowodować wyczerpanie się pamięci i awarię programu.
- dodawanie nowych parametrów pozycyjnych do funkcji akceptujących * args może wprowadzać trudne do znalezienia błędy.
pozycja 19: zapewnienie opcjonalnego zachowania z argumentami kluczowymi
podobnie jak większość innych języków programowania, wywołanie funkcji w Pythonie pozwala na przekazywanie argumentów według pozycji.
def
remainder(number, divisor):return
number%
divisorassert
remainder(20
,7
)==
6
wszystkie argumenty pozycyjne do funkcji Pythona mogą być również przekazywane za pomocą słowa kluczowego, gdzie nazwa argumentu jest używana w przypisaniu w nawiasach wywołania funkcji. Argumenty kluczowe mogą być przekazywane w dowolnej kolejności, o ile wszystkie wymagane argumenty pozycyjne są określone. Możesz mieszać i dopasowywać argumenty kluczowe i pozycyjne. Wywołania te są równoważne:
remainder(20
,7
)remainder(20
, divisor=
7
)remainder(number=
20
, divisor=
7
)remainder(divisor=
7
, number=
20
)
argumenty pozycyjne muszą być podane przed argumentami kluczowymi.
remainder(number=
20
,7
)>>>SyntaxError: non-keyword arg after keyword arg
każdy argument może być podany tylko raz.
remainder(20
, number=
7
)>>>TypeError: remainder() got multiple values for argument 'number'
elastyczność argumentów słów kluczowych zapewnia trzy znaczące korzyści.
pierwszą zaletą jest to, że argumenty kluczowe sprawiają, że wywołanie funkcji staje się jaśniejsze dla nowych czytelników kodu. Przy wywołaniu reszta(20, 7) nie jest jasne, który argument jest liczbą, a który dzielnikiem, nie patrząc na implementację metody reszta. W wywołaniu z argumentami kluczowymi number=20 i divisor = 7 od razu widać, który parametr jest używany do każdego celu.
drugi wpływ argumentów słów kluczowych polega na tym, że mogą mieć wartości domyślne określone w definicji funkcji. Pozwala to funkcji zapewnić dodatkowe Funkcje, gdy ich potrzebujesz, ale pozwala zaakceptować domyślne zachowanie przez większość czasu. Może to wyeliminować powtarzający się kod i zmniejszyć hałas.
na przykład, powiedzmy, że chcesz obliczyć szybkość przepływu płynu do kadzi. Jeśli kadzi jest również na skali, to można użyć różnicy między dwoma pomiarami wagi w dwóch różnych czasach, aby określić natężenie przepływu.
def
flow_rate(weight_diff, time_diff):return
weight_diff/
time_diffweight_diff=
0.5
time_diff=
3
flow=
flow_rate(weight_diff, time_diff)'%.3f kg per second'
%
flow)>>>0.167 kg per second
w typowym przypadku warto znać natężenie przepływu w kilogramach na sekundę. Innym razem pomocne byłoby użycie ostatnich pomiarów z czujnika w celu przybliżenia większych skal czasowych, takich jak godziny lub dni. Takie zachowanie można zastosować w tej samej funkcji, dodając argument dla współczynnika skalowania przedziału czasu.
def
flow_rate(weight_diff, time_diff, period):return
(weight_diff/
time_diff)*
period
problem polega na tym, że teraz musisz podać argument period za każdym razem, gdy wywołujesz funkcję, nawet w powszechnym przypadku natężenia przepływu na sekundę (gdzie okres wynosi 1).
flow_per_second=
flow_rate(weight_diff, time_diff,1
)
aby uczynić to mniej hałaśliwym, mogę podać domyślną wartość argumentu period.
def
flow_rate(weight_diff, time_diff, period=
1
):return
(weight_diff/
time_diff)*
period
argument period jest teraz opcjonalny.
flow_per_second=
flow_rate(weight_diff, time_diff)flow_per_hour=
flow_rate(weight_diff, time_diff, period=
3600
)
to działa dobrze dla prostych wartości domyślnych (robi się trudne dla złożonych wartości domyślnych—patrz pozycja 20: “użyj None i Docstrings, aby określić dynamiczne argumenty domyślne”).
trzecim powodem użycia argumentów słów kluczowych jest to, że zapewniają one potężny sposób na rozszerzenie parametrów funkcji, zachowując kompatybilność wsteczną z istniejącymi wywołującymi. Pozwala to zapewnić dodatkową funkcjonalność bez konieczności migracji dużej ilości kodu, zmniejszając ryzyko wprowadzenia błędów.
na przykład, powiedzmy, że chcesz rozszerzyć powyższą funkcję flow_rate, aby obliczyć natężenia przepływu w jednostkach wagi oprócz kilogramów. Możesz to zrobić, dodając nowy opcjonalny parametr, który zapewnia współczynnik konwersji do preferowanych jednostek miary.
def
flow_rate(weight_diff, time_diff, period=
1
, units_per_kg=
1
):return
((weight_diff*
units_per_kg)/
time_diff)*
period
domyślną wartością argumentu dla units_per_kg jest 1, co sprawia, że zwracane jednostki wagi pozostają w kilogramach. Oznacza to, że wszystkie istniejące osoby wywołujące nie będą widzieć żadnych zmian w zachowaniu. Nowe wywołania do flow_rate mogą określać nowy argument słowa kluczowego, aby zobaczyć nowe zachowanie.
pounds_per_hour=
flow_rate(weight_diff, time_diff, period=
3600
, units_per_kg=
2.2
)
jedynym problemem z tym podejściem jest to, że opcjonalne argumenty słów kluczowych, takie jak period I units_per_kg, mogą być nadal podawane jako argumenty pozycyjne.
pounds_per_hour=
flow_rate(weight_diff, time_diff,3600
,2.2
)
pozycjonowanie opcjonalnych argumentów może być mylące, ponieważ nie jest jasne, do czego odpowiadają wartości 3600 i 2.2. Najlepszą praktyką jest zawsze określanie opcjonalnych argumentów za pomocą nazw słów kluczowych i nigdy nie przekazywanie ich jako argumentów pozycyjnych.
rzeczy do zapamiętania
- argumenty funkcji mogą być określone przez pozycję lub słowo kluczowe.
- wyjaśniają, jaki jest cel każdego argumentu, gdy byłby mylony tylko z argumentami pozycyjnymi.
- argumenty ze słowami kluczowymi o wartościach domyślnych ułatwiają dodawanie nowych zachowań do funkcji, zwłaszcza gdy funkcja ma istniejące wywołania.
- opcjonalne argumenty słowa kluczowego powinny być zawsze przekazywane przez słowo kluczowe zamiast przez pozycję.
słowa kluczowe
poz. 20: Użyj None i Docstrings, aby określić dynamiczne domyślne argumenty
czasami musisz użyć niestatycznego typu jako domyślnej wartości argumentu słowa kluczowego. Na przykład, powiedzmy, że chcesz wydrukować wiadomości logowania, które są oznaczone czasem zalogowanego zdarzenia. W domyślnym przypadku wiadomość powinna zawierać czas wywołania funkcji. Możesz spróbować następującego podejścia, zakładając, że domyślne argumenty są ponownie oceniane za każdym razem, gdy funkcja jest wywoływana.
def
log(message, when=
datetime.now()):'%s: %s'
%
(when, message))log('Hi there!'
)sleep(0.1
)log('Hi again!'
)>>>2014-11-15 21:10:10.371432: Hi there!2014-11-15 21:10:10.371432: Hi again!
znaczniki czasu są takie same, ponieważ datetime.teraz jest wykonywany tylko jeden raz: gdy funkcja jest zdefiniowana. Domyślne wartości argumentów są obliczane tylko raz na obciążenie modułu, co zwykle ma miejsce podczas uruchamiania programu. Po załadowaniu modułu zawierającego ten kod, DateTime.teraz domyślny argument nie będzie już nigdy sprawdzany.
konwencją osiągnięcia pożądanego rezultatu w Pythonie jest podanie domyślnej wartości None i udokumentowanie rzeczywistego zachowania w docstringu (patrz pozycja 49: “Write Docstrings for Every Function, Class, and Module”). Gdy twój kod widzi wartość argumentu None, odpowiednio przydzielasz wartość domyślną.
def
log(message, when=
None
):"""Log a message with a timestamp.
Args:
message: Message to print.
when: datetime of when the message occurred.
Defaults to the present time.
"""
when=
datetime.now()if
whenis None else
when'%s: %s'
%
(when, message))
teraz znaczniki czasu będą różne.
log('Hi there!'
)sleep(0.1
)log('Hi again!'
)>>>2014-11-15 21:10:10.472303: Hi there!2014-11-15 21:10:10.573395: Hi again!
używanie None dla domyślnych wartości argumentów jest szczególnie ważne, gdy argumenty są zmienne. Na przykład, powiedzmy, że chcesz załadować wartość zakodowaną jako dane JSON. Jeśli dekodowanie danych nie powiedzie się, domyślnie ma zostać zwrócony pusty słownik. Możesz spróbować tego podejścia.
def
decode(data, default=
{}):try:
return
json.loads(data)except
ValueError:return
default
problem jest tutaj taki sam jak datetime.teraz przykład powyżej. Słownik podany dla domyślnego będzie współdzielony przez wszystkie wywołania do dekodowania, ponieważ domyślne wartości argumentów są obliczane tylko raz (w czasie ładowania modułu). Może to powodować niezwykle zaskakujące zachowanie.
foo=
decode('bad data'
)foo=
5
bar=
decode('also bad'
)bar=
1
'Foo:'
, foo)'Bar:'
, bar)>>>Foo: {'stuff': 5, 'meep': 1}Bar: {'stuff': 5, 'meep': 1}
można oczekiwać dwóch różnych słowników, każdy z jednym kluczem i wartością. Ale modyfikowanie jednego wydaje się również modyfikować drugie. Winowajcą jest to, że foo i bar są równe domyślnemu parametrowi. To ten sam obiekt słownikowy.
assert
foois
bar
poprawką jest ustawienie domyślnej wartości argumentu słowa kluczowego na None, a następnie udokumentowanie zachowania w docstringu funkcji.
def
decode(data, default=
None
):"""Load JSON data from a string.
Args:
data: JSON data to decode.
default: Value to return if decoding fails.
Defaults to an empty dictionary.
"""
if
defaultis None
: default=
{}try
:return
json.loads(data)except
ValueError:return
default
teraz uruchomienie tego samego kodu testowego co poprzednio daje oczekiwany wynik.
foo=
decode('bad data'
)foo=
5
bar=
decode('also bad'
)bar=
1
'Foo:'
, foo)'Bar:'
, bar)>>>Foo: {'stuff': 5}Bar: {'meep': 1}
rzeczy do zapamiętania
- domyślne argumenty są obliczane tylko raz: podczas definiowania funkcji w czasie ładowania modułu. Może to powodować dziwne zachowania dla wartości dynamicznych (takich jak {} lub ).
- używa None jako domyślnej wartości dla argumentów słów kluczowych, które mają wartość dynamiczną. Dokumentuj rzeczywiste zachowanie domyślne w docstring funkcji.
pozycja 21: wymuszanie przejrzystości za pomocą argumentów tylko dla słów kluczowych
przekazywanie argumentów za słowami kluczowymi jest potężną cechą funkcji Pythona (patrz pozycja 19: “podaj opcjonalne zachowanie za pomocą argumentów kluczowych”). Elastyczność argumentów słów kluczowych umożliwia pisanie kodu, który będzie jasny dla Twoich przypadków użycia.
na przykład, powiedzmy, że chcesz podzielić jedną liczbę przez drugą, ale bądź bardzo ostrożny w szczególnych przypadkach. Czasami chcesz zignorować wyjątki ZeroDivisionError i zamiast tego zwrócić nieskończoność. Innym razem, chcesz zignorować wyjątki OverflowError i zamiast tego zwrócić zero.
def
safe_division(number, divisor, ignore_overflow, ignore_zero_division):try
:return
number/
divisorexcept
OverflowError:if
ignore_overflow:return 0
else
:raise
except
ZeroDivisionError:if
ignore_zero_division:return
float
('inf'
)else
:raise
Korzystanie z tej funkcji jest proste. To wywołanie zignoruje float overflow z division i zwróci zero.
result=
safe_division(1
,10
**
500
,True
,False
)
to wywołanie zignoruje błąd dzielenia przez zero i zwróci nieskończoność.
result=
safe_division(1
,0
,False
,True
)
problem polega na tym, że łatwo jest pomylić pozycję dwóch argumentów logicznych, które kontrolują zachowanie ignorujące wyjątki. Może to łatwo spowodować błędy, które są trudne do wyśledzenia. Jednym ze sposobów poprawy czytelności tego kodu jest użycie argumentów słów kluczowych. Domyślnie funkcja może być zbyt ostrożna i zawsze może ponownie podnosić wyjątki.
def
safe_division_b(number, divisor, ignore_overflow=
False
, ignore_zero_division=
False
):# ...
wtedy osoby wywołujące mogą użyć argumentów słów kluczowych, aby określić, które z ignorowanych FLAG chcą odwrócić dla określonych operacji, nadpisując domyślne zachowanie.
safe_division_b(1
,10
**
500
, ignore_overflow=
True
)safe_division_b(1
,0
, ignore_zero_division=
True
)
problem polega na tym, że ponieważ te argumenty kluczowe są opcjonalnym zachowaniem, nic nie zmusza wywołujących twoje funkcje do używania argumentów kluczowych dla jasności. Nawet z nową definicją safe_division_b, nadal można nazwać ją starą metodą z pozycyjnymi argumentami.
safe_division_b(1
,10
**
500
,True
,False
)
przy złożonych funkcjach takich jak ta, lepiej jest wymagać, aby rozmówcy mieli jasność co do swoich zamiarów. W Pythonie 3 możesz żądać jasności, definiując swoje funkcje za pomocą argumentów tylko dla słów kluczowych. Argumenty te mogą być dostarczone tylko przez słowo kluczowe, nigdy przez pozycję.
tutaj zmieniam definicję funkcji safe_division, aby akceptowała argumenty tylko dla słów kluczowych. Symbol * na liście argumentów wskazuje koniec argumentów pozycyjnych i początek argumentów tylko słów kluczowych.
def
safe_division_c(number, divisor,*
, ignore_overflow=
False
, ignore_zero_division=
False
):# ...
teraz wywołanie funkcji z pozycyjnymi argumentami dla słów kluczowych arguments nie będzie działać.
safe_division_c(1
,10
**
500
,True
,False
)>>>TypeError: safe_division_c() takes 2 positional arguments but 4 were given
argumenty słów kluczowych i ich domyślne wartości działają zgodnie z oczekiwaniami.
safe_division_c(1
,0
, ignore_zero_division=
True
)# OK
try
: safe_division_c(1
,0
)except
ZeroDivisionError:pass
# Expected
argumenty tylko dla słów kluczowych w Pythonie 2
Niestety, Python 2 nie ma jawnej składni do określania argumentów tylko dla słów kluczowych, takich jak Python 3. Ale można osiągnąć to samo zachowanie podnosząc TypeErrors dla nieprawidłowych wywołań funkcji za pomocą operatora * * na listach argumentów. Operator * * jest podobny do operatora * (patrz punkt 18: “Reduce Visual Noise with Variable Positive Arguments”), z tą różnicą, że zamiast przyjmować zmienną liczbę argumentów pozycyjnych, akceptuje dowolną liczbę argumentów słów kluczowych, nawet jeśli nie są one zdefiniowane.
# Python 2
def
print_args(*
args,**
kwargs):'Positional:'
, args'Keyword: '
, kwargsprint_args(1
,2
, foo=
'bar'
, stuff=
'meep'
)>>>Positional: (1, 2)Keyword: {'foo': 'bar', 'stuff': 'meep'}
aby safe_division pobierało argumenty tylko dla słów kluczowych w Pythonie 2, masz funkcję accept **kwargs. Następnie wstawiasz argumenty kluczowe, których oczekujesz ze słownika kwargs, używając drugiego argumentu metody pop, aby określić domyślną wartość, gdy brakuje klucza. Na koniec upewnij się, że w kwargs nie ma już argumentów słów kluczowych, aby uniemożliwić wywołującym dostarczanie argumentów, które są nieprawidłowe.
# Python 2
def
safe_division_d(number, divisor,**
kwargs): ignore_overflow=
kwargs.pop('ignore_overflow'
,False
) ignore_zero_div=
kwargs.pop('ignore_zero_division'
,False
)if
kwargs:raise
TypeError('Unexpected **kwargs: %r'
%
kwargs)# ...
teraz możesz wywołać funkcję z argumentami kluczowymi lub bez nich.
safe_division_d(1
,10
)safe_division_d(1
,0
, ignore_zero_division=
True
)safe_division_d(1
,10
**
500
, ignore_overflow=
True
)
próba przekazania argumentów tylko dla słów kluczowych przez pozycję nie zadziała, tak jak w Pythonie 3.
safe_division_d(1
,0
,False
,True
)>>>TypeError: safe_division_d() takes 2 positional arguments but 4 were given
próba przekazania nieoczekiwanych argumentów słów kluczowych również nie zadziała.
safe_division_d(0
,0
, unexpected=
True
)>>>TypeError: Unexpected **kwargs: {'unexpected': True}
rzeczy do zapamiętania
- argumenty kluczowe sprawiają, że intencja wywołania funkcji jest bardziej jasna.
- używa argumentów tylko dla słów kluczowych, aby zmusić wywołujących do podania argumentów kluczowych dla potencjalnie mylących funkcji, zwłaszcza tych, które akceptują wiele flag logicznych.
- Python 3 obsługuje jawną składnię tylko dla słów kluczowych argumentów w funkcjach.
- Python 2 może emulować argumenty tylko dla słów kluczowych dla funkcji, używając **kwargs i ręcznie podnosząc wyjątki TypeError.