Kódujte funkčně v Pythonu: Naučte se sílu funkčního programovacího paradigmatu. Mapa funkcí a zip a lambda. Krajta. Proč je lepší mapovat a redukovat

V programování existuje několik paradigmat, například OOP, funkcionální, imperativní, logická a mnoho z nich. Budeme mluvit o funkcionálním programování.

Předpoklady pro plné funkční programování v Pythonu jsou: funkce vyššího řádu, vyvinuté nástroje pro zpracování seznamů, rekurze, schopnost organizovat líné výpočty.

Dnes se seznámíme s jednoduchými prvky a složité návrhy budou v dalších lekcích.

Teorie v teorii

Stejně jako u OOP a funkcionálního programování se snažíme vyhýbat definicím. Stále jasná definice Je těžké to dát, takže zde nebude jasná definice. Však! Zdůrazněme přání funkčního jazyka:

  • Funkce vyššího řádu
  • Čisté funkce
  • Neměnná data

To není úplný seznam, ale i to stačí k tomu, aby to bylo „krásné“. Pokud chce čtenář více, zde je rozšířený seznam:

  • Funkce vyššího řádu
  • Čisté funkce
  • Neměnná data
  • Uzávěry
  • Lenost
  • Rekurze ocasu
  • Algebraické datové typy
  • Shoda vzorů

Pojďme se postupně zamyslet nad všemi těmito body a jak je používat v Pythonu.

A dnes krátce, co je co na prvním seznamu.

Čisté funkce

Čisté funkce nevyvolávají žádné pozorovatelné vedlejší účinky, pouze vrací výsledek. Nemění globální proměnné, nikam nic neposílají ani netisknou, nedotýkají se objektů a tak dále. Přijímají data, něco vypočítají, přičemž berou v úvahu pouze argumenty, a vracejí nová data.

  • Snazší čtení a pochopení kódu
  • Snadnější testování (není třeba vytvářet „podmínky“)
  • Spolehlivější, protože nezávisí na „počasí“ a stavu prostředí, pouze na argumentech
  • Lze spustit paralelně, výsledky lze uložit do mezipaměti

Neměnná data

Neměnné datové struktury jsou kolekce, které nelze změnit. Skoro jako čísla. Číslo prostě existuje, nelze ho změnit. Stejně tak neměnné pole je způsob, jakým bylo vytvořeno a bude vždy. Pokud potřebujete přidat prvek, budete muset vytvořit nové pole.

Výhody neměnných struktur:

  • Bezpečně sdílejte reference mezi vlákny
  • Snadno testovatelné
  • Snadno sledovatelný životní cyklus (odpovídá toku dat)

Funkce vyššího řádu

Funkce, která bere jako argument jinou funkci a/nebo vrací jinou funkci, je volána funkce vyššího řádu:

Def f(x): return x + 3 def g(funkce, x): return function(x) * function(x) print(g(f, 7))

Po zvážení teorie začněme přejít k praxi, od jednoduchých po složité.

Seznam zahrnutí nebo generátor seznamu

Podívejme se na jeden jazykový návrh, který pomůže snížit počet řádků kódu. Není neobvyklé určit úroveň programátora Pythonu pomocí této konstrukce.

Ukázkový kód:

Pro x v xrange(5, 10): if x % 2 == 0: x =* 2 else: x += 1

Cyklus s tímto stavem není neobvyklý. Nyní se pokusíme proměnit těchto 5 řádků v jeden:

>>>

Není to špatné, 5 řádků nebo 1. Navíc se zvýšila expresivita a takový kód je srozumitelnější - pro každý případ lze přidat jeden komentář.

Obecně je tento design následující:

Stojí za to pochopit, že pokud kód není vůbec čitelný, je lepší takový návrh opustit.

Anonymní funkce nebo lambda

Pokračujeme ve snižování množství kódu.

Def calc(x, y): vrátí x**2 + y**2

Funkce je krátká, ale minimálně 2 řádky byly zbytečné. Je možné takto malé funkce zkrátit? Nebo to možná neformátovat jako funkce? Koneckonců, nechcete vždy v modulu vytvářet zbytečné funkce. A pokud funkce zabírá jeden řádek, pak ještě více. Proto v programovacích jazycích existují anonymní funkce, které nemají jméno.

Anonymní funkce v Pythonu jsou implementovány pomocí lambda kalkulu a vypadají jako lambda výrazy:

>>> lambda x, y: x**2 + y**2 na 0x7fb6e34ce5f0>

Pro programátora jsou to stejné funkce a dá se s nimi i pracovat.

Abychom k anonymním funkcím přistupovali několikrát, přiřadíme je k proměnné a využijeme je ve svůj prospěch.

>>> (lambda x, y: x**2 + y**2)(1, 4) 17 >>> >>> func = lambda x, y: x**2 + y**2 >>> func(1, 4) 17

Funkce lambda mohou fungovat jako argument. I pro jiné lambdy:

Násobitel = lambda n: lambda k: n*k

Použití lambda

Naučili jsme se vytvářet funkce bez názvu, ale nyní zjistíme, kde je použít. Standardní knihovna poskytuje několik funkcí, které mohou mít funkci jako argument – ​​map(), filter(), reduction(), apply().

mapa()

Funkce map() zpracovává jednu nebo více sekvencí pomocí dané funkce.

>>> seznam1 = >>> seznam2 = [-1, 1, -5, 4, 6] >>> seznam(mapa(lambda x, y: x*y, seznam1, seznam2)) [-7, 2, -15, 40, 72]

S generátorem seznamů jsme se již seznámili, použijme jej, pokud je délka seznamu stejná):

>>> [-7, 2, -15, 40, 72]

Je tedy patrné, že použití seznamů je kratší, ale lambdy jsou flexibilnější. Jdeme dál.

filtr()

Funkce filter() umožňuje filtrovat hodnoty sekvence. Výsledný seznam obsahuje pouze ty hodnoty, pro které platí hodnota funkce pro prvek:

>>> čísla = >>> seznam(filtr(lambda x: x< 5, numbers)) # В результат попадают только те элементы x, для которых x < 5 истинно

Totéž s výrazy seznamu:

>>> čísla = >>>

snížit()

Chcete-li uspořádat výpočty řetězce v seznamu, můžete použít funkci reduction(). Například součin prvků seznamu lze vypočítat takto (Python 2):

>>> čísla = >>> snížit(lambda res, x: res*x, čísla, 1) 720

Výpočty probíhají v následujícím pořadí:

((((1*2)*3)*4)*5)*6

Řetězec volání je propojen pomocí mezivýsledku (res). Pokud je seznam prázdný, jednoduše se použije třetí parametr (v případě součinu nulových faktorů je to 1):

>>> snížit(lambda res, x: res*x, , 1) 1

Průběžný výsledek samozřejmě nemusí být nutně číslo. Může to být jakýkoli jiný datový typ, včetně seznamu. Následující příklad ukazuje opak seznamu:

>>> snížit(lambda rozlišení, x: [x]+rozlišení, , )

Python má vestavěné funkce pro nejběžnější operace:

>>> čísla = >>> součet(čísla) 15 >>> seznam(obrácený(čísla))

Python 3 nemá vestavěnou funkci reduction(), ale lze ji nalézt v modulu functools.

použít()

Funkce pro aplikaci jiné funkce na poziční a pojmenované argumenty, daný seznam a odpovídajícím způsobem slovník (Python 2):

>>> def f(x, y, z, a=Žádné, b=Žádné): ... tisk x, y, z, a, b ... >>> použít(f, , ("a": 4, "b": 5)) 1 2 3 4 5

V Pythonu 3 byste měli místo funkce apply() použít speciální syntaxi:

>>> def f(x, y, z, a=Žádné, b=Žádné): ... print(x, y, z, a, b) ... >>> f(*, **(" a": 4, "b": 5)) 1 2 3 4 5

Dokončeme naši recenzi standardní knihovny s touto vestavěnou funkcí a přejdeme k poslednímu funkčnímu přístupu pro dnešek.

Uzávěry

Funkce definované uvnitř jiných funkcí jsou uzávěry. Proč je to nutné? Podívejme se na příklad pro vysvětlení:

Kód (smyšlený):

Def processing(element, type_filter, all_data_size): filters = Filter(all_data_size, type_filter).get_all() pro filtrování filtrů: element = filtr.filter(element) def main(): data = DataStorage().get_all_data() for x v datech: processing(x, "all", len(data))

Čeho si v kódu můžete všimnout: v tomto kódu jsou proměnné, které v podstatě žijí trvale (tj. identické), ale zároveň je několikrát načítáme nebo inicializujeme. V důsledku toho jsme pochopili, že inicializace proměnných zabírá v tomto procesu lví podíl času, stává se, že i načítání proměnných do rozsahu snižuje výkon. Chcete-li snížit horní použití uzávěry.

Uzavření jednou inicializuje proměnné, které pak lze použít bez režie.

Pojďme se naučit, jak vytvořit uzávěry:

Def multiplier(n): "multipliker(n) vrací funkci, která se násobí n" def mul(k): return n*k return mul # stejného efektu lze dosáhnout pomocí # multiplikátor = lambda n: lambda k: n* k mul2 = multiplikátor(2) # mul2 - funkce, která se násobí 2, například mul2(5) == 10

Závěr

V lekci jsme se podívali základní pojmy FP, a také sestavil seznam mechanismů, které budou probrány v následujících lekcích. Mluvili jsme o způsobech, jak snížit množství kódu, jako jsou zahrnutí seznamu (generátor seznamu), funkce lamda a jejich použití a nakonec bylo pár slov o uzávěrech a o tom, k čemu jsou.

3.2.3 Porozumění slovníku

Řekněme, že máme slovník, jehož klíče jsou znaky a jejichž hodnoty mapují, kolikrát se daný znak objevil v nějakém textu. Slovník aktuálně rozlišuje velká a malá písmena.

Požadujeme slovník, ve kterém jsou kombinovány výskyty velkých a malých písmen:

dct = ( "a": 10, "b": 34, "A": 7, "Z": 3)

frekvence = ( k . dolní ( ) : dct . získat ( k . nižší ( ) , 0 ) + dct . získat ( k . horní ( ) , 0 )

pro k v dct . klíče())

frekvence tisku #("a": 17, "z": 3, "b": 34)

Python podporuje vytváření anonymních funkcí (tj nejsou vázaný na jméno) za běhu pomocí konstrukce nazvané „lambda“. To není úplně totéž jako lambda ve funkcionálních programovacích jazycích, ale je to velmi výkonný koncept, který je dobře integrován do Pythonu a často se používá ve spojení s typickými funkčními koncepty, jako je filter() , map() a reduction() .

Anonymní funkce ve formě výrazu lze vytvořit pomocí lambda
prohlášení:

args je seznam argumentů oddělených čárkami a výraz je výraz obsahující tyto argumenty. Tento kus kódu ukazuje rozdíl mezi definicí normální funkce a funkcí lambda:

def funkce (x):

vrátit x * x

funkce tisku (2) #4

#-----------------------#

funkce = lambda x : x * x

funkce tisku (2) #4

Jak vidíte, obě funkce function() dělají přesně totéž a lze je použít stejným způsobem. Všimněte si, že definice lambda nezahrnuje příkaz „return“ – vždy obsahuje výraz, který je vrácen. Všimněte si také, že definici lambda můžete umístit kamkoli, kde se očekává funkce, a nemusíte ji vůbec přiřazovat k proměnné.

Následující fragmenty kódu demonstrují použití funkcí lambda.

přírůstek def (n):

návrat lambda x : x + n

přírůstek tisku (2) # na 0x022B9530>

přírůstek tisku (2) (20) #22

Výše uvedený kód definuje přírůstek funkce, který vytváří anonymní funkci za běhu a vrací ji. Vrácená funkce zvýší svůj argument o hodnotu, která byla zadána při jejím vytvoření.

Nyní můžete vytvořit několik různých inkrementačních funkcí a přiřadit je k proměnným a poté je používat nezávisle na sobě. Jak ukazuje poslední příkaz, funkci ani nemusíte nikam přiřazovat – stačí ji okamžitě použít a zapomenout, když už ji nepotřebujete.

Q3. K čemu je lambda dobrá?
Ans.
Odpověď zní:

  • Nepotřebujeme lambdu, bez ní bychom se obešli. Ale...
  • jsou určité situace, kdy je to pohodlné – psaní kódu je o něco jednodušší a psaný kód o něco čistší.

Q4. Jaké situace?

No, situace, ve kterých potřebujeme jednoduchou jednorázovou funkci: funkci, která bude použita pouze jednou.

Normálně jsou funkce vytvářeny pro jeden ze dvou účelů: (a) snížit duplicitu kódu nebo (b) modularizovat kód.

  • Pokud vaše aplikace obsahuje duplicitní kusy kódu na různých místech, můžete jednu kopii tohoto kódu vložit do funkce, pojmenovat ji a pak ji – pomocí tohoto názvu funkce – volat z různých míst v kódu.
  • Pokud máte kus kódu, který provádí jednu dobře definovanou operaci - ale je opravdu dlouhý a drsný a přerušuje jinak čitelný tok vašeho programu - pak můžete tento dlouhý drsný kód vytáhnout a vložit jej do funkce úplně sám.

Předpokládejme však, že potřebujete vytvořit funkci, která bude použita pouze jednou – volána z pouze jeden místo ve vaší aplikaci. No, za prvé, nemusíte funkci pojmenovávat. Může být „anonymní“. A můžete si jej nadefinovat přímo na místě, kde jej chcete používat. V tom je lambda užitečná.

Typicky se lambda používá v kontextu nějaké jiné operace, jako je třídění nebo redukce dat:

jména = [ "David Beazley" , "Brian Jones" , "Raymond Hettinger" , "Ned Batchelder" ]

tisk seřazený (jména , klíč = název lambda : jméno . rozdělení () [ - 1 ] . nižší () )

# ["Ned Batchelder", "David Beazley", "Raymond Hettinger", "Brian Jones"]

Přestože lambda umožňuje definovat jednoduchou funkci, její použití je značně omezené. V
konkrétně lze zadat pouze jeden výraz, jehož výsledkem je návrat
hodnota. To znamená, že nelze zahrnout žádné další jazykové funkce, včetně více příkazů, podmínek, iterací a zpracování výjimek.
Můžete docela šťastně napsat spoustu kódu Python, aniž byste kdy používali lambda. Však,
občas se s tím setkáte v programech, kde někdo píše hodně maličkosti
funkce, které vyhodnocují různé výrazy, nebo v programech, které od uživatelů vyžadují
funkce zpětného volání.

Definovali jste anonymní funkci pomocí lambda, ale musíte ji také zachytit
hodnoty určitých proměnných v době definice.

>>> x = 10

>>> a = lambda y : x + y

>>> x = 20

>>> b = lambda y : x + y

Nyní si položte otázku. Co jsou hodnoty a(10) a b(10)? Pokud si myslíte,
výsledky mohou být 20 a 30, mýlili byste se:

Problém je v tom, že hodnota x použitá ve výrazu lambda je volná proměnná
který se váže za běhu, nikoli za čas definice. Tedy hodnota x v lambda
výrazů je jakákoliv hodnota proměnné x v době provádění.
Například:

Pokud chcete, aby anonymní funkce zachytila ​​hodnotu v bodě definice a
ponechat, zahrnout hodnotu jako výchozí hodnotu, takto:

Problém, který je zde řešen, je něco, co se obvykle objevuje v kódu, který
se snaží být s používáním lambda funkcí až příliš chytrý. Například,
vytvoření seznamu výrazů lambda pomocí porozumění seznamu nebo v nějaké smyčce a očekávání, že si funkce lambda zapamatují iterační proměnnou v době definice. Například:

>>> funcs = [ lambda x : x + n pro n v rozsahu (5) ]

  • Překlad

Když mluvíme o funkčním programování, lidé často začnou vyhazovat spoustu „funkčních“ charakteristik. Neměnná data, prvotřídní funkce a optimalizace tail rekurze. Toto jsou vlastnosti jazyka, které vám pomáhají psát funkční programy. Zmiňují mapování, currying a používání funkcí vyššího řádu. Jedná se o programovací techniky používané k psaní funkčního kódu. Zmiňují paralelizaci, líné hodnocení a determinismus. To jsou výhody funkčních programů.

Zabodujte. Funkční kód má jednu vlastnost: absenci vedlejších účinků. Nespoléhá se na data zvenčí aktuální funkce a nemění data mimo funkci. Z toho se dají odvodit všechny ostatní "vlastnosti".

Nefunkční funkce:

A = 0 def increment1(): globální a a += 1

Funkční funkce:

Def increment2(a): vrátí a + 1

Místo iterování seznamem použijte mapu a zmenšení

Mapa

Přijímá funkci a sadu dat. Vytvoří nová kolekce, provede funkci na každé pozici dat a přidá návratovou hodnotu do nové kolekce. Vrátí novou kolekci.

Jednoduchá mapa, která vezme seznam jmen a vrátí seznam délek:

Name_lengths = map(len, ["Masha", "Petya", "Vasya"]) print name_lengths # =>

Tato mapa zarovná každý prvek do čtverce:

Čtverce = mapa(lambda x: x * x, ) tisk čtverců # =>

Nepřijímá pojmenovanou funkci, ale bere anonymní funkci definovanou přes lambda. Parametry lambda jsou definovány vlevo od dvojtečky. Tělo funkce je vpravo. Výsledek je vrácen implicitně.

Nefunkční kód v následujícím příkladu vezme seznam jmen a nahradí je náhodnými přezdívkami.

Import náhodných jmen = ["Masha", "Petya", "Vasya"] code_names = ["Shpuntik", "Vintik", "Funtik"] pro i v rozsahu(len(jména)): jména[i] = náhodné. choice(code_names) tisknout jména # => ["Shpuntik", "Vintik", "Shpuntik"]

Algoritmus může přiřadit stejné přezdívky různým tajným agentům. Doufejme, že to nezpůsobí problémy během tajné mise.

Přepišme to pomocí mapy:

Import náhodných jmen = ["Masha", "Petya", "Vasya"] secret_names = map(lambda x: random.choice(["Shpuntik", "Vintik", "Funtik"]), jména)

Cvičení 1. Zkuste přepsat následující kód pomocí mapy. Vezme seznam skutečných jmen a nahradí je přezdívkami pomocí spolehlivější metody.

Jména = ["Masha", "Petya", "Vasya"] pro i v rozsahu(len(jména)): jména[i] = hash(jména[i]) tisknout jména # =>

Moje řešení:

jména = ["Masha", "Petya", "Vasya"] secret_names = mapa(hash, jména)

Snížit

Snížit má funkci a sadu položek. Vrátí hodnotu získanou kombinací všech položek.

Příklad jednoduchého snížení. Vrátí součet všech položek v sadě:

Součet = snížit(lambda a, x: a + x, ) vytisknout součet # => 10

X je aktuální položka a je baterie. Toto je hodnota, která je vrácena provedením lambda na předchozí položce. reduction() iteruje všechny hodnoty a pro každou spustí lambda na aktuálních hodnotách a a x a vrátí výsledek v a pro další iteraci.

Jaká je hodnota a v první iteraci? Je roven prvnímu prvku kolekce a reduction() začne pracovat od druhého prvku. To znamená, že první x se bude rovnat druhé položce sady.

Následující příklad počítá, jak často se slovo „kapitán“ objevuje v seznamu řetězců:

Věty = ["kapitán Jack Sparrow", "námořní kapitán", "vaše loď je připravena, kapitáne"] cap_count = 0 pro větu ve větách: cap_count += věta.count("kapitán") print cap_count # => 3

Stejný kód pomocí snížit:

Věty = ["kapitán jack sparrow", "námořní kapitán", "vaše loď je připravena, kapitáne"] cap_count = snížit(lambda a, x: a + x.count("kapitán"), věty, 0)

Odkud se zde bere počáteční hodnota a? Nelze jej vypočítat z počtu opakování v prvním řádku. Proto je uveden jako třetí argument funkce reduction().

Proč jsou map a redukce lepší?

Za prvé, obvykle se vejdou na jeden řádek.

Za druhé, důležité části iterace – kolekce, operace a návratová hodnota – jsou vždy na stejném místě, mapují se a redukují.

Za třetí, kód ve smyčce může změnit hodnotu dříve definovaných proměnných nebo ovlivnit kód po něm. Podle konvence jsou mapování a zmenšení funkční.

Za čtvrté, mapování a zmenšení jsou základní operace. Místo čtení smyček řádek po řádku je pro čtenáře snazší vnímat mapu a redukovat vestavěné do složitých algoritmů.

Za páté, mají mnoho přátel, což umožňuje užitečné, mírně upravené chování těchto funkcí. Například filtrovat, vše, libovolné a najít.

Cvičení 2: Přepište následující kód pomocí map, zmenšení a filtru. Filtr přijímá funkci a kolekci. Vrátí kolekci těch věcí, pro které funkce vrací True.

Lidé = [("name": "Masha", "height": 160), ("height": "Sasha", "height": 80), ("name": "Pasha")] height_total = 0 height_count = 0 pro osobu v lidech: if "height" in person: height_total += person["height"] height_count += 1 if height_count > 0: average_height = height_total / height_count print average_height # => 120

Moje řešení:

lidé = [("jméno": "Máša", "výška": 160), ("výška": "Saša", "výška": 80), ("jméno": "Paša")] výšky = mapa(lambda x: x["height"], filter(lambda x: "height" in x, people)) if len(heights) > 0: from operator import add average_height = reduction(add, heights) / len(heights)

Pište deklarativně, ne imperativně.

Následující program emuluje závod tří vozů. V každém okamžiku se auto buď pohybuje vpřed, nebo ne. Pokaždé, když program zobrazí vzdálenost ujetou auty. Po pěti časových intervalech závod končí.

Příklady výstupů:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Text programu:

Z náhodného importu náhodný čas = 5 pozic auta = while time: # time down time time -= 1 print "" for i in range(len(car_positions)): # move car if random() > 0.3: car_positions[i] += 1 # kreslení auta tisk "-" * pozice auta[i]

Kód je nezbytně nutný. Funkční verze by byla deklarativní – popisovala by, co je třeba udělat, nikoli jak by se to mělo udělat.

Použití funkcí

Deklarativnosti lze dosáhnout vložením kódu do funkcí:

Z náhodného importu random def move_cars(): for i, _ in enumerate(car_positions): if random() > 0.3: car_positions[i] += 1 def draw_car(car_position): print "-" * car_position def run_step_of_race(): globální čas čas -= 1 move_cars() def draw(): print "" pro car_position in car_positions: draw_car(car_position) time = 5 car_positions = while time: run_step_of_race() draw()

Pro pochopení programu se čtenář podívá na hlavní smyčku. „Pokud zbude čas, projdeme jeden krok závodu a zobrazíme výsledek. Pojďme znovu zkontrolovat čas." Pokud čtenář potřebuje pochopit, jak závodní krok funguje, může si kód přečíst samostatně.

Není třeba komentářů, vysvětluje kód sám.

Rozdělení kódu na funkce činí kód čitelnějším. Tato technika používá funkce, ale pouze jako podprogramy. Kód zabalí, ale nefunkční. Funkce ovlivňují kód kolem nich a mění globální proměnné spíše než vracejí hodnoty. Pokud čtenář narazí na proměnnou, bude muset zjistit, odkud pochází.

Zde funkční verze tento program:

Z náhodného importu random def move_cars(car_positions): return map(lambda x: x + 1 if random() > 0.3 else x, car_positions) def output_car(car_position): return "-" * car_position def run_step_of_race(state): return ( "time": state["time"] - 1, "car_positions": move_cars(state["aut_positions"])) def draw(state): print "" print "\n".join(map(výstupní_auto, stav[ "car_positions"])) def race(state): draw(state) if state["time"]: race(run_step_of_race(state)) race(("time": 5, "car_positions": ))

Kód je nyní rozdělen na funkční funkce. Existují tři známky toho. První je, že neexistují žádné sdílené proměnné. čas a pozice auta jsou předány přímo race(). Za druhé, funkce berou parametry. Za třetí, proměnné se uvnitř funkcí nemění; Pokaždé, když run_step_of_race() provede další krok, je předán zpět k dalšímu.

Zde jsou dvě funkce zero() a one():

Def nula(y): if s == "0": návrat s def jedna(s): if s == "1": návrat s

Zero() vezme řetězec s. Pokud je první znak 0, vrátí zbytek řetězce. Pokud ne, pak Žádné. one() dělá totéž, pokud je první znak 1.

Představme si funkci rule_sequence(). Vyžaduje řetězec a seznam funkcí pravidel, skládající se z funkcí nula a jedna. Zavolá první pravidlo a předá mu řetězec. Pokud je vráceno None, převezme vrácenou hodnotu a zavolá další pravidlo. A tak dále. Pokud je vráceno None, rule_sequence() se zastaví a vrátí None. Jinak – význam posledního pravidla.

Příklady vstupních a výstupních dat:

Tisk rule_sequence("0101", ) # => 1 tisk rule_sequence("0101", ) # => Žádné

Imperativní verze rule_sequence():

Def rule_sequence(s, rules): pro pravidlo v pravidlech: s = pravidlo(a) if s == None: break return s

Cvičení 3. Tento kód používá smyčku. Přepište to deklarativně pomocí rekurze.

Moje řešení:

def rule_sequence(s, rules): if s == None or not rules: return s else: return rule_sequence(rules(s), rules)

Použijte potrubí

Nyní přepišme jiný typ smyčky pomocí techniky zvané potrubí.

Další smyčka upravuje slovníky obsahující název, nesprávnou zemi původu a status některých skupin.

Kapely = [("name": "sunset rubdown", "country": "UK", "active": False), ("name": "women", "country": "Německo", "active": False ), ("name": "a silver mt. zion", "country": "Španělsko", "active": True)] def format_bands(bands): pro skupinu ve skupinách: band["country"] = "Kanada " kapela["jméno"] = kapela["jméno"].replace(".", "") kapela["jméno"] = kapela["jméno"].title() format_bands(bands) print bands # => [("name": "Sunset Rubdown", "active": False, "country": "Kanada"), # ("name": "Women", "active": False, "country": "Canada" ) , # ("name": "A Silver Mt Zion", "active": True, "country": "Kanada")]

Název funkce "formát" je příliš obecný. Obecně kód vyvolává určité obavy. V jednom cyklu se dějí tři různé věci. Hodnota klíče „země“ se změní na „Kanada“. Tečky se odstraní a první písmeno jména se změní na velké. Je těžké pochopit, co má kód dělat, a těžko říct, jestli to dělá. Je obtížné používat, testovat a paralelizovat.

Porovnat:

Tisk potrubí_každý(pásma, )

Je to jednoduché. Pomocné funkce vypadají funkčně, protože jsou zřetězené. Výstup předchozího je vstupem dalšího. Lze je snadno testovat, znovu používat, ověřovat a paralelizovat.

Pipeline_each() prochází skupinami jednu po druhé a předává je konverzním funkcím, jako je set_canada_as_country(). Po aplikaci funkce na všechny skupiny vytvoří pipeline_each() jejich seznam a předá je další skupině.

Podívejme se na transformační funkce.

Def assoc(_d, key, value): from copy import deepcopy d = deepcopy(_d) d = value return d def set_canada_as_country(band): return assoc(band, "country", "Canada") def strip_punctuation_from_name(band): return assoc(band, "name", band["name"].replace(".", "")) def capitalize_names(band): return assoc(band, "name", band["name"].title( ))

Každý přidruží skupinový klíč k nové hodnotě. To je těžké udělat bez změny původních dat, takže to řešíme pomocí assoc(). Používá deepcopy() k vytvoření kopie předávaného slovníku. Každá funkce transformuje kopii a vrátí tuto kopii.

Vše se zdá být v pořádku. Původní data jsou chráněna před změnami. V kódu však existují dvě potenciální místa pro změny dat. V strip_punctuation_from_name() se vytvoří název bez teček voláním funkce replace() s původním názvem. Capitalize_names() vytvoří název s prvním velké písmeno na základě title() a původní název. Pokud nejsou funkční nahradit a čas, pak funkce strip_punctuation_from_name() a capitalize_names() nejsou funkční.

Naštěstí jsou funkční. V Pythonu jsou řetězce neměnné. Tyto funkce pracují s kopiemi řetězců. Uff, díky bohu.

Tento kontrast mezi řetězci a slovníky (jejich proměnlivá povaha) v Pythonu demonstruje výhody jazyků, jako je Clojure. Tam programátor nemusí myslet na to, zda bude měnit data. To se nezmění.

Cvičení 4. Zkuste vytvořit funkci pipeline_each. Zamyslete se nad posloupností operací. Skupiny jsou v poli, předávané jedna po druhé do první konverzní funkce. Výsledné pole se pak po kusech předá druhé funkci a tak dále.

Moje řešení:

def pipeline_each(data, fns): return reduction(lambda a, x: map(x, a), fns, data)

Všechny tři transformační funkce vedou ke změně konkrétního pole pro skupinu. call() lze použít k vytvoření abstrakce. Přijímá funkci a klíč, na který bude aplikován.

Set_canada_as_country = call(lambda x: "Kanada", "země") strip_punctuation_from_name = call(lambda x: x.replace(".", ""), "name") capitalize_names = call(str.title, "name") print pipeline_each(bands, )

Nebo obětovat čitelnost:

Tisk potrubí_každý(pásma, )

Kód pro volání():

Def assoc(_d, klíč, hodnota): z importu kopie deepcopy d = deepcopy(_d) d = hodnota return d def call(fn, klíč): def apply_fn(záznam): return assoc(záznam, klíč, fn(záznam. get(key))) return apply_fn

co se to tu děje?

Jeden. volání je funkce vyššího řádu, protože vezme jinou funkci jako argument a vrátí funkci.

Dva. apply_fn() je podobná konverzním funkcím. Získá záznam (skupinu). Vyhledá záznam hodnoty. Volá fn. Přiřadí výsledek kopii záznamu a vrátí ji.

Tři. volání samo o sobě nic nedělá. apply_fn() dělá veškerou práci. V příkladu pipeline_each() jedna instance apply_fn() nastaví "country" na "Canada". U druhého je první písmeno velké.

Čtyři. Při provádění instance apply_fn() nebudou funkce fn a key dostupné v rozsahu. Toto nejsou argumenty pro apply_fn() nebo lokální proměnné. Ale bude k nim přístup. Když je funkce definována, zachovává si odkazy na proměnné, které uzavírá – ty, které byly definovány mimo funkci a použity interně. Když je funkce spuštěna, proměnné se hledají mezi lokálními, pak mezi argumenty a pak mezi odkazy na uzávěry. Tam najdete fn a klíč.

Pět. Není zde žádná zmínka o skupinách v hovoru. Je to proto, že volání lze použít k vytvoření libovolných kanálů bez ohledu na jejich obsah. Funkční programování vytváří knihovnu běžných funkcí vhodných pro skládání a opětovné použití.

Dobrá práce. Uzávěry, funkce vyššího řádu a rozsah – vše v několika odstavcích. Můžete si také dát čaj a sušenky.

Zbývá ještě jedno zpracování skupinových dat. Odstraňte vše kromě jména a země. funkce extrakt_jméno_a_země():

Def extract_name_and_country(band): plucked_band = () plucked_band["name"] = kapela["name"] plucked_band["country"] = band["country"] return plucked_band print pipeline_each(bands, ) # => [(" name": "Sunset Rubdown", "country": "Kanada"), # ("name": "Women", "country": "Canada"), # ("name": "A Silver Mt Zion", " země": "Kanada")]

Extract_name_and_country() by mohlo být zapsáno v obecné formě zvané pluck(). Použilo by se to takto:

Print pipeline_each(bands, )])

Cvičení 5. pluck přijímá seznam klíčů k extrakci ze záznamů. Zkuste to napsat. Toto bude funkce vyššího řádu.

Existuje velký počet publikace věnované implementaci konceptů funkcionálního programování v Pythonu, 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 řádku 2 a řádku 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. Zvláštností Pythonu je, že funkce je pojmenovaný objekt, stejně jako jakýkoli jiný objekt nějakého datového typu, řekněme jako celočíselná proměnná. Výpis 1 pořadů nejjednodušší příklad(soubor func.py z archivu python_functional.tgz

Výpis 1. Definice funkcí
#!/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 přemě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ý funkční programovací jazyk, například existují speciální jazyky pro plně funkční programování: 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 funkcionálních jazyků. Tito. „skládat“ jednotlivé fragmenty imperativního kódu (někdy poměrně velké) 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 lambda, 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ředávanou funkci na každý prvek v předaném seznamu (seznamech) a vrátí seznam výsledků (stejný rozměr jako vstup);
  • snížit() aplikuje předávanou funkci na každou hodnotu v seznamu a na interní akumulátor výsledků, 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 implementovat překvapivě širokou škálu operací řídicího toku, aniž byste se uchýlili k imperativním příkazům, ale s použitím pouze výrazů ve funkčním stylu, jak je uvedeno ve výpisu 2 (soubor funcH.py z archivu python_functional.tgz

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().

Proto bude tento kód fungovat v obou verzích Pythonu:

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

Pokud není vyžadována přenositelnost kódu mezi různými verzemi, pak lze takové fragmenty vyloučit, což umožní kód poněkud zjednodušit.

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 z archivu python_functional.tgz

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 python_functional.tgz 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 výrazy funkcí nahrazeny:

  • 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 vytisknout() 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 python_functional.tgz 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 vytisknout() 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 důstojnost? 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 uzávěru je, že definice funkce „zmrazí“ okolní kontext moment odhodlání. To lze provést různými způsoby, například parametrizací vytváření funkce, jak je uvedeno ve výpisu 5 (soubor clos1.py v archivu python_functional.tgz 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 taková dynamicky definovaná 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 z archivu python_functional.tgz 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 dílčích funkcí

Částečná funkce aplikace předpokládá na základě funkce N proměnných definice nové funkce 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í aplikovat na instanci takového objektu, stejně jako se to děje u funkcí. Ve výpisu 7 (soubor part.py z archivu python_functional.tgz(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 currying transformaci lze napsat např. v 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 python_functional.tgz 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.




Nahoru