Kódujte funkčně v Pythonu: Naučte se sílu funkčního programovacího paradigmatu. Svět Pythonu: funkčnost v malých směrech - Pokročilý Python - Hexlet. Python v akci

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 plnohodnotné funkcionální programování v Pythonu jsou: funkce vyššího řádu, vyvinuté nástroje pro zpracování seznamů, rekurze a 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í, 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é
  • Snadné sledování ž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 zadané v seznamu a slovníku (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í neustále (tj. stejné), 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 si zopakovali základní koncepty FP a také jsme sestavili 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ý vytvoří anonymní funkci na létat a vrátí to. 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é vyžadují od uživatelů
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. Jaké 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ý se zde řeší, 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. Kód funkce se liší jednou vlastností: nepřítomností vedlejších účinků. Nespoléhá se na data mimo aktuální funkci a nemění data mimo tuto funkci. Z toho lze 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ří novou kolekci, provede funkci na každé pozici dat a přidá návratovou hodnotu k nové kolekci. 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ých agentů. 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 je funkční verze tohoto programu:

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ým písmenem na základě title() a původního názvu. 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í, zejména 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.

2010-11-17 09:47

mapové funkce, zip a lambda (mimochodem se jim říká „funkce vyššího řádu“ nebo „funkce první třídy“) vám umožňují provádět různé manipulace s daty docela jednoduše, což vyžaduje napsat trochu více kódu v „běžném“ postupu. styl. Vše napsané níže se týká tzv. funkcionálního programování, hledejte podrobnosti.

V příkladech mapa funkcí, zip a lambda.

Jednoduchým úkolem je seznam a = a seznam b = o stejné délce a je potřeba je sloučit do dvojic. Je to stejně snadné jako loupání hrušek – pomocí funkce zip :

a = [ 1 , 2 ] b = [ 3 , 4 ] tisknout zip (a , b ) [(1 , 3 ), (2 , 4 )]

nebo v trojicích:

a = [ 1 , 2 ] b = [ 3 , 4 ] c = [ 5 , 6 ] tisk zip (a , b , c ) [(1 , 3 , 5 ), (2 , 4 , 6 )]

nebo obecněji

seznam = [a, b, c] tisk zip (* seznam) [(1, 3, 5), (2, 4, 6)]

Zdá se, že hvězdička * před seznamem označuje, že se předává seznam argumentů, tj. Herectví je ekvivalentní předávání a, b, c tzn. Je to dokonce možné tisk zip(*) výsledek se nezmění.

def f (x): návrat x * x nums = [ 1 , 2 , 3 ] pro num v nums : tisk f (num )

Zkušenější noob, který studoval porozumění seznamům:

def f (x): return x * x tisk [ f (num) pro num in nums ]

Programátor to usnadní:

def f (x): návrat x * x tisk mapy (f, nums)

A hacker se skutečnými dovednostmi to udělá následovně (samozřejmě za předpokladu, že funkci lze zapsat jako lambda; funkce nebude vždy dostatečně jednoduchá, aby byla zapsána jako lambda):

tisknout mapu (lambda x : x * x , nums )

Poslední položka je příkladem nejkompetentnějšího přístupu. Faktem je, že když člověk píše kód jako poezii, v návalu inspirace (který lze jinými slovy nazvat „v divokém šílenství“), je rychlost psaní nesmírně důležitá (zde jsou kořeny uctivé lásky mnoha vývojářů pro jednoduché textové editory vim, emacs, sublimetext grow) a síla Pythonu je přesně ve velikosti generovaného kódu - je velmi kompaktní. Zápis jednoho řádku je přirozeně rychlejší než 7 a čtení krátký kód jednodušší, ale psaní takového kódu vyžaduje určitou dovednost. Druhou stranou mince je, že někdy jsou v tomto „divokém šílenství“ napsány celé sekvence poměrně složitých akcí na jeden řádek, až je velmi těžké pochopit, co se tam děje a co se nakonec stane.

Z příkladu je zřejmé, že mapa aplikuje funkci na seznam a vrátí výsledek, opět ve formě seznamu. Můžete předat několik seznamů, pak funkce (což je první parametr) musí mít několik argumentů (podle počtu seznamů předávaných mapa).

def f (x, y): návrat x * y a = [1, 3, 4] b = [3, 4, 5] tisk mapy (f, a, b) [3, 12, 20]

V pohodě, že?

Pokud však seznamy různé délky, tj. Jedna je kratší než druhá, pak bude doplněna hodnotami None na požadovanou délku. Pokud je odstraněn ze seznamu b poslední hodnota - příklad nebude fungovat, protože Ve funkci f dojde k pokusu o vynásobení čísla None a to Python neumožňuje, čímž se mimochodem příznivě odlišuje od php, které by v podobné situaci fungovalo dál. Pokud je tedy funkce f dostatečně velká, bylo by dobré zkontrolovat předávané hodnoty. Například;

Pokud je funkce nahrazena None, pak mapa funguje zhruba stejně jako zip, ale pokud jsou přenášené seznamy různě dlouhé, bude výsledek napsán None - což je mimochodem v některých případech velmi vhodné.

a = [ 1 , 3 , 4 ] b = [ 3 , 4 ] tisknout mapu (Žádné , a , b ) [(1 , 3 ), (3 , 4 ), (4 , Žádné )]

Nyní o lambda funkce v pythonu. Používají se, když potřebujete definovat funkci bez použití def func_name(): ..., protože často (jako v předchozích příkladech) je funkce tak malá, že nemá smysl ji definovat samostatně (řádky kódu navíc zhoršují čitelnost ). Proto lze funkci definovat „na místě“ f = lambda x: x*x jako by nám říkal - přijímá x, vrací x*x

Takže pomocí standardní nástroje Python umí psát poměrně složité akce na jeden řádek. Například funkce:

def f (x , y ): if (y == Žádné ): y = 1 návrat x * y

může být reprezentován jako:

lambda x , y : x * (y pokud y není Nikdo jiný 1 )

Nyní by bylo hezké předat seznamy seřazené podle délky - len(a) > (b) - stejně snadné jako loupání hrušek - použijme funkci seřazeno :

seřazeno ([ a , b ], klíč = lambda x : len (x), obráceně = Pravda )

funkce seřazena vezme seznam hodnot ( = [,]) a seřadí podle klíče - což je dáno funkcí len(x) - vrátí délku seznamu seřazenou v sestupném pořadí (reverse=True)

Nakonec je celá operace napsána takto:

mapa (lambda x , y : x * (y, pokud y není Žádná jiná 1 ), * seřazeno ([ a , b ], klíč = lambda x : len (x), obráceně = Pravda ))

seznamy a a b mohou mít různou délku a přenášet se v libovolném pořadí. Lambda výrazy jsou užitečné pro definování nepříliš složitých funkcí, které se pak předávají dalším funkcím.




Nahoru