Argumenty funkce lze také vkládat různými způsoby. Mějme funkci,
>>> def foo(x,y=0):
... return x-y
ve které je „difóltně“ nastavena hodnota
0 pro proměnnou
y. Po zavolání
>>> foo(3,1)
je však hodnota
0 u
y přepsána na
1 a funkce vrátí hodnotu
2. Po zavolání
>>> foo(3)
funkce vrátí hodnotu
3, protože druhá pozice v závorce je nezadaná a bere tedy
y jako nezadané a vezme jeho „difóltní“ hodnotu
0. Dodržení pořadí argumentů není nutné dodržovat, je ale nutné při volání funkce říct, která je která. Zavolání funkce
>>> foo(y=1,x=3)
vypíše tedy hodnotu
2. V
Pythonu je všechno objekt, včetně proměnných a funkcí samotných. Funkce pracují s objekty, takže nejen proměnné, ale i samotné funkce mohou být lokálně definovány v těle procedury se všemi výše popsanými vlastnostmi, jak je vidět v dalším příkladu.
>>> def outer():
... x=1
... def inner():
... print x
... inner()
Po zavolání
>>> outer()
se vypíše hodnota
1 proměnné
x, přičemž
x a procedura
inner jsou následně zapomenuty. Nejdříve se v proceduře
outer lokálně nadefinuje
x, pak také lokálně procedura
inner a poté je
inner zavolána a provede se. Proměnná
x je globální vzhledem k
inner, ale lokální vzhledem k
outer. Dále, procedura
inner se vytváří pokaždé znovu, jakmile je procedura
outer opětovně zavolána. To je vcelku důležité v dalších příkladech. Zatím však ukázka klasického chování funkcí jako argumentů v jiných funkcích.
>>> def add(x,y):
... return x+y
>>> def sub(x, y):
... return x-y
>>> def apply(func,x, y):
... return func(x,y)
Procedura
add resp.
sub vrací součet resp. rozdíl dvou čísel. Mohou se samozřejmě použít jako argumenty jiné funkce nebo procedury, což umožňují i primitivnější programovací jazyky než
Python. Takže po zavolání
>>> apply(add, 2, 1)
resp.
>>> apply(sub, 2, 1)
vypadnou hodnoty
3 resp.
1. Nic nového pod sluncem. Co se však stane, když je jako hodnota vrácena samotná funkce, ne jen její hodnota?
>>> def outer():
... def inner():
... print "Inside inner"
... return inner
Stane se málo (na první pohled), ale jak je výše poznamenáno, vše v
Pythonu je objekt, včetně funkce či procedury, tak není důvod v rámci filozofie
Pythonu s nimi zacházet jinak než jako s jinými objekty, tj. čísly apod. Tzn. že by neměl být problém přiřadit takovýto „funkcionální“ výstup nějaké proměnné. Což samozřejmě problém není.
>>> foo=outer()
Avšak po zavolání
>>> foo
vypíše
Python hlášku, že jde o funkci. Teprve až po vypsání
>>> foo()
se provede přiřazená procedura
outer, tj. vypíše se text
Inside inner. Prostě a jednoduše, přiřazením procedury
outer proměnné
foo jsme vytvořili funkci
foo, proto ty závorky při volání proměnné
foo. Jinými slovy jsme funkci
inner uložili do proměnné
foo. Geniální! Tahle vlastnost umožňuje vygenerovat procedury třeba následovně.
>>> def outer(x):
... def inner():
... print x
... return inner
Nyní se přiřadí procedura
inner pro různé hodnoty
x různým proměnným.
>>> print1=outer(1)
>>> print2=outer(2)
A po jejich zavolání
>>> print1()
resp.
>>> print2()
vypadnou hodnoty
1 resp.
2. Neodborně řečeno, funkce
inner se „zakonzervovala“ do proměnných (funkcí, procedur)
print1 a
print2 vzhledem k lokální proměnné
x. Jak ale bylo zmíněno výše,
x je
lokální vzhledem k
outer, ale
globální vzhledem k
inner. Tahle vlastnost vložených funkcí je nějaký terminus technicus -
Closure. Je to však vlastnost, která spolu s klasickou vlastností použití funkce jako argumentu v jiné funkci, umožňuje upravovat tyto funkce v argumentu pomocí
inner v podstatě za chodu programu, prostě je ozdobit a hrát si s nimi -
dekorovat. Dekorovaná funkce vstoupí jako argument do procedury
outer, kde bude jako globální proměnná vzhledem k
inner, která ji zavolá a výsledek upraví -
dekoruje. Nakonec funkce
outer vrátí
inner jako funkci. Jako příklad si vezměme elipsu v rovině, kterou chceme natočit o libovolný úhel. Definice elipsy,
>>> from math import cos,sin
>>> def elipsa(u,coefs):
... rx,ry=1/coefs[0],1/coefs[1]
... x=rx*cos(u)
... y=ry*sin(u)
... return (x,y)
která pro
u z intervalu
(0, π) vrací souřadnice
x a
y elipsy o poloměrech
rx a
ry. Nyní tuto funkci dekorujeme tak, že funkce bude vracet hodnoty elipsy natočené o hodnotu
phi. Nejdříve ale dekorátor samotný.
>>> def natoceni(krivka,phi=0):
... mC=((cos(phi),-sin(phi)), \
... (sin(phi),cos(phi)))
... def inner(u,coefs):
... v=krivka(u,coefs)
... v1=(mC[0][0]*v[0]+mC[0][1]*v[1], \
... mC[1][0]*v[0]+mC[1][1]*v[1])
... return v1
... return inner
Jestliže napíšeme
>>> x,y=elipsa(0.0,(1.0,2.0))
>>> x,y
dostaneme pro
x a
y hodnoty
1.0 a
0.0. Nyní funkci
elipsa dekorujme, tedy
>>> from math import pi
>>> elipsa=natoceni(elipsa,pi)
a zavolejme si
elipsa znova, stejně jako předtím.
>>> x,y=elipsa(0.0,(1.0,2.0))
>>> x,y
Světe div se, z proměnných
x a
y vypadne
-1 a
0, což jsou souřadnice stejného bodu elipsy, ale otočené o úhel
π. Co se tedy stalo? Při
dekorování vstoupila funkce
krivka=elipsa jako parametr (a tedy globální proměnná z pohledu vnitřní funkce
inner) do funkce
natoceni spolu s proměnnou
phi=pi. Z výše napsaného plyne, že přiřazení dekorátoru
natočení opětovně funkci
elipsa způsobí „zakonzervování“ vnitřní funkce
inner vzhledem k těmto vstupním parametrům zpět do proměnné (funkce)
elipsa. Funkce
elipsa je teď vlastně předefinovaná. Když se nyní zavolá se stejnými parametry použitými u její nedekorované varianty, tedy
u=0.0 a
coefs=(1.0,2.0), převezme si tyto parametry funkce
inner, která si vyjádří souřadnice bodu elipsy
v pomocí původní funkce
elipsa, transformuje je pomocí matice
mC a vrátí jako
v1 a na řádku
return inner ji předá dekorované funkci
elipsa. Funkci lze dekorovat přímo při její definici pomocí symbolu.
>>> @dekorátor
... def funkce_k_dekorování():
... tělo funkce
V
Pythonu, podobně jako v jiných programovacích jazycích (umí to dokonce i
Fortran), je možné předávat funkci či proceduře i před samotnou definicí funkce neznámé množství parametrů. Parametr reprezentující místo, odkud je pozice parametrů a množství parametrů libovolná, je označen *, viz následující příklad.
>>> def one(*args):
... print args
Pokud se zavolá
>>> one()
vypíše se prázdná závorka (), pokud se zavolá
>>> one(1,2,3)
vypíše se
(1, 2, 3). Další způsob použití ukazuje následující příklad.
>>> def two(x,y,*args):
... print x,y,args
Zavoláním
>>> two(’a’,’b’,’c’)
se vypíše
a b (’c’,). Hvězdička
* před argumentem může být použita k rozbalení argumentu, jak ukazuje další příklad.
>>> def add(x,y):
... return x+y
Funkci je možné volat dvěma způsoby.
>>> lst=[1,2]
>>> add(lst[0],lst[1])
První je klasický a vyhodí číslo
3.
>>> add(*lst)
Druhý je pomocí hvězdičky
* a vypíše samozřejmě také
3. V případě slovníku se musí použít dvě hvězdičky.
>>> def foo(**kwargs):
... print kwargs
Po zavolání
>>> foo()
se vypíše
{}. Při zavolání ve tvaru
>>> foo(x=1,y=2)
se vypíše slovník
{’y’: 2, ’x’: 1}. A podobně jako u seznamu, i v tomto případě lze při předávání argumentu funkci použít dvě hvězdičky
** k rozbalení slovníku, jak ukazuje další příklad.
>>> dct={’x’:1,’y’:2}
>>> def bar(x,y):
... return x+y
Po zavolání funkce
bar pomocí argumentu s
** se dostane výsledek
x+y, tj.
3.
>>> bar(**dct)
Tato vlastnost libovolného množství argumentů se dá využít při
dekorování funkcí. Ono totiž nastavení fixovaného množství parametrů ve vnitřní funkci
inner dekorátoru je značně omezující a dekorátor je možné zobecnit následovně.
>>> def natoceni(krivka,phi=0):
... mC=((cos(phi),-sin(phi)), \
... (sin(phi),cos(phi)))
... def inner(*args,**kwargs):
... v=krivka(*args,**kwargs)
... v1=list(v)
... v1[0]=mC[0][0]*v[0]+mC[0][1]*v[1]
... v1[1]=mC[1][0]*v[0]+mC[1][1]*v[1]
... return v1
... return inner
Tímto způsobem je možné
dekorovat (natočit) nejen elipsu
elipsa tak, jak je uvedeno výše, ale třeba i elipsoid, který může být definován následovně.
>>> def elipsoid(u,v,coefs):
... rx,ry,rz=1/coefs[0],1/coefs[1],1/coefs[2]
... x=rx*cos(u)*sin(v)
... y=ry*sin(u)*sin(v)
... z=rz*cos(v)
... return (x,y,z)
Funkce
elipsoid má oproti funkci
elipsa o argument
v navíc. To však nevadí, i tak ji můžeme
dekorovat pomocí dekorátoru
natoceni, protože jeho funkce
inner má jako argumenty hvězdičkované
*args a
**kwargs. To znamená, že dekorování funkce,
>>> elipsa=natoceni(elipsa,pi/2.)
po jejím zavolání
>>> x,y=elipsa(0.0,(1.0,2.0))
>>> x,y
místo původního (nedekorovaného) bodu
x=1.0 a
y=0.0 vypíše
x=0.0 a
y=1.0. Tedy otočí elipsu o
π ⁄ 2. A stejně tak se může stejným dekorátorem dekorovat funkce
elipsoid, i když s jiným množstvím argumentů.
>>> elipsoid=natoceni(elipsoid,pi/2.)
A jeho zavoláním,
>>> x,y,z=elipsoid(0.0,pi/2.0,(1.0,2.0,3.0))
>>> x,y,z
vrátí místo původních (nedekorovaných) souřadnic bodu elipsoidu
x=1.0,
y=0.0 a
z=0.0 hodnoty souřadnic bodu otočeného v rovině
xy o úhel
π ⁄ 2, tj.
x=0.0,
y=1.0 a
z=0.0.
To je v podstatě vše. Dekorování funkcí je vcelku zbytečná záležitost, člověk se bez ní v pohodě obejde. Zpestřuje však Python a dělá ho zase o něco zábavnější.