Python vrací více parametrů z funkce. Nespecifikovaný počet parametrů. Koncept rekurze, implementace v Pythonu

Připomeňme, že v matematice je faktoriál čísla n definován jako n! = 1 ⋅ 2 ⋅ ... ⋅ n. Například 5! = 1 ⋅ 2 ⋅ 3 ⋅ 4 ⋅ 5 = 120. Je jasné, že faktoriál lze snadno vypočítat pomocí cyklu for. Představme si, že v našem programu potřebujeme vypočítat faktoriál různá čísla několikrát (resp různá místa ah kód). Faktoriální výpočet můžete samozřejmě napsat jednou a poté jej pomocí Copy-Paste vložit kamkoli potřebujete.

# vypočítejme 3! res = 1 pro i v rozsahu (1, 4): res *= i print(res) # vypočítat 5! res = 1 pro i v rozsahu (1, 6): res *= i print(res)

Pokud se však jednou spleteme v počátečním kódu, pak se tato chyba objeví v kódu na všech místech, kam jsme zkopírovali výpočet faktoriálu. Každopádně kód zabere více místa než jsem mohl. Aby se zabránilo psaní stejné logiky znovu a znovu, programovací jazyky mají funkce.

Funkce jsou části kódu, které jsou izolované od zbytku programu a jsou prováděny pouze tehdy, když jsou volány. Funkce sqrt(), len() a print() jste již viděli. Všichni mají společný majetek: Mohou převzít parametry (nula, jedna nebo více) a mohou vrátit hodnotu (i když nemusí). Například funkce sqrt() přebírá jeden parametr a vrací hodnotu (kořen čísla). Funkce print() trvá variabilní číslo parametry a nevrací nic.

Pojďme si ukázat, jak napsat funkci factorial(), která vezme jeden parametr – číslo a vrátí hodnotu – faktoriál tohoto čísla.

Def faktorial(n): res = 1 pro i v rozsahu(1, n + 1): res *= i return res print(faktoriál(3)) print(faktoriál(5))

Uveďme několik vysvětlení. Nejprve musí být kód funkce umístěn na začátek programu, nebo spíše před místo, kde chceme použít funkci factorial(). První řádek tohoto příkladu je popis naší funkce. faktoriál je identifikátor, tedy název naší funkce. Za identifikátorem v závorce je seznam parametrů, které naše funkce přijímá. Seznam se skládá z identifikátorů parametrů oddělených čárkami. V našem případě se seznam skládá z jedné hodnoty n. Na konec řádku se umístí dvojtečka.

Následuje tělo funkce navržené jako blok, tedy odsazené. Uvnitř funkce se vypočítá faktoriál n a uloží se do proměnné res. Funkce končí příkazem return res, který funkci ukončí a vrátí hodnotu proměnné res.

Instrukce return se může objevit na libovolném místě funkce, její provedení funkci ukončí a vrátí specifikovaná hodnota na místo volání. Pokud funkce nevrátí hodnotu, použije se příkaz return bez návratové hodnoty. Funkce, které nepotřebují vracet hodnotu, nemusí mít příkaz return.

Uveďme další příklad. Napišme funkci max(), která vezme dvě čísla a vrátí z nich maximum (ve skutečnosti je taková funkce již zabudována v Pythonu).

10 20 def max(a, b): if a > b: return a else: return b print(max(3, 5)) print(max(5, 3)) print(max(int(input()), int(vstup())))

Nyní můžeme napsat funkci max3(), která vezme tři čísla a vrátí maximum z nich.

Def max(a, b): if a > b: return a else: return b def max3(a, b, c): return max(max(a, b), c) print(max3(3, 5, 4 ))

Vestavěný maximální funkce() v Pythonu může vzít proměnný počet argumentů a vrátit maximum z nich. Uveďme si příklad, jak lze takovou funkci zapsat.

Def max(*a): res = a pro val v a: if val > res: res = val return res print(max(3, 5, 4))

Všechny parametry předané této funkci budou shromážděny do jedné n-tice s názvem a, jak je označeno hvězdičkou v řádku deklarace funkce.

2. Lokální a globální proměnné

Uvnitř funkce můžete použít proměnné, které jsou deklarovány mimo funkci.

Def f(): print(a) a = 1 f()

Zde je proměnné a přiřazena hodnota 1 a funkce f() tuto hodnotu vypíše, i když tato proměnná není inicializována před deklarací funkce f. Když je volána f(), a je již přiřazena hodnota, takže f() ji může zobrazit na obrazovce.

Takové proměnné (deklarované mimo funkci, ale přístupné uvnitř funkce) jsou volány globální.

Ale pokud inicializujete proměnnou uvnitř funkce, nebudete moci tuto proměnnou použít mimo funkci. Například:

Def f(): a = 1 f() tisk(a)

Dostaneme chybu NameError: název "a" není definován . Takové proměnné deklarované uvnitř funkce jsou volány místní. Tyto proměnné se po ukončení funkce stanou nedostupnými.

Výsledek bude zajímavý, pokud se pokusíte změnit hodnotu globální proměnné uvnitř funkce:

Def f(): a = 1 tisk(a) a = 0 f() tisk(a)

Budou vytištěna čísla 1 a 0 I když se hodnota proměnné a uvnitř funkce změnila, mimo funkci zůstává stejná! To se provádí za účelem „ochrany“ globálních proměnných před náhodnými změnami funkce. Pokud je například funkce volána ze smyčky pomocí proměnné i a v této funkci bude pro organizaci smyčky použita také proměnná i, pak se tyto proměnné musí lišit. Pokud nerozumíte poslední větě, podívejte se na následující kód a přemýšlejte o tom, jak by to fungovalo, kdyby proměnná i byla změněna uvnitř funkce.

Def factorial(n): res = 1 pro i v rozsahu (1, n + 1): res *= i návrat res pro i v rozsahu (1, 6): print(i, "! = ", faktoriál(i) , sep="")

Pokud by se globální proměnná i změnila uvnitř funkce, dostali bychom toto:

5! = 1 5! = 2 5! = 6 5! = 24 5! = 120

Pokud je tedy uvnitř funkce upravena hodnota nějaké proměnné, pak se proměnná s tímto názvem stane lokální proměnnou a její modifikace nezmění globální proměnnou se stejným názvem.

Formálněji: interpret Pythonu považuje proměnnou za lokální pro danou funkci, pokud její kód obsahuje alespoň jednu instrukci, která hodnotu proměnné upravuje, pak je tato proměnná považována za lokální a nelze ji před inicializací použít. Instrukce, která upravuje hodnotu proměnné, jsou operátory = , += , stejně jako použití proměnné jako parametru pro smyčku. Navíc, i když instrukce modifikující proměnnou není nikdy provedena, interpret to nemůže zkontrolovat a proměnná je stále považována za lokální. Příklad:

Def f(): print(a) if False: a = 0 a = 1 f()

Dojde k chybě: UnboundLocalError: místní proměnná "a" odkazovaná před přiřazením. Konkrétně ve funkci f() se identifikátor a stává lokální proměnnou, protože funkce obsahuje příkaz, který modifikuje proměnnou a , i když není nikdy provedena (ale interpret to nemůže sledovat). Proto tisk proměnné a má za následek přístup k neinicializované lokální proměnné.

Aby funkce změnila hodnotu globální proměnné, je nutné tuto proměnnou uvnitř funkce deklarovat jako globální pomocí klíčové slovo globální:

Def f(): globální a a = 1 tisk(a) a = 0 f() tisk(a)

V tomto příkladu se na obrazovce zobrazí 1 1, protože proměnná a je deklarována jako globální a její změna uvnitř funkce vede k tomu, že proměnná bude dostupná mimo funkci.

Je však lepší neměnit hodnoty globálních proměnných uvnitř funkce. Pokud vaše funkce musí změnit nějakou proměnnou, bylo by lepší, kdyby tuto hodnotu vrátila a vy sami tuto hodnotu proměnné při volání funkce výslovně přiřadíte. Pokud dodržíte tato pravidla, funkce jsou nezávislé na kódu a lze je snadno zkopírovat z jednoho programu do druhého.

Řekněme například, že váš program potřebuje vypočítat faktoriál vstupního čísla, které pak chcete uložit do proměnné f. Takhle to je Nestojí to za to dělat:

5 def faktoriál(n): globální f res = 1 pro i v rozsahu(2, n + 1): res *= i f = res n = int(vstup()) faktoriál(n) # další akce s proměnnou f

Tento kód je špatně napsaný, protože je obtížné jej znovu použít. Pokud zítra potřebujete použít funkci faktoriálu v jiném programu, nemůžete tuto funkci jednoduše zkopírovat odtud a vložit ji do svého. nový program. Budete muset změnit způsob, jakým vrací vypočítanou hodnotu.

Mnohem lepší je tento příklad přepsat takto:

5 # začátek části kódu, kterou lze zkopírovat z programu do programu def factorial(n): res = 1 pro i v rozsahu (2, n + 1): res *= i return res # konec části kódu n = int(input()) f = faktoriál(n) # dále všemožné akce s proměnnou f

Pokud potřebujete, aby funkce nevracela jednu hodnotu, ale dvě nebo více, pak pro tento účel může funkce vrátit seznam dvou nebo více hodnot:

Pak lze výsledek volání funkce použít ve vícenásobném přiřazení:

3. Rekurze

Def short_story(): print("Kněz měl psa, miloval ji.") print("Snědla kus masa, on ji zabil,") print("Zakopal to do země a napsal nápis: ") krátký příběh()

Jak jsme viděli výše, funkce může volat jinou funkci. Ale funkce se může také volat sama! Podívejme se na to na příkladu pomocí funkce výpočtu faktoriálu. Je dobře známo, že 0!=1, 1!=1. Jak vypočítat hodnotu n! pro velké n? Pokud bychom dokázali vypočítat hodnotu (n-1)!, pak můžeme snadno vypočítat n!, protože n!=n⋅(n-1)!. Ale jak vypočítat (n-1)!? Pokud jsme počítali (n-2)!, pak můžeme počítat i (n-1)!=(n-1)⋅(n-2)!. Jak vypočítat (n-2)!? Pokud... Nakonec se dostaneme k hodnotě 0!, která se rovná 1. Pro výpočet faktoriálu tedy můžeme použít hodnotu faktoriálu pro menší číslo. To lze také provést v programu Python:

Def factorial(n): if n == 0: return 1 else: return n * factorial(n - 1) print(factorial(5))

Tato technika (funkce volající sama sebe) se nazývá rekurze a funkce samotná se nazývá rekurzivní.

Rekurzivní funkce jsou mocným mechanismem v programování. Bohužel nejsou vždy účinné. Také použití rekurze často vede k chybám. Nejběžnější z těchto chyb je nekonečná rekurze, kdy řetězec volání funkcí nikdy nekončí a pokračuje, dokud neskončí. volná paměť v počítači. Příklad nekonečné rekurze je uveden v epigrafu k této části. Dva nejčastější důvody pro nekonečnou rekurzi jsou:

  1. Nesprávný výstup z rekurze. Pokud například zapomeneme zkontrolovat, zda n == 0 v programu pro výpočet faktoriálu, pak faktorial(0) zavolá faktorial(-1), který zavolá faktorial(-2) atd.
  2. Rekurzivní volání s nesprávné parametry. Pokud například funkce factorial(n) zavolá faktorial(n) , výsledkem bude také nekonečný řetězec.

Proto při vývoji rekurzivní funkce musíte nejprve formalizovat podmínky pro ukončení rekurze a přemýšlet o tom, proč se rekurze vůbec ukončí.

Pojmenované funkce, instrukce def

Funkce v pythonu je objekt, který přebírá argumenty a vrací hodnotu. Funkce je obvykle definována pomocí příkazu def.

Pojďme definovat nejjednodušší funkci:

Příkaz return říká, že má vrátit hodnotu. V našem případě funkce vrací součet x a y.

Nyní to můžeme nazvat:

>>> přidat (1, 10)

>>> add("abc", "def")

Funkce může být libovolně složitá a může vracet libovolné objekty (seznamy, n-tice a dokonce i funkce!):

>>> def newfunc(n):

Def myfunc(x):

Návrat x + n

Return myfunk

>>> new = newfunc(100) # new je funkce

>>> nové (200)

Funkce nemusí končit příkazem return, ale funkce vrátí hodnotuNone:

>>> def func():

>>> tisk (func())

Funkční argumenty

Funkce může mít libovolný počet argumentů nebo vůbec žádné. Časté jsou také funkce s libovolným počtem argumentů, funkce s pozičními a pojmenovanými argumenty, povinné a nepovinné.

>>> def func(a, b, c=2): # c - volitelný argument

Vraťte a + b + c

>>> func(1, 2) # a = 1, b = 2, c = 2 (výchozí)

>>> func(1, 2, 3) # a = 1, b = 2, c = 3

>>> func(a=1, b=3) # a = 1, b = 3, c = 2

>>> func(a=3, c=6) # a = 3, c = 6, b nedefinováno

Zpětné sledování (poslední poslední hovor):

Soubor "", řádek 1, in

TypeError: func() vyžaduje alespoň 2 argumenty (2 dané)

Funkce může také mít proměnný počet pozičních argumentů, pak se před název umístí *:

>>> def func(*args):

>>> func(1, 2, 3, "abc")

(1, 2, 3, "abc")

>>> func(1)

Jak můžete vidět z příkladu, args je n-tice všech argumentů předávaných funkci a s proměnnou lze pracovat stejným způsobem jako s n-ticí.

Funkce může mít libovolný počet pojmenovaných argumentů, pak se před název umístí **:

>>> def func(**kwargs):

Vraťte kwargy

>>> func(a=1, b=2, c=3)

("a": 1, "c": 3, "b": 2)

>>> func(a="python")

V proměnné kwargs ukládáme slovník, se kterým si opět můžeme dělat, co chceme.

Anonymní funkce, instrukce lambda

Anonymní funkce mohou obsahovat pouze jeden výraz, ale také se provádějí rychleji. Anonymní funkce se vytvářejí pomocí instrukce lambda. Navíc nemusí být přiřazeny k proměnné, jak jsme to udělali s instrukcí def func():

>>> func = lambda x, y: x + y

>>> func(1, 2)

>>> func("a", "b")

>>> (lambda x, y: x + y) (1, 2)

>>> (lambda x, y: x + y)("a", "b")

Funkce lambda na rozdíl od běžných funkcí nevyžadují příkaz return, ale jinak se chovají úplně stejně:

>>> func = lambda *args: args

>>> func(1, 2, 3, 4)

19. Pojem rekurze, implementace v Pythonu

V programování je rekurze voláním funkce (procedury) ze sebe samé, přímo (jednoduchá rekurze) nebo prostřednictvím jiných funkcí (komplexní nebo nepřímá rekurze), například funkce volá funkci a funkce funkci. Počet vnořených volání funkce nebo procedury se nazývá hloubka rekurze.

Jednodušší to říct nemůže. Existuje známé rčení o rekurzi:

Chcete-li porozumět rekurzi, musíte nejprve porozumět rekurzi

Díky Pythonu je práce s rekurzí snadná a uvolněná. Úplně prvním příkladem rekurze, se kterým se většina programátorů setkává, je nalezení faktoriálu. Kód by mohl být takto:

def factorial(n):

pokud n<= 1: return 1

else: return n * faktoriál(n - 1)

Jak vidíte, příkaz if else jsme pro Python napsali trochu neobvyklým způsobem, ale to je povoleno v tomto případě Tedy s ohledem na to, že zde není zhoršená čitelnost, ale tento styl by se neměl zneužívat. A vůbec, PEP8 bude soudit každého. :)

Nyní si ujasněme pár důležitých funkcí, které byste měli mít při práci s rekurzemi vždy na paměti.

    Hloubka rekurze je omezena. Ve výchozím nastavení je to 1000.

    Chcete-li tento limit změnit, musíte zavolat funkci sys.setrecursionlimit() a zobrazit aktuální limit sys.getrecursionlimit().

    Navzdory tomu existuje omezení velikosti zásobníku, který je instalován operačním systémem.

    Rekurzi v Pythonu nelze použít ve funkcích generátoru a rutinách.

    Toto chování však můžete napravit, ale je lepší ne.

Poslední věc – použití dekorátorů na rekurzivní funkce může vést k neočekávaným výsledkům, takže buďte velmi opatrní při zdobení rekurzivních funkcí.

Také byste měli vždy definovat výstupní body z rekurzivních funkcí. Je to jako se smyčkami – nekonečná smyčka může skutečně plýtvat vaším operačním systémem. A nakonec, kde je lepší použít rekurzi a kde je lepší se zdržet a vystačit si například se smyčkami? Zde samozřejmě hodně záleží na úloze, ale vždy byste měli mít na paměti, že rekurze je několikrát pomalejší než smyčka. Python je navržen tak, aby vás volání funkce stálo hodně :) Obecně platí, že byste neměli volat funkce v cyklech, natož rekurzivní funkce.

Rekurze se dobře hodí tam, kde výkon není příliš důležitý, ale důležitější je čitelnost a udržovatelnost kódu. Například napište dvě funkce pro procházení stromem adresářů, jednu rekurzivní a jednu se smyčkami. Existuje velké množství publikací věnovaných implementaci konceptů funkcionálního programování v jazyk Python

, ale většinu tohoto materiálu napsal jeden autor – David Mertz. Navíc mnoho z těchto článků je již zastaralých a roztroušených po různých online zdrojích. V tomto článku se pokusíme znovu navštívit toto téma, abychom obnovili a uspořádali dostupné informace, zejména s ohledem na velké rozdíly, které existují mezi verzemi Pythonu na řádcích 2 a 3.

Funkce v Pythonu Funkce v Pythonu jsou definovány dvěma způsoby: prostřednictvím definice def nebo pomocí anonymního popisu lambda . Obě tyto definiční metody jsou v různé míře dostupné v některých jiných programovacích jazycích. Rysem Pythonu je, že funkce je pojmenovaný objekt stejně jako jakýkoli jiný objekt nějakého datového typu, řekněme celočíselná proměnná. Výpis 1 pořadů nejjednodušší příklad (soubor func.py z archivu

python_functional.tgz
#!/usr/bin/python # -*- kódování: utf-8 -*- import sys def show(fun, arg): print("() : ()".format(type(fun), fun))) print("arg=() => fun(arg)=()".format(arg, fun(arg))) if len(sys.argv) > 1: n = float(sys.argv[ 1 ]) else : n = float(input("číslo?: ")) def pow3(n): # 1. definice funkce return n * n * n show(pow3, n) pow3 = lambda n: n * n * n # 2 -th definice funkce se stejným názvem show(pow3, n) show((lambda n: n * n * n), n) # 3., použití definice anonymní funkce

Když zavoláme všechny tři funkční objekty, dostaneme stejný výsledek:

$ python func.py 1.3 : arg=1,3 => zábava(arg)=2,197 : at 0xb7662bc4> arg=1.3 => fun(arg)=2.197 : at 0xb7662844> arg=1.3 => fun(arg)=2.197

To je ještě výraznější v Pythonu verze 3, ve které je vše třída (včetně celočíselné proměnné) a funkce jsou programové objekty, které patří do třídy. funkce:

$python3 func.py 1.3 : arg=1,3 => zábava(arg)=2,19700000000000005 : at 0xb745432c> arg=1.3 => fun(arg)=2.1970000000000005 : at 0xb74542ec> arg=1.3 => fun(arg)=2.1970000000000005

Poznámka. Existují ještě 2 typy objektů, které umožňují volání funkce – metoda funkční třídy a funktor, o kterých si povíme později.

Pokud jsou funkční objekty Pythonu objekty stejně jako jiné datové objekty, můžete s nimi dělat vše, co můžete dělat s jakýmikoli daty:

  • dynamicky změna probíhá;
  • vkládat do složitějších datových struktur (kolekcí);
  • předat jako parametry a návratové hodnoty atd.

Na tomto (manipulace s funkčními objekty jako datovými objekty) je založeno funkcionální programování. Python samozřejmě není skutečný jazyk Funkcionální programování, takže pro plně funkční programování existují speciální jazyky: Lisp, Planner a novější: Scala, Haskell. Ocaml, ... Ale v Pythonu můžete „vložit“ techniky funkcionálního programování do obecného toku imperativního (příkazového) kódu, například použít metody vypůjčené z plnohodnotných funkční jazyky. Tito. „sbalit“ jednotlivé fragmenty imperativního kódu (někdy stačí velký objem) do funkčních výrazů.

Někdy se lidé ptají: "Jaké jsou výhody funkčního stylu psaní jednotlivých fragmentů pro programátora?" Hlavní výhodou funkčního programování je, že po jednorázovém odladění takového fragmentu nezpůsobí následné opakované použití chyby v důsledku vedlejších efektů spojených s přiřazením a konflikty jmen.

Poměrně často se při programování v Pythonu používají typické konstrukce z oblasti funkcionálního programování, například:

tisknout ([ (x,y) pro x v (1, 2, 3, 4, 5) \ pro y v (20, 15, 10) \ pokud x * y > 25 a x + y< 25 ])

V důsledku běhu získáme:

$ python funcp.py [(2,20), (2,15), (3,20), (3,15), (3,10), (4,20), (4,15), (4 ,10), (5,15), (5,10)]

Funguje jako objekty

Při vytváření funkčního objektu s operátorem nebo pomocí anonymního popisu, jak je uvedeno ve výpisu 1, můžete svázat vytvořený funkční objekt s názvem pow3 přesně stejným způsobem, jako by se dalo k tomuto jménu připojit číslo 123 nebo řetězec "Ahoj!". Tento příklad potvrzuje stav funkcí jako prvotřídních objektů v Pythonu. Funkce v Pythonu je jen další hodnota, se kterou můžete něco dělat.

Nejběžnější akcí prováděnou s prvotřídními funkčními objekty je jejich předání vestavěným funkcím vyššího řádu: mapa(), snížit() A filtr(). Každá z těchto funkcí má jako svůj první argument objekt funkce.

  • mapa() aplikuje předanou funkci na každý prvek v předaném seznamu (seznamech) a vrátí seznam výsledků (stejný rozměr jako vstup);
  • snížit() použije předávanou funkci na každou hodnotu v seznamu a na vnitřní úložiště výsledkem je např. snížit(lambda n,m: n * m, rozsah(1, 10)) prostředek 10! (faktoriální);
  • filtr() aplikuje předávanou funkci na každý prvek seznamu a vrátí seznam těch prvků původního seznamu, pro které předaná funkce vrátila hodnotu true.

Kombinací těchto tří funkcí můžete realizovat nečekané široký rozsahřídit operace toku bez použití imperativních příkazů, ale s použitím pouze výrazů funkčního stylu, jak je uvedeno ve výpisu 2 (soubor funcH.py func.py z archivu

Výpis 2. Funkce vyššího řádu v Pythonu
#!/usr/bin/python # -*- kódování: utf-8 -*- import sys def input_arg(): global arg arg = (lambda: (len(sys.argv) > 1 a int(sys.argv[ 1 ])) nebo \ int(input("číslo?: ")))() return arg print("argument = ()".format(input_arg())) print(list(map(lambda x: x + 1) , range(arg))) print(list(filter(lambda x: x > 4, range(arg)))) import functools print("()! = ()".format(arg, functools.reduce(lambda x , y: x * y, rozsah (1, arg))))

Poznámka. Tento kód je o něco složitější než předchozí příklad kvůli následujícím problémům s kompatibilitou mezi Pythonem verze 2 a 3:

  • Funkce snížit(), deklarovaný jako vestavěný v Pythonu 2, byl přesunut do modulu v Pythonu 3 functools a zavolání přímo jménem vyvolá výjimku NameError, takže pro správnou funkci musí být volání naformátováno jako v příkladu nebo musí obsahovat řádek: z importu functools *
  • Funkce mapa() A filtr() v Pythonu 3 nevracejí seznam (jak již bylo ukázáno při diskuzi o rozdílech verzí), ale objekty iterátoru formuláře:

Chcete-li získat celý seznam hodnot pro ně, zavolá se funkce seznam().

Tento kód tedy může fungovat v obou Verze Pythonu:

$python3 funcH.py 7 argument = 7 7! = 720

Pokud je kód přenosný mezi různé verze není vyžadováno, pak lze takové fragmenty vyloučit, což poněkud zjednoduší kód.

Rekurze

Ve funkcionálním programování je rekurze základním mechanismem, podobně jako smyčky v iterativním programování.

V některých diskuzích o Pythonu jsem se opakovaně setkal s tvrzeními, že v Pythonu je hloubka rekurze omezena „hardwarem“, a proto některé akce z principu nelze implementovat. Python má výchozí limit hloubky rekurze 1000, ale toto je číselné nastavení, které lze vždy přepsat, jak je znázorněno ve výpisu 3 (úplný příklad kódu naleznete v souboru fakt2.py func.py z archivu

Výpis 3. Výpočet faktoriálu s libovolnou hloubkou rekurze
#!/usr/bin/python # -*- kódování: utf-8 -*- import sys arg = lambda: (len(sys.argv) > 1 a int(sys.argv[ 1 ])) nebo \ int( input("číslo?: ")) faktoriál = lambda x: ((x == 1) a 1) nebo x * faktoriál(x - 1) n = arg() m = sys.getrecursionlimit() pokud n >= m - 1: sys.setrecursionlimit(n + 2) print("hloubka rekurze přesahuje hodnotu nastavenou v systému (), reset na ()".\ format(m, sys.getrecursionlimit())) print("n=( ) => n!=()".format(n, faktoriál(n))) if sys.getrecursionlimit() > m: print("hloubka rekurze obnovena na ()".format(m)) sys.setrecursionlimit(m )

Zde je návod, jak vypadá provedení tohoto příkladu v Pythonu 3 a Pythonu2 (i když ve skutečnosti se výsledné číslo pravděpodobně nevejde na jednu obrazovku terminálu konzoly):

$ python3 fact2.py 1001 hloubka rekurze překračuje systémovou sadu 1000, resetováno na 1003 n=1001 => n!=4027........................ . ........................0000000000000 hloubka rekurze obnovena na 1000

Několik jednoduchých příkladů

Proveďme několik jednoduchých transformací obvyklého imperativního kódu (příkaz, operátor), abychom převedli jeho jednotlivé fragmenty na funkční. Nejprve vyměníme operátory poboček logické podmínky, které díky „odloženým“ (líným, líným) výpočtům umožňují kontrolovat spouštění či nespouštění jednotlivých větví kódu. Takže imperativní konstrukce:

-li<условие>: <выражение 1>jiný:<выражение 2>

Zcela ekvivalentní následujícímu funkčnímu fragmentu (kvůli „odloženým“ schopnostem logických operátorů a A nebo):

# funkce bez parametrů: lambda: (<условие>a<выражение 1>) nebo (<выражение 2>)

Použijme jako příklad opět faktoriální výpočet. Výpis 4 ukazuje funkční kód pro výpočet faktoriálu (soubor fakt1.py v archivu z archivu v sekci "Stáhnout materiály"):

Výpis 4. Operátorová (imperativní) definice faktoriálu
#!/usr/bin/python # -*- kódování: utf-8 -*- import sys def factorial(n): if n == 1: return 1 else: return n * factorial(n - 1) if len( sys.argv) > 1: n = int(sys.argv[ 1 ]) else: n = int(vstup("číslo?: ")) print("n=() => n!=()".formát (n, faktoriál(n)))

Argument, který se má vypočítat, je odvozen z hodnoty parametru příkazový řádek(pokud existuje) nebo zadané z terminálu. První výše uvedená změna je již použita ve výpisu 2, kde byly nahrazeny funkční výrazy:

  • Definice faktoriálové funkce: faktoriál = lambda x: ((x == 1) a 1) nebo x * faktoriál(x - 1)
  • požadavek na zadání hodnoty argumentu z konzoly terminálu: arg = lambda: (len(sys.argv) > 1 a int(sys.argv[ 1 ])) nebo \ int(input("číslo?: ")) n = arg()

V souboru fakt3.py objeví se další definice funkce, vytvořená pomocí funkce vyššího řádu snížit():

faktoriál = faktoriál = lambda z: snížit(lambda x, y: x * y, rozsah(1, z + 1))

Zde také zjednodušíme výraz pro n, redukuje to na jediné volání anonymní (nejmenované) funkce:

n = (lambda: (délka(sys.argv) > 1 a int(sys.argv[ 1 ])) nebo \ int(vstup("číslo?: ")))()

Nakonec si můžete všimnout, že přiřazení hodnoty proměnné n nutné pouze pro jeho použití při hovoru tisk() pro výstup této hodnoty. Pokud toto omezení opustíme, celá aplikace se zvrhne v jeden funkční operátor (viz soubor fact4.py v archivu z archivu v sekci "Stáhnout materiály"):

from sys import * from functools import reduction print("computed factorial = ()".format(\ (lambda z: reduction(lambda x, y: x * y, range(1, z + 1))) \ ((lambda : (délka(argv) > 1 a int(argv[ 1 ])) nebo \ int(vstup("číslo?: ")))())))

Toto jediné volání uvnitř funkce tisk() a představuje celou aplikaci v její funkční podobě:

$python3 fact4.py číslo?: 5 vypočtený faktoriál = 120

Čte se tento kód (soubor fact4.py) lépe než imperativní zápis (soubor fact1.py)? Spíš ne než ano. Jaká je tedy jeho zásluha? Faktem je, že kdy žádný změny okolního kódu, normální provoz tento fragment bude zachován, protože nehrozí žádné vedlejší účinky v důsledku změn hodnot použitých proměnných.

Funkce vyššího řádu

Ve stylu funkčního programování je standardní praxe dynamická generace funkční objekt během provádění kódu s jeho následným voláním ve stejném kódu. Existuje řada oblastí, kde by taková technika mohla být užitečná.

Uzavření

Jedním ze zajímavých konceptů funkcionálního programování je uzávěry(uzavření). Tento nápad se ukázal být pro mnoho vývojářů natolik lákavý, že byl dokonce implementován v některých nefunkčních programovacích jazycích (Perl). David Mertz uvádí následující definici uzavření: „Uzávěr je procedura spolu se sadou dat, která je k ní připojena“ (na rozdíl od objektů v objektové programování, jako: „data spolu se souborem na ně navázaných procedur“).

Význam uzavření spočívá v tom, že definice funkce „zmrazí“ kontext, který ji obklopuje moment odhodlání. To se dá udělat různé způsoby, například parametrizací vytvoření funkce, jak je uvedeno ve výpisu 5 (soubor clos1.py v archivu z archivu v sekci "Stáhnout materiály"):

Výpis 5. Vytvoření uzávěrky
# -*- kódování: utf-8 -*- def multiplier(n): # multiplikátor vrací funkci, která se násobí n def mul(k): return n * k return mul mul3 = multiplikátor(3) # mul3 je funkce který se vynásobí 3 print(mul3(3), mul3(5))

Takto funguje tato dynamika specifická funkce:

$ python clos1.py (9, 15) $ python3 clos1.py 9 15

Dalším způsobem, jak vytvořit uzávěr, je použít výchozí hodnotu parametru v bodě definice funkce, jak je uvedeno ve výpisu 6 (soubor clos3.py func.py z archivu v sekci "Stáhnout materiály"):

Výpis 6. Další způsob vytvoření uzávěru
n = 3 def mult(k, mul = n): return mul * k n = 7 print(mult(3)) n = 13 print(mult(5)) n = 10 mult = lambda k, mul=n: mul * k tisk (více(3))

Žádné následné přiřazení k výchozímu parametru nezmění dříve definovanou funkci, ale samotnou funkci lze přepsat:

$ python clos3.py 9 15 30

Aplikace částečné funkce

Částečná funkce aplikace předpokládá na základě funkce N definice proměnné nová vlastnost s menším počtem proměnných M < N, zatímco zbytek N-M proměnné dostávají pevné „zamrzlé“ hodnoty (používá se modul functools). Podobný příklad bude diskutován níže.

Funktor

Funktor není funkce, ale objekt třídy, ve kterém je volána metoda __volání__(). V tomto případě lze volání použít na instanci takového objektu, stejně jako se to děje u funkcí. Ve výpisu 7 (soubor part.py z archivu z archivu(viz sekce Ke stažení) ukazuje, jak použít uzávěr, částečnou definici funkce a funktor k dosažení stejného výsledku.

Výpis 7. Porovnání uzávěru, parciální definice a funktoru
# -*- kódování: utf-8 -*- def multiplier(n): # closure def mul(k): return n * k return mul mul3 = multiplier(3) z functools import částečného def mulPart(a, b ): # částečná aplikace funkce return a * b par3 = částečný(mulPart, 3) class mulFunctor: # ekvivalentní funktor def __init__(self, val1): self.val1 = val1 def __call__(self, val2): return self.val1 * val2 fun3 = mulFunctor(3) print("() . () . ()".format(mul3(5), par3(5), fun3(5))))

Volání všech tří konstruktů pro argument rovný 5 povede ke stejnému výsledku, i když budou používat zcela odlišné mechanismy:

$ python part.py 15 . 15. 15

Karring

Curring (neboli currying) je transformace funkce z mnoha proměnných na funkci, která přebírá své argumenty jeden po druhém.

Poznámka. Tuto transformaci představili M. Sheinfinkel a G. Frege a byla pojmenována po matematikovi Haskell Currym, po kterém je pojmenován i programovací jazyk Haskell.

Přenášení neplatí jedinečné vlastnosti funkcionální programování, takže lze zapsat například currying transformaci Jazyky perlu nebo C++. Operátor kari je dokonce zabudován do některých programovacích jazyků (ML, Haskell), což umožňuje vícemístným funkcím vést k curried reprezentaci. Ale všechny jazyky, které podporují uzávěry, vám umožňují psát curried funkce a Python není v tomto ohledu výjimkou.

Výpis 8 ukazuje jednoduchý příklad s použitím kari (soubor kari1.py v archivu z archivu v sekci "Stáhnout materiály"):

Výpis 8. Curring
# -*- kódování: utf-8 -*- def spam(x, y): print("param1=(), param2=()".format(x, y)) spam1 = lambda x: lambda y: spam (x, y) def spam2(x) : def new_spam(y) : return spam(x, y) return new_spam spam1(2)(3) # spam2(2)(3)

Provádění těchto hovorů vypadá takto:

$ python curry1.py param1=2, param2=3 param1=2, param2=3

Závěr

Tento článek představil některé funkce jazyka Python, které vám umožňují používat jej k psaní programů pomocí funkčního programovacího stylu. Popsali jsme tedy základní techniky funkcionálního programování a ukázali příklady jejich implementace v Pythonu. Stejně jako v předchozích článcích jsou příklady kódu napsány tak, aby je bylo možné úspěšně spustit a spustit v obou verzích Pythonu.

V příštím článku probereme otázky organizace paralelního provádění kódu v prostředí Pythonu.

Funkce v programování je samostatná část kódu, kterou lze volat odkazem na ni jménem, ​​kterým byla pojmenována. Při volání se provádějí příkazy těla funkce.

Funkce lze přirovnat k malým programům, které se nespouštějí samy o sobě, tedy autonomně, ale jsou zabudovány do pravidelný program. Často se jim říká podprogramy. Ostatní klíčové rozdíly Z programů nejsou žádné funkce. Funkce mohou také přijímat a vracet data podle potřeby. Pouze je obvykle nedostávají ze vstupu (klávesnice, souboru atd.), ale ze volací program. Právě sem vracejí výsledky své práce.

V programovacím jazyce je zabudováno mnoho funkcí. S některými z nich jsme se již setkali v Pythonu. Jsou to print(), input(), int(), float(), str(), type(). Kód jejich těla není pro nás viditelný, je někde „skrytý uvnitř jazyka“. Máme k dispozici pouze rozhraní - název funkce.

Na druhou stranu si programátor může vždy definovat své vlastní funkce. Říká se jim zvyk. V tomto případě „uživatel“ znamená programátora, nikoli toho, kdo program používá. Pojďme zjistit, proč tyto funkce potřebujeme a jak je vytvořit.

Předpokládejme, že musíte požádat o dvojici čísel třikrát za sebou a přidat je. Pro tento účel můžete použít smyčku:

i = 0, zatímco i< 3 : a = int (input () ) b = int (input () ) print (a+b) i += 1

Co když však před každou žádostí o čísla potřebujete zobrazit nápis, proč jsou potřeba, a pokaždé je tento nápis jiný. Nemůžeme přerušit cyklus a pak se vrátit do stejného cyklu znovu. Budete to muset opustit a pak skončíte s dlouhým kódem obsahujícím stejné sekce na různých místech:

tisk () a = int (vstup () ) b = int (vstup () ) tisk ("Celkem", a+b, "ks." ) tisk () a = int (vstup () ) b = int (vstup () ) tisk ("Celkem", a+b, "ks." )

Příklad spuštění programu:

Kolik banánů a ananasů pro opice? 15 5 Celkem 20 ks. Kolik brouků a červů pro ježky? 50 12 Celkem 62 ks. Kolik ryb a korýšů pro vydry? 16 8 Celkem 24 ks.

Zavedení funkcí umožňuje vyřešit problém duplikace kódu na různých místech programu. Díky nim můžete stejnou část kódu spustit ne okamžitě, ale pouze tehdy, když je to potřeba.

Definice funkce. operátor def

V jazyce Programování v Pythonu funkce jsou definovány pomocí operátor def. Podívejme se na kód:

def countFood() : a = int (vstup () ) b = int (vstup () ) tisk ("Celkem", a+b, "ks." )

Toto je příklad definice funkce. Stejně jako jiné složité pokyny jako podmíněný operátor a smyčky, funkce se skládá z hlavičky a těla. Titul končí dvojtečkou a přechodem do nový řádek. Tělo je vroubkované.

Klíčové slovo def říká interpretu, že se jedná o definici funkce. def následuje název funkce. Může to být cokoli, stejně jako jakýkoli identifikátor, například proměnná. Při programování je velmi žádoucí dát všemu smysluplné názvy. Takže v tomto případě se funkce nazývá „countFood“ přeloženo do ruštiny.

Za názvem funkce následují závorky. V uvedeném příkladu jsou prázdné. To znamená, že funkce nepřijímá žádná data z programu, který ji volá. Mohl by je však přijmout a pak by byly v závorkách uvedeny tzv. parametry.

Za dvojtečkou následuje tělo obsahující instrukce, které jsou provedeny při volání funkce. Je třeba rozlišovat mezi definováním funkce a jejím voláním. V programový kód nejsou blízko a nejsou spolu. Funkci můžete definovat, ale nikdy ji nevolat. Nemůžete volat funkci, která nebyla definována. Pokud definujete funkci, ale nikdy ji nezavoláte, nikdy nespustíte její tělo.

Volání funkce

Uvažujme plná verze programy s funkcí:

def countFood() : a = int (vstup () ) b = int (vstup () ) tisk ("Celkem", a+b, "ks." ) tisk ( "Kolik banánů a ananasů pro opice?") countFood() tisk ( "Kolik brouků a červů pro ježky?") countFood() tisk ( "Kolik ryb a měkkýšů pro vydry?") countFood()

Po zobrazení každého informační zpráva provede se volání funkce, které jednoduše vypadá jako uvedení jejího názvu v závorkách. Protože do funkce nic nepředáváme, jsou závorky opět prázdné. Ve výše uvedeném kódu je funkce volána třikrát.

Když je funkce volána, vlákno programu skočí na její definici a začne provádět její tělo. Po provedení těla funkce se vlákno provádění vrátí do hlavního kódu v místě, kde byla funkce volána. Dále se provede výraz následující za voláním.

V Pythonu musí definice funkce předcházet jejím voláním. To je způsobeno tím, že interpret čte kód řádek po řádku a ještě neví, co je downstream. Pokud tedy volání funkce předchází její definici, dojde k chybě (vyvolá se výjimka NameError):

tisk( "Kolik banánů a ananasů pro opice?") countFood() tisk ( "Kolik brouků a červů pro ježky?") countFood() tisk ( "Kolik ryb a měkkýšů pro vydry?") countFood() def countFood() : a = int (vstup () ) b = int (vstup () ) tisk ("Celkem", a+b, "ks." )

Výsledek:

Kolik banánů a ananasů pro opice? Traceback (poslední poslední volání): Soubor "test.py" , řádek 2, in< module>countFood() NameError: název "countFood" není definován

Pro mnoho kompilovaných jazyků to tak není požadovaný stav. Zde můžete definovat a volat funkci na libovolných místech v programu. Pro čitelnost kódu však programátoři i v tomto případě raději dodržují určitá pravidla.

Funkce dávají strukturu programu

Výhody funkcí nejsou jen v možnosti vícenásobný hovor stejný kód z různých míst v programu. Neméně důležité je, že dávají programu skutečnou strukturu. Zdá se, že funkce jej rozdělují na samostatné části, z nichž každá plní svůj vlastní specifický úkol.

Předpokládejme, že potřebujete napsat program, který vypočítá plochy různých obrazců. Uživatel uvede oblast, kterou číslo chce vypočítat. Poté zadejte počáteční data. Například délka a šířka v případě obdélníku. Chcete-li rozdělit vlákno provádění do více větví, měli byste použít příkaz if-elif-else:

obrázek = vstup () if figure == "1" : a = plovoucí (vstup ("Šířka: " ) ) b = plovoucí (vstup ("Výška: " ) ) tisk ("Plocha: %.2f" % (a* b) ) elif figure == "2" : a = float (vstup ("Základ: " ) ) h = float (vstup ("Výška: " ) ) tisk ("Plocha: %.2f" % (0,5 * a * h) ) elif figure == "3" : r = float (vstup ("Radius: " ) ) print ("Plocha: %.2f" % (3,14 * r**2 ) ) else : print ("Chyba vstupu" )

Nejsou zde žádné funkce a vše je v pořádku. Ale pojďme napsat verzi s funkcemi:

def rectangle() : a = plovoucí (vstup ("Šířka: " ) ) b = plovoucí (vstup ("Výška: " ) ) tisk ("Oblast: %.2f" % (a*b) ) def trojúhelník() : a = float (vstup ("Základ: " ) ) h = float (vstup ("Výška: " ) ) tisk ( "Plocha: %.2f" % (0,5 * a * h) ) def circle() : r = plovoucí (vstup ("Radius: " ) ) print ( "Plocha: %.2f" % (3,14 * r**2 ) ) číslo = vstup ( "1-obdélník, 2-trojúhelník, 3-kruh: ") if figure == "1" : rectangle() elif figure == "2" : trojúhelník() elif figure == "3" : circle() else : print ("Chyba vstupu")

Zdá se to být složitější a každá ze tří funkcí je volána pouze jednou. Nicméně, od obecná logika Zdá se, že programy jsou odstraněny a pokyny pro hledání oblastí jsou odděleny. Program se nyní skládá z jednotlivých "Lego kostek". V hlavní větvi je můžeme libovolně kombinovat. Hraje roli kontrolního mechanismu.

Pokud bychom někdy chtěli spočítat plochu trojúhelníku pomocí Heronova vzorce místo výšky, nebudeme muset hledat kód v celém programu (představte si, že jsou to tisíce řádků kódu jako např. skutečné programy). Půjdeme tam, kde jsou funkce definovány, a změníme tělo jedné z nich.

Pokud potřebujeme tyto funkce použít v nějakém jiném programu, můžeme je tam importovat odkazem na tento soubor s kódem (jak se to dělá v Pythonu bude diskutováno později).

Praktická práce

Při programování můžete z jedné funkce volat jinou funkci. Pro ilustraci této možnosti napište program s následujícím popisem.

Hlavní větev programu, nepočítaje hlavičky funkcí, tvoří jeden řádek kódu. Toto je volání funkce test(). Požaduje zadání celého čísla. Pokud je kladné, je volána funkce positive(), jejíž tělo obsahuje příkaz pro zobrazení slova „Positive“ na obrazovce. Pokud je číslo záporné, zavolá se funkce negative(), jejíž tělo obsahuje výraz pro zobrazení slova "Negative".

Funkce mohou přenášet libovolná data ze svých těl do hlavní větve programu. Říká se, že funkce vrací hodnotu. Ve většině programovacích jazyků, včetně číslo Python, ukončení funkce a předání dat do místa, odkud byla volána, se provádí příkazem return.

Pokud interpret Pythonu při provádění těla funkce narazí na return, pak „převezme“ hodnotu zadanou po tomto příkazu a „ukončí“ funkci.

def cylinder() : r = float(input()) h = float(input()) # plocha povrchu na straně válce: strana = 2 * 3,14 * r * v # plocha základny jednoho válce: kruh = 3,14 * r**2 # celková plocha válce: plný = strana + 2 * kruh návrat plný čtverec = válec() tisk (čtverec)

Příklad provedení:

3 7 188.4

V tomto programu se hodnota lokální proměnné full vrací z funkce do hlavní větve. Ne samotná proměnná, ale její hodnota, v tomto případě nějaké číslo získané výpočtem plochy válce.

V hlavní větvi programu je tato hodnota přiřazena globální proměnné square. To znamená, že výraz square = cylinder() se provede takto:

    Je volána funkce cylinder().

    Z něj se vrátí hodnota.

    Tato hodnota je přiřazena čtvercové proměnné.

Výsledek není nutné přiřazovat proměnné, lze jej okamžitě zobrazit na obrazovce:

Tisk(válec())

Zde je přímo předáno číslo získané z cylinder(). funkce tisku(). Pokud jednoduše napíšeme cylindr() v programu, aniž bychom přiřazovali přijatá data proměnné nebo je předali někam dál, pak se tato data ztratí. Ale chyba syntaxe nebude.

Funkce může mít více příkazů return. Vždy je však popraven pouze jeden z nich. Ten, na který se prováděcí vlákno dostane jako první. Řekněme, že se rozhodneme zpracovat výjimku, která se vyskytuje na nesprávné zadání. Nechte pak ve větvi kromě obslužné rutiny výjimky opustit funkci bez jakýchkoli výpočtů a předání hodnoty:

def cylinder() : try : r = float (vstup () ) h = float (vstup () ) kromě ValueError : návratová strana = 2 * 3,14 * r * h kruh = 3,14 * r**2 plný = strana + 2 * kruhový návrat plný tisk (cylinder() )

Pokud se pokusíte zadat písmena místo čísel, bude fungovat návrat vnořený do kromě. Ukončí funkci, takže budou vynechány všechny následné výpočty, včetně návratu plné. Příklad provedení:

Ale počkej! Co je to slovo Žádné, které nám vrátil „prázdný“ návrat? To není nic, takový předmět je „nic“. Patří do třídy NoneType. Předtím jsme znali čtyři datové typy, známé také jako čtyři třídy: int, float, str, bool. Je čas na pátou.

Když není po návratu nic zadáno, výchozí je, že tam je objekt None. Ale nikdo vám nebrání výslovně napsat return None.

Navíc. Dříve jsme se dívali na funkce, které se zdály nevracet žádnou hodnotu, protože ji neměly návratový operátor. Ve skutečnosti byla vrácena, jen jsme tomu nevěnovali pozornost, nepřiřadili ji k žádné proměnné a nezobrazovali ji na obrazovce. V Pythonu každá funkce něco vrací. Pokud nemá příkaz return, vrátí None. To samé, jako kdyby obsahoval „prázdný“ návrat.

Vrácení více hodnot

V Pythonu můžete vrátit více objektů z funkce tak, že je uvedete oddělené čárkami za příkazem return:

def cylinder() : r = plovoucí (vstup () ) h = plovoucí (vstup () ) strana = 2 * 3,14 * r * h kruh = 3,14 * r**2 plný = strana + 2 * kruh zpětná strana, plný sCyl , fCyl = cylinder() tisk ( "Postranní plocha %.2f"% sCyl) tisk ( "Celková plocha %.2f"% fCyl)

Funkce cylinder() vrací dvě hodnoty. První z nich je přiřazena k proměnné sCyl, druhá - fCyl. Možnost takového skupinového přiřazení je Funkce Pythonu, obvykle netypické pro jiné jazyky:

>>> a, b, c = 10, 15, 19 >>>a 10 >>>b 15 >>> c 19

Trik je v tom, že výpis hodnot oddělených čárkami (například 10, 15, 19) vytvoří objekt typu n-tice. Přeloženo do ruštiny jako "nice". Toto je typ datové struktury, který bude prozkoumán později.

Když je n-tici přiřazeno několik proměnných najednou, její prvky se porovnávají s odpovídajícími proměnnými ve frontě. Tomu se říká rozbalení.

Když tedy funkce vrací více hodnot, ve skutečnosti vrací jeden objekt n-tice. Tyto vícenásobné hodnoty jsou před vrácením zabaleny do n-tice. Pokud je po příkazu return pouze jedna proměnná nebo objekt, pak je její typ zachován tak, jak je.

Rozbalení je volitelné. Bude to fungovat takto:

...tisk (cylindr())

Příklad provedení:

4 3 (75.36 , 175.84 )

Na obrazovce se zobrazí n-tice, jak je uvedeno v závorkách. Lze ji také přiřadit k jedné proměnné a poté zobrazit její hodnotu na obrazovce.




Horní