Pozičních argumentů nemusí být dopředu známý počet – všechny přebytečné „schroustne“ v podobě n-tice speciální argument s operátorem *, který způsobí rozbalení předaných hodnot do n-tice:
def f(arg1, arg2, *args):
print( "Argument 1: ", arg1 )
print( "Argument 2: ", arg2 )
print( "Následující argumenty: ", args )
>>> f( 1, 2, 3, 4, 5 )
Argument 1: 1
Argument 2: 2
Následující argumenty: (3, 4, 5)
Uvedenou funkci f() můžete zavolat pouze se dvěma argumenty, args je potom prázdná n-tice (). Ovšem alespoň dva argumenty jsou pochopitelně vyžadovány.
Podobně jako u pozičních argumentů můžeme použít „zkratku“ také pro získání blíže neurčeného počtu argumentů pojmenovaných. Jelikož ale nyní rozbalujeme dvojice klíč – hodnota, místo jedné hvězdičky použijeme operátor **, který rozbalí předané hodnoty do slovníku:
def f(arg1, arg2, **kwargs):
print( "Argument 1: ", arg1 )
print( "Argument 2: ", arg2 )
print( "Následující argumenty: ", kwargs )
>>> f( 1, 2, jedna=1, dvě=2, tři=3 )
Argument 1: 1
Argument 2: 2
Následující argumenty: {'tři': 3, 'dvě': 2, 'jedna': 1}
Uvedenou funkci f() můžete zavolat pouze se dvěma argumenty, kwargs je potom prázdný slovník {}. Ovšem alespoň dva argumenty jsou pochopitelně vyžadovány.
Kombinací operátorů * a ** můžete funkci na vstupu vnutit přímo n-tici, seznam nebo slovník:
def funkce(*args, **kwargs):
for arg in args:
print(arg)
for kwarg in kwargs:
print(kwarg, kwargs[kwarg])
>>> seznam = [1, 2, 3]
>>> slovnik = { 'jedna': 1, 'dvě': 2, 'tři': 3 }
>>> funkce( *seznam, **slovnik )
1
2
3
tři 3
dvě 2
jedna 1
Poznámka: Slovník se rozbalí na dvojce klíč – hodnota, takže klíč musí být řetězec (a když na to přijde, tak klidně i český, jsme v Python'u 3).
Pokud tento způsob předávání argumentů použijete, je zvykem používat právě uvedené názvy:
args – pro výslednou n-tici nepojmenovaných argumentů
kwargs – pro výsledný slovník pojmenovaných argumentů
Protože *args si – narozdíl od přiřazování více hodnot najednou – uzurpuje všechny zbývající poziční argumenty, musí být na konci. Cokoliv po něm musí být pojmenovaný (keyword-only) argument:
A opravdu stačí tento poslední parametr zavolat se jménem a vše bude v pořádku:
>>> f(1, 2, 3, 4, arg2=5)
1
(2, 3, 4)
5
Funkce s parametry můžeme volat mnoha rozdílnými způsoby:
funkce s konečným počtem pozičních argumentů f(arg1, arg2) voláme jejich úplným vyjmenováním;
Přitom můžeme použít stejná jména jako v definici funkce, ale za argumentem se jménem nesmí následovat argument beze jména.
funkce s neurčitým počtem pozičních argumentů f(*args) nemusí dostat argument žádný;
funkce pouze s pojmenovanými argumenty s výchozí hodnotou f(arg1=v1, arg2=v2) můžeme volat beze jmen i se jmény;
Přitom ale opět za argumentem se jménem nesmí následovat argument beze jména.
funkce s mixem konečného počtu pozičních a pojmenovaných argumentů f(arg1, arg2, arg3=v3, arg4=v4) můžeme volat jak pouze „pozičně“, tak i se jmény;
Za pojmenovanými samozřejmě už nesmí být žádný argument beze jména. Dlužno podotknout, že pro více jak dva argumenty, kde si člověk ještě snadno vybaví, co který znamená, je uvedení jmen podstatně čitelnější.
funkce s mixem neurčitého počtu pozičních argumentů a několika pojmenovaných argumentů f(arg1, arg2, *args, arg3, arg4) důsledně vyžadují, aby argumenty po *args byly volány jen a pouze jako pojmenované (a to i když nemají žádnou výchozí hodnotu).
Z předchozího je tedy zřejmé, že například funkci..
def compare(a, b, key=None):
…
..můžeme volat pouze pomocí pozičních argumentů – stačí uvést všechny tři argumenty a poslední bude automaticky přiřazen ke jménu key. Někdy však můžeme vyžadovat, aby poslední argument byl volán jen a pouze jako pojmenovaný. To zajistí uvedení speciálního argumentu * na příslušném místě:
def compare(a, b, *, key=None):
…
PS: Dříve by nám nezbylo nic jiného, než napsat cosi jako..
def compare(a, b, *ignore, key=None):
if ignore: # není-li „ignore“ prázdný
raise TypeError
…
..tedy použít argument proměnného počtu s nějakým konkrétním jménem *ignore (tzv. varargs), který zajistí, že argumenty po něm musí být pojmenované. Protože je zbytečné plácat na tuto signalizaci jméno (a kus kódu), zavedl trojkový Python zkratku v podobě argumentu *.
Volně podle PEP 3102.
V Python'u 3.8 přibyla (nějakou dobu chystaná) možnost i vynutit si volání vybraných parametrů jen a pouze jejich pozicí. Stačí do definice funkce na vhodné místo přidat klíčový znak /:
Předávání parametrů do funkcí není v Python'u tak jednoduché, jako jinde. Všechny parametry funkcí jsou sice předány odkazem, jenomže se tak chovají pouze při čtení a při zápisu do konkrétních prvků proměnných typů. Při zápise přímo do parametru – ať už neproměnného nebo proměnného typu – se začnou ihned tvářit jakoby předané hodnotou.
Nebo spíš by bylo lepší říct, že jakmile použijeme jméno parametru ve funkci na levé straně přiřazovacího příkazu, tak z něj okamžitě uděláme novou proměnnou lokální pro danou funkci. Zcela bez ohledu na to, čím byl předtím.
Jestli nějak, tak se tomuto podivnému mixu říká předávání objektem. Na dalších slajdech si ho ukážeme na příkladech.
Parametry neproměnných typů (namátkou čísla nebo řetězce) se zdánlivě tváří jako předávané hodnotou:
x = 3
def f(y):
y = 2 * y
print(y)
>>> f(x)
6
>>> print(x)
3
Logika chování při zavolání funkce f() je následující:
Nejdříve je vyhodnocena proměnná yna pravé straně přiřazovacího příkazu. Ta obsahuje hodnotu 3, kterou získala při volání funkce. (Jenže ve skutečnosti je to přímá reference na stejný objekt. Takže k žádnému kopírování hodnoty nedojde.)
Následně je zavedena nová lokální proměnnáy, do které je uložena příslušná spočítaná hodnota (tedy 6). (Ta už nemá s předchozí referencí na globálníx nic společného.)
Po ukončení vykonávání funkce jsou ale všechny lokální proměnné zapomenuty. Nepřekvapivě globálníx si stále drží svou původní hodnotu 3.
PS: Kdo nevěří, doplňte si do kódu výpisy id().
U parametrů proměnných typů (namátkou seznamy nebo slovníky) se to celé kapku komplikuje – parametry se sice opět předávají odkazem, nicméně to zůstane pravdou jenom do té doby, dokud se „přehrabujeme“ přímo v jejich prvcích:
x = [3]
def f(y):
y[0] = 2 * y
>>> f(x)
>>> print(x)
[3, 3]
Použitím y[0] si v těle funkce „sáhneme“ přímo do odkazovaného objektu, a proto je výsledek takový, jaký je. Kdybychom místo toho napsali např. y = 2 * y[0] nebo y = 2 * y, nestane se vůbec nic, protože tímto přiřazením uvnitř funkce převedeme referenci známého lokálního jména (argumentu funkce) na jiný objekt (samozřejmě až poté, co pro výpočet na pravé straně přiřazení využijeme nejdříve referenci původní):
x = [3]
def f(x):
x = 2 * x[0]
>>> f(x)
>>> print(x)
[3]
A samozřejmě je nepřekvapivě x po provedení x = 2 * x[0] již celým číslem a nikoli původně předaným seznamem.
Ze stejného důvodu se zde vyhneme trablům s UnboundLocalError – jméno x je totiž vstupním parametrem funkce, jen jsme ho „prostě“ z odkazu na vnější proměnný objekt přereferencovali na objekt vnitřní.
Na první pohled stejně podivně se chovají výchozí hodnoty parametrů – vyhodnocují se totiž pouze při prvním volání funkce, což má poněkud překvapivé důsledky, je-li jejich výchozí hodnotou proměnný (mutable) typ:
Při prvním zavolání funkce f bude proměnná xs nastavena jako nálepka na místo paměti vyhrazené proměnnému datovému typu seznam. Při každém volání funkce bude ovšem obsah tohoto místa v paměti upraven.
Navíc se uvedené chování projeví až ve chvíli, kdy funkci poprvé zavoláte bez tohoto parametru – do té doby bude totiž spořádaně přebírat hodnoty (či odkazy), které jí pošlete.
Pokud výše uvedené chování zrovna není to, co opravdu chceme, tak nejspíš nebudeme chvíli vůbec vědět, která bije. Odpovídající řešení v tomto případě je přepsat funkci následujícím způsobem:
Nyní pokaždé, když bude funkce f zavolána bez druhého parametru, bude tento pozitivně otestován na hodnotu None a tudíž mu bude pokaždé vždy znovu přiřazena lokální hodnota [].
PS: Místo příkazu if můžete psát..
xs = xs or []
..což využívá zkráceného vyhodnocování podmínek. Na jednu stranu je to možná elegantnější, na druhou tím asi docela ztížíte čtení kódu.
Tahle „podivnost“ s vyhodnocením výchozích hodnot parametrů proměnného typu pouze napoprvé má ale i své bezvadné užití:
Jsou-li vstupními parametry funkce hešovatelné typy, může si funkce při každém volání postupně budovat slovník vstupů a odpovídajících výstupů a natrefí-li znovu na stejné vstupy, může místo třeba-zrovna-velmi-náročného výpočtu použít již dříve získaný výsledek.
Jednoduchý příklad:
def fn(x, xs={}):
if x not in xs:
xs[x] = x**3
return xs[x]
PS: Pravda, Python na to má i vestavěný dekorátor, který dělá to samé (a ještě něco navíc), ale pamatujte si, že máte psát @functools.lru_cache.
Předchozí může mít docela velký dopad na rychlost kódu. Zatímco jména objektů ve funkci jsou „proskenována“ a připravena v tabulce místních jmen dopředu, parametry a tělo funkce se vyhodnucují úplně jinak:
Výchozí hodnoty pojmenovaných parametrů jsou poprvé a naposled vyhodnoceny, když je funkce zavolána bez jejich explicitního uvedení.
Tělo funkce je vykonáno při každém volání znovu.
Voláte-li tudíž funkci mnohokrát, je setsakra rozdíl, jestli nějakou (potenciálně dosti náročnou) strukturu zavádíte v rámci pojmenovaného parametru nebo až v těle funkce.
Například u funkce, která vyhodnocovala regexp. Pokud se kompiloval až v těle funkce, byl příslušný kód dvakrát pomalejší.