Python *args i **kwargs wyjaśnione: elastyczne funkcje z przejrzystymi przykładami

  1. Strona główna
  2. Blog
  3. Rozwój oprogramowania

Rozwój oprogramowania

Python *args i **kwargs wyjaśnione: Elastyczne funkcje z przejrzystymi przykładami

Te gwiazdy w sygnaturach funkcji są mniej tajemnicze, niż na to wyglądają. Dowiedz się, jak *args i **kwargs zbierają argumenty, jak te same gwiazdy je rozpakowują i jak projektować sygnatury funkcji, które trudno będzie nadużyć.

8 min czytania
Wyjaśnienie Pythona *args i **kwargs: elastyczne funkcje z przejrzystymi przykładami

Najważniejsze

  • *args zbiera dodatkowe argumenty pozycyjne w krotce.
  • **kwargs zbiera dodatkowe argumenty słów kluczowych w słowniku.
  • Nazwy są jedynie konwencją, ponieważ znaczenie mają * i **.
  • Te same gwiazdki rozpakowują listę lub słownik podczas wywoływania funkcji.

Wcześniej czy później przeczytasz funkcję taką jak def handler(*args, **kwargs) i będziesz się zastanawiać, co akceptuje. Python *args i **kwargs pozwalają funkcji przyjmować elastyczną liczbę argumentów. Obsługują dekoratory, opakowania i wiele interfejsów API bibliotek.

W tym przewodniku opisano, jak działają one w obu kierunkach, a także nowsze sposoby, w jakie Python pozwala kontrolować sposób wywoływania funkcji. Każdy przykład uruchomiliśmy w Pythonie 3.13 i pokazane dane wyjściowe pochodzą z tego uruchomienia.

Co robi *args

Parametr zapisany jedną gwiazdką zbiera dodatkowe argumenty pozycyjne w krotce:

def total(*args):
    print(type(args).__name__, args)
    return sum(args)

print(total(1, 2, 3))
# tuple (1, 2, 3)
# 6

print(total())
# tuple ()
# 0

Bez argumentów otrzymujesz po prostu pustą krotkę, więc funkcja nadal działa. Nazwa args jest konwencją. *numbers zachowywałby się tak samo, a bardziej opisowa nazwa jest często lepsza.

Co robi **kwargs

Dwie gwiazdki gromadzą dodatkowe argumenty słów kluczowych w słowniku:

def describe(**kwargs):
    print(type(kwargs).__name__, kwargs)

describe(name="Asha", city="Pune")
# dict {'name': 'Asha', 'city': 'Pune'}

Ponieważ jest to normalny słownik, możesz przeglądać go w pętli, szukać kluczy za pomocą get() i przekazywać go dalej. Klucze zachowują kolejność, w jakiej zostały przekazane.

Kolejność parametrów

Kiedy mieszasz rodzaje parametrów, Python wymaga określonej kolejności:

  1. Regularne parametry pozycyjne
  2. *argumenty
  3. Parametry dotyczące tylko słów kluczowych
  4. **kwargs
def order(item, qty=1, *extras, priority=False, **notes):
    print(item, qty, extras, priority, notes)

order("pen")
# pen 1 () False {}

order("pen", 2, "gift wrap", "card", priority=True, note="leave at door")
# pen 2 ('gift wrap', 'card') True {'note': 'leave at door'}

Uwaga priority. Ponieważ pojawia się po *extras, można go ustawić tylko według nazwy, więc przypadkowy argument pozycyjny nigdy nie mógłby ustawić go przez przypadek. Wcześniejsze umieszczenie **kwargs jest niedozwolone, a nasz test zwrócił SyntaxError: ​​„argumenty nie mogą następować po argumencie słowa kluczowego var”.

Rozpakowywanie podczas dzwonienia

Te same gwiazdy działają odwrotnie w miejscu połączenia. Jedna gwiazdka dzieli listę lub krotkę na argumenty pozycyjne, a dwie gwiazdki dzielą słownik na argumenty oparte na słowach kluczowych:

nums = [4, 5, 6]
total(*nums)            # same as total(4, 5, 6) -> 15

settings = {"name": "Ben", "city": "Delhi"}
describe(**settings)    # same as describe(name="Ben", city="Delhi")

Rozpakowywanie działa z dowolną funkcją, nie tylko z tymi, które korzystają z *args i **kwargs. Jest to przydatne, gdy konfiguracja pojawia się w postaci słownika:

def connect(host, port, timeout=5):
    return f"{host}:{port} (timeout {timeout})"

connect(**{"host": "db.local", "port": 5432})
# 'db.local:5432 (timeout 5)'

connect(**{"host": "db.local", "port": 5432, "ssl": True})
# TypeError: connect() got an unexpected keyword argument 'ssl'

Ten błąd jest przydatny. Funkcja bez **kwargs odrzuca nieznane opcje, więc literówka w nazwie ustawienia kończy się głośnym niepowodzeniem, zamiast być dyskretnie ignorowana.

Argumenty zawierające wyłącznie słowa kluczowe

Pusta * w podpisie, bez nazwy, oznacza, że „wszystko po tym musi być przekazane za pomocą słowa kluczowego”:

def make_user(name, *, admin=False):
    return name, admin

make_user("Asha", admin=True)
# ('Asha', True)

make_user("Asha", True)
# TypeError: make_user() takes 1 positional argument but 2 were given

To świetny sposób na ochronę flag boolowskich. Wywołanie takie jak make_user("Asha", True) zmusza czytelników do odgadnięcia, co oznacza True. Wymaganie admin=True sprawia, że ​​każde wywołanie jest oczywiste.

Argumenty wyłącznie pozycyjne

Od wersji Python 3.8 / oznacza parametry, które mogą być przekazywane tylko przez pozycję:

def area(width, height, /):
    return width * height

area(3, 4)                 # 12
area(width=3, height=4)
# TypeError: area() got some positional-only arguments
#   passed as keyword arguments: 'width, height'

Dzięki temu biblioteka może później zmienić nazwy tych parametrów bez łamania niczyjego kodu, ponieważ nikt nie może polegać na nazwach. Kilka wbudowanych funkcji działa w ten sposób. Na przykład len(obj=[1]) nie działa.

Wskazówka: Pełny wzór to def f(pos_only, /, normal, *, kw_only). Rzadko potrzebujesz wszystkich trzech, ale wiedza, co oznaczają / i *, pomaga w czytaniu dokumentacji i podpisów bibliotek.

Dekoratory i opakowania

Jest to główny powód, aby używać obu razem. Opakowanie często musi zaakceptować dowolne argumenty oryginalnej funkcji i przekazać je bez zmian:

import functools, time

def timed(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@timed
def slow_add(a, b, delay=0.01):
    time.sleep(delay)
    return a + b

print(slow_add(2, 3, delay=0.02))
# slow_add took 0.0205s
# 5

Opakowanie w ogóle nie musi znać parametrów slow_add, więc działa dla dowolnej funkcji. Dane dotyczące czasu będą się nieznacznie różnić. Nasz przewodnik po dekoratorach Pythona wyjaśnia functools.wraps i inne szczegóły.

Opcje łączenia

** działa również w dosłownym słowniku, co daje fajny sposób na zastosowanie nadpisań nad wartościami domyślnymi:

defaults = {"theme": "light", "lang": "en"}

def build(**overrides):
    return {**defaults, **overrides}

build(theme="dark")
# {'theme': 'dark', 'lang': 'en'}

Późniejsze klucze wygrywają, więc nadpisanie zastępuje ustawienie domyślne. Podklasy również używają tego wzorca, jak w super().__init__(*args, **kwargs), aby przekazywać dodatkowe argumenty do klasy nadrzędnej. Zobacz Klasy i obiekty Pythona, aby uzyskać więcej informacji na temat super().

Ściągawka

Składnia Gdzie Znaczenie
def f(*args) Definicja Zbierz dodatkowe argumenty pozycyjne w krotce
zdecydowanie f(**kwargs) Definicja Zbierz dodatkowe argumenty słów kluczowych w dyktacie
zdecydowanie f(a, *, b) Definicja b dotyczy wyłącznie słów kluczowych
zdecydowanie f(a, /, b) Definicja a ma wyłącznie charakter pozycyjny
f(*items) Zadzwoń Rozłóż sekwencję na argumenty pozycyjne
f(**options) Zadzwoń Rozłóż dyktando na argumenty słów kluczowych
{**a, **b} Dosłowne Połącz słowniki
[*a, *b] Dosłowne Połącz iteracje w listę

Kiedy ich unikać

Elastyczność ma swoją cenę. Funkcja zdefiniowana jako def create(**kwargs) nie mówi czytelnikom, które opcje istnieją, a redaktorzy nie mogą ich automatycznie uzupełniać. Literówki mogą po cichu przejść przez kilka warstw, zanim cokolwiek zawiedzie.

Preferuj więc jawne parametry, jeśli wiesz, czego potrzebuje funkcja. Użyj *args i **kwargs, jeśli naprawdę potrzebujesz elastyczności: opakowania, dekoratory, przekazywanie argumentów do innej funkcji lub prawdziwe dane wejściowe o zmiennej długości, takie jak total(*numbers). Jeśli funkcja wymaga wielu nazwanych opcji, dataclass może być również jaśniejsze.

Typowe błędy

Używanie zmiennej domyślnej

Nie chodzi tu o gwiazdy, ale często pojawia się w ich pobliżu. Wartość domyślna tworzona jest jednorazowo podczas definiowania funkcji, a następnie udostępniana pomiędzy wywołaniami:

def bad(items=[]):
    items.append(1)
    return items

print(bad(), bad())
# [1, 1] [1, 1]

Obydwa wywołania zwróciły tę samą listę, która zawierała już dwie pozycje. Użyj items=None i utwórz nową listę wewnątrz funkcji.

Inne błędy

  • Zapominam rozpakować. Dzwoniąc do total(nums) przekazujemy jedną listę, a nie trzy numery. Napisz total(*nums).
  • Połykanie literówek. Funkcja, która akceptuje **kwargs, ale ignoruje nieznane klucze, ukrywa błędnie napisane opcje. Zweryfikuj je lub unikaj **kwargs.
  • Dwukrotne przekazanie tego samego argumentu. f(1, **{"a": 2}), gdzie a jest pierwszym parametrem, powoduje błąd „wiele wartości”.
  • Brak przekazywania w opakowaniu. Wywołanie func(args, kwargs) przekazuje krotkę i dyktando. Poprawna forma to func(*args, **kwargs).

Wpisz wskazówki dla *args i **kwargs

Wskazówki dotyczące typów *args i **kwargs opisują każdą pojedynczą wartość, a nie całą kolekcję. To zaskakuje wiele osób:

def total(*prices: float) -> float:
    return sum(prices)      # prices is tuple[float, ...]

def tag(**attrs: str) -> str:
    return " ".join(f'{k}="{v}"' for k, v in attrs.items())   # dict[str, str]

total(1.5, 2.5)                  # 4.0
tag(id="x", role="button")       # id="x" role="button"

Tutaj prices jest traktowane jako krotka liczb zmiennoprzecinkowych, a attrs jako słownik ciągów. Gdy argumenty słów kluczowych mają różne typy, w Pythonie 3.12 dodano precyzyjną opcję: a TypedDict w połączeniu z Unpack.

from typing import TypedDict, Unpack

class Options(TypedDict, total=False):
    timeout: float
    retries: int

def fetch(url: str, **opts: Unpack[Options]) -> None: ...

Narzędzie do sprawdzania typu, takie jak mypy lub Pyright, może następnie odrzucić fetch(url, retries="3") lub błędnie napisaną nazwę opcji, czego zwykły **kwargs nigdy nie jest w stanie zrobić. Zachowanie środowiska wykonawczego nie zmienia się jednak. Kiedy uruchomiliśmy fetch("u", retries="3") w Pythonie 3.13, wykonano go bez zarzutu, ponieważ sam Python nie wymusza wskazówek dotyczących typów.

Jeśli dokumentujesz długą listę akceptowanych kluczy dla **kwargs, rozważ zastąpienie ich wyraźnymi parametrami zawierającymi tylko słowa kluczowe. Redaktorzy mogą następnie automatycznie je uzupełniać, a błędy są natychmiast wychwytywane.

Podsumowanie

Pamiętaj o dwóch kierunkach. Z definicji gwiazdy zbierają. Podczas rozmowy rozpakowują się. Dodaj argumenty składające się wyłącznie ze słów kluczowych, aby wywołania były jaśniejsze, i zarezerwuj **kwargs dla kodu, który rzeczywiście przekazuje argumenty. Aby poćwiczyć, napisz mały dekorator, który będzie rejestrował argumenty każdego wywołania i użyj go w kilku różnych funkcjach.

Najczęstsze pytania

Jaka jest różnica między *args i **kwargs?

*args zbiera dodatkowe argumenty pozycyjne w krotce. **kwargs zbiera dodatkowe argumenty słów kluczowych w słowniku. Funkcja może używać obu razem.

Czy muszę nazywać je args i kwargs?

Nie. Gwiazdy wykonują swoją pracę, więc *wartości i **opcje są równie ważne. args i kwargs to po prostu powszechnie uznawana konwencja.

W jakiej kolejności powinny znajdować się *args i **kwargs?

Najpierw parametry zwykłe, potem *args, następnie parametry zawierające tylko słowa kluczowe, a na końcu **kwargs. Każde inne zamówienie powoduje błąd SyntaxError.

Co oznacza pojedynczy * w definicji funkcji?

Sam znak * oznacza, że ​​każdy parametr po nim musi zostać przekazany przez słowo kluczowe. Dzięki temu wywołania są wyraźniejsze, zwłaszcza w przypadku flag boolowskich.

Co oznacza/oznacza w funkcji Pythona?

Parametry przed / mają charakter wyłącznie pozycyjny, więc osoby wywołujące nie mogą przekazywać ich według nazwy. Ta funkcja została dodana w Pythonie 3.8.

Ucz się dalej

Pracuj z Stack Quora

Potrzebujesz pomocy przy testowaniu, WordPressie lub SEO?

Pomagamy zespołom dostarczać lepsze oprogramowanie i rozwijać ich witryny. Powiedz nam, nad czym pracujesz, a my zaproponujemy jasny następny krok.

Czytaj dalej

Więcej poradników na testowanie, sztuczna inteligencja i nowoczesny rozwój

Podobał Ci się ten artykuł? Wybierz temat i przejdź bezpośrednio do naszych najnowszych tutoriali.

Sztuczna inteligencja

Wyjaśnienie halucynacji AI: dlaczego modele językowe tworzą rzeczy i jak je zmniejszyć

Wyjaśniono halucynacje AI: czym są, dlaczego powstają w modelach językowych, typowe typy, jak je zredukować za pomocą uziemień i podpowiedzi oraz pr…

Przeczytaj artykuł →
DevOps

Wyjaśnienie zmiennych środowiskowych: pliki .env, sekrety i konfiguracja wykonana prawidłowo

Zmienne środowiskowe wyjaśnione na przykładach Pythona, które przeprowadziliśmy: ich odczytywanie i ustawianie, dlaczego każda wartość jest ciągiem …

Przeczytaj artykuł →
Tworzenie oprogramowania

Wyjaśnienie ograniczenia szybkości interfejsu API: algorytmy, nagłówki i obsługa błędów 429

Ograniczanie szybkości API wyjaśnione za pomocą przeprowadzonej przez nas symulacji: zasobnik tokenów, stałe okno, przesuwane okno, 429 odpowiedzi, …

Przeczytaj artykuł →
Automatyzacja testów

Wyśmiewanie w Pythonie: unittest.mock, patch i błędy, które sprawiają, że testy kłamią

Wyśmiewanie w Pythonie za pomocą unittest.mock, przetestowane: Mock i zwracana wartość, efekt uboczny, łatanie tam, gdzie używana jest nazwa, specyf…

Przeczytaj artykuł →
Testowanie oprogramowania

Wyjaśnienie zasięgu kodu: pokrycie linii i oddziału oraz co naprawdę oznacza 100%

Wyjaśnienie pokrycia kodu za pomocą prawdziwego pliku cover.py: pokrycie linii, gałęzi i warunków, przeczytanie raportu, dlaczego 100% pokrycia może…

Przeczytaj artykuł →
Bazy danych i SQL

DELETE vs TRUNCATE vs DROP w SQL: różnice, wycofywanie i kiedy używać każdego z nich

DELETE vs TRUNCATE vs DROP wyjaśnione za pomocą przetestowanego SQL: co każdy usuwa, wycofywanie, wyzwalacze, resetowanie tożsamości, prędkość i czy…

Przeczytaj artykuł →
Bazy danych i SQL

Wyjaśnienie widoków SQL: tworzenie, używanie i unikanie pułapek wraz z przykładami

Widoki SQL wyjaśnione za pomocą zapytań, które uruchomiliśmy: tworzenie widoku, wyniki na żywo, ukrywanie kolumn, wydajność, widoki z możliwością ak…

Przeczytaj artykuł →
Tworzenie oprogramowania

Interfejs TypeScript a typ: rzeczywiste różnice, testowane za pomocą kompilatora

Interfejs TypeScript a typ, sprawdzony z prawdziwym kompilatorem: co oba mogą zrobić, co tylko jeden, łączenie deklaracji, rozszerzenia a przecięcia…

Przeczytaj artykuł →
Tworzenie oprogramowania

Python *args i **kwargs wyjaśnione: elastyczne funkcje z przejrzystymi przykładami

Python *args i **kwargs wyjaśnione za pomocą uruchomionego przez nas kodu: pakowanie i rozpakowywanie, kolejność parametrów, argumenty zawierające t…

Przeczytaj artykuł →
Tworzenie oprogramowania

Funkcje Lambda w Pythonie: składnia, rzeczywiste zastosowania i kiedy ich nie używać

Funkcje lambda w Pythonie wyjaśnione za pomocą uruchomionego przez nas kodu: składnia, sortowanie za pomocą klucza, mapa i filtr, domknięcia, pułapk…

Przeczytaj artykuł →
Sztuczna inteligencja

Kim są agenci AI? Jak one działają, dzięki prostej pętli agenta, którą możesz uruchomić

Kim są agenci AI? Prosty przewodnik po pętli agenta, narzędziach, pamięci i poręczach, z małą pętlą w Pythonie, którą uruchomiliśmy, prawdziwymi zag…

Przeczytaj artykuł →
DevOps

Monitorowanie a obserwowalność: prawdziwa różnica z logami, wskaźnikami i śladami

Wyjaśnienie monitorowania a obserwowalności: znane i nieznane awarie, dzienniki, metryki i ślady, opis incydentu przy kasie, złote sygnały, OpenTele…

Przeczytaj artykuł →
DevOps

Woluminy platformy Docker a mocowania powiązań: utrwalanie danych kontenera we właściwy sposób

Wyjaśniono woluminy Dockera a montowanie powiązań: dlaczego dane kontenera znikają, nazwane woluminy, montowanie powiązań, tmpfs, przykłady tworzeni…

Przeczytaj artykuł →
Automatyzacja testów

TDD vs BDD: kluczowe różnice, jedna funkcja zbudowana w obie strony i kiedy używać każdej z nich

TDD vs BDD wyjaśnione za pomocą jednej funkcji rabatowej zbudowanej w obie strony: refaktoryzacja czerwono-zielona z uruchomionym przez nas kodem, s…

Przeczytaj artykuł →
Tworzenie oprogramowania

Git merge vs rebase: różnice, przykłady i kiedy czego używać

Git merge vs rebase wyjaśnione na prawdziwym wyniku logu, z tabelą porównawczą, złotą zasadą rebase i bezpiecznym procesem pracy zespołu.

Przeczytaj artykuł →
Tworzenie oprogramowania

Python list comprehension: składnia, przykłady i kiedy jej unikać

Python list comprehension wyjaśnione: składnia, filtrowanie, if-else, zagnieżdżone pętle, zmierzone czasy i sytuacje, w których zwykła pętla for jes…

Przeczytaj artykuł →
Bazy danych i SQL

SQL JOIN – wyjaśnienie z przykładami (Inner, Left, Right, Full)

Zrozum złączenia SQL krok po kroku na przykładowych tabelach i zapytaniach: INNER, LEFT, RIGHT, FULL, CROSS i SELF, plus typowe błędy i wskazówki wy…

Przeczytaj artykuł →