Python profesionálně: dynamické parametry, generátory, lambda funkce... with. Dynamické parametry



Podobné dokumenty
Programování v Pythonu

Funkce, podmíněný příkaz if-else, příkaz cyklu for

SPJA, cvičení 1. ipython, python, skripty. základy syntaxe: základní datové typy, řetězce. podmínky: if-elif-else, vyhodnocení logických výrazů

Chyby a výjimky. Chyba. Odkud se chyby berou? Kdo chyby opravuje? Co můžete dělat jako programátor? Dvě hlavní metody práce s chybami.

IB111 Úvod do programování skrze Python Přednáška 7

Obsah. Část I Začínáme s jazykem AppleScript

10. Editor databází dotazy a relace

Funkce - opakování. Funkce může přijímat parametry na vstupu a může vracet parametry na výstupu.

Kolekce ArrayList. Deklarace proměnných. Import. Vytvoření prázdné kolekce. napsal Pajclín

Šablonovací systém htmltmpl vypracoval: Michal Vajbar, Šablonovací systém htmltmpl

Programování v Pythonu

20. Projekt Domácí mediotéka

3. Řízení běhu programu

Django, 2. cvičení url, views, templates. Úvod

Helios RED a Internetový obchod

Minebot manuál (v 1.2)

Vestavěné nástroje Pythonu

Statické proměnné a metody. Tomáš Pitner, upravil Marek Šabo

Jazyk C# (seminář 5)

Psaní jednoduchých programů (2. přednáška) F F U K. Jonathan L. Verner. Department of Logic

PREPROCESOR POKRAČOVÁNÍ

Programování v Pythonu

Projekty pro výuku programování v jazyce Java

Naučit se, jak co nejsnadněji přejít od verze TopoLu pro Windows k verzi TopoL xt. Cílem není vysvětlení všech možností programu.

2. blok část B Základní syntaxe příkazů SELECT, INSERT, UPDATE, DELETE

Miroslav Adamec, ARAS: JUDr. Jiří Srstka, DILIA:

Manuál k užívání aplikace Monitoringrejstriku.cz

Úvod do PHP s přihlédnutím k MySQL

Čtvrtá část odpovědi aneb jak je to vlastně s interakcí <<include>>

Jazyk C# (seminář 3)

Formuláře. Internetové publikování

Dědění, polymorfismus

Interpret jazyka IFJ2011

Konečný automat. Jan Kybic.

Motivace. Vstup a výstup. Minimální komunikace. Motivace. ÚDPJ - Vstup a výstup. Ing. Lumír Návrat katedra informatiky, A

Textové popisky. Typ dat

Java a Caché IV: Manipulace s objekty

Přechod webových aplikací na Python 3 Tomáš Pazderka 6. listopadu 2018

Programování v Javě I. Leden 2008

Programování: základní konstrukce, příklady, aplikace. IB111 Programování a algoritmizace

Deník mých kachních let. Září. 10. září

Více o konstruktorech a destruktorech

Stravenky Exit. 1. Spuštění modulu Stravenky Exit

KAPITOLA 3. Architektura aplikací na frameworku Rails. V této kapitole: modely, pohledy, řadiče.

Programování v Javě I. Únor 2009

CADKON/TZB verze

Rozptylovací tabulky

Intervalové stromy. Představme si, že máme posloupnost celých čísel p 0, p 1,... p N 1, se kterou budeme. 1. Změna jednoho čísla v posloupnosti.

Knihomol. Manuál pro verzi 1.2

PL/SQL. Jazyk SQL je jazykem deklarativním, který neobsahuje procedurální příkazy jako jsou cykly, podmínky, procedury, funkce, atd.

8 Třídy, objekty, metody, předávání argumentů metod

2 Základní funkce a operátory V této kapitole se seznámíme s použitím funkce printf, probereme základní operátory a uvedeme nejdůležitější funkce.

Spojový seznam. Jan Kybic.

IB111 Základy programování Radek Pelánek

Využití OOP v praxi -- Knihovna PHP -- Interval.cz

Uživatelská příručka pro dodavatele

SEZNÁMENÍ S PROGRAMEM

Generování žádostí o kvalifikovaný certifikát a instalace certifikátu Uživatelská příručka pro prohlížeč Internet Explorer

IB111 Úvod do programování skrze Python

MŮJ STRACH. Nejstrašnější bída je samota a pocit, že mě nikdo nepotřebuje. - Matka Tereza

Můj strach. Nejstrašnější bída je samota a pocit, že mě nikdo nepotřebuje.

Obsah. Začínáme pracovat v InventorCAMu - frézování SolidCAM All Rights Reserved.

VAR-NET INTEGRAL Manuál správce VNI 5.1 VAR-NET INTEGRAL. verze 0.2. Manuál správce VNI 5.1

PB161 Programování v jazyce C++ Přednáška 4

PSK3-9. Základy skriptování. Hlavička

Pokročilé programování v jazyce C pro chemiky (C3220) Pokročilá témata jazyka C++

Metodická příručka pro učitele. InspIS SET modul školní testování

Kámen-nůžky-papír. Tomáš Svoboda Centrum strojového vnímání, Katedra kybernetiky Fakulta elektrotechnická, České vysoké učení technické

Připravil: Ing. Jiří Lýsek, Ph.D. Verze: Webové aplikace

Koordinační středisko pro resortní zdravotnické informační systémy

1) Sémantika: operační sémantika, denotační sémantika, pevný bod funkce, vázání jmen, stav a data programu.

DSL manuál. Ing. Jan Hranáč. 27. října V této kapitole je stručný průvodce k tvorbě v systému DrdSim a (v

Cykly. Základy programování 1 Martin Kauer (Tomáš Kühr)

Scénář ukázkového testu Přetištěno z knihy Nenuťte uživatele přemýšlet! 2010 Steve Krug

2) Napište algoritmus pro vložení položky na konec dvousměrného seznamu. 3) Napište algoritmus pro vyhledání položky v binárním stromu.

Pokročilé programování v jazyce C pro chemiky (C3220) Třídy v C++

IAJCE Přednáška č. 8. double tprumer = (t1 + t2 + t3 + t4 + t5 + t6 + t7) / 7; Console.Write("\nPrumerna teplota je {0}", tprumer);

Postup pro podání návrhu projektu studentské grantové soutěže

Scrapping stránek prakticky: Dívka dne na TN.cz

a autentizovaná proxy

Rozdílová dokumentace k ovládání IS KARAT.net

Teoretické minimum z PJV

Distanční opora předmětu: Programování v jazyce C Tématický blok č. 8: Dynamické datové struktury, ladění programů Autor: RNDr. Jan Lánský, Ph.D.

Informační systémy 2008/2009. Radim Farana. Obsah. Skripty a dávky. Nastavení aktuální databáze. USE DatabaseName

Programování II. Polymorfismus

PHP tutoriál (základy PHP snadno a rychle)

Zvýšení zabezpečení počítače

PHP a Large Objecty v PostgreSQL

Kámen-nůžky-papír. Tomáš Svoboda Centrum strojového vnímání, Katedra kybernetiky Fakulta elektrotechnická, České vysoké učení technické

Technologie počítačových sítí 1. cvičení

Programování v Pythonu

Návod k ovládání administrační části nového turistického portálu Olomouckého kraje

Generické programování

Návod k administraci e-learningové platformy

JavaScript 101. "Trocha života do statických stránek"

Třídy a struktury v C++

Použití databází. Mnoho postupů, které si ukážeme pro prací s formulářů využijeme i při návrhu tiskových sestav.

JAVA. Další jazyky kompilovatelné do Java byte-code

Uživatelský manuál Radekce-Online.cz

Transkript:

1 z 9 09.11.2015 10:02 Python profesionálně: dynamické parametry, generátory, lambda funkce a with Články - Michal Hořejšek (https://www.zdrojak.cz/autori/michal-horejsek/) - Různé (https://www.zdrojak.cz/ruzne/) - 10.4.2012 V minulém díle jsme se podívali na několik jednoduchých syntaktických tipů, které nám usnadní vývoj v programovacím jazyce Python. Dnes navážeme generátory, lambda funkcemi, with konstrukcemi a dynamickými parametry. Dynamické parametry Na předchozí článek (https://www.zdrojak.cz/clanky/python-profesionalne-uvod/) navážeme další zajímavostí Pythonu. Jedná se o neznámý počet parametrů. To určitě všichni známe a ti, kteří si četli minimálně nějaký tutoriál, na to i narazili. Jedná se o hvězdičku v parametru funkce/metody. Ale věděli jste, že to jde na obou místech; jak ve volání, tak v definici? def f(first, second, *rest): print first, second, rest f(*range(1, 6)) # 1 2 (3, 4, 5) Jak je vidět, stačí přidat hvězdičku a podle kontextu se seznam rozloží nebo složí. To je užitečné například pro funkce k formátování řetězců, sumarizační funkce a podobně. Ale co kdybych chtěl udělat vyhledávající funkci, která využívá pojmenovávaných parametrů? A samozřejmě bych nechtěl definovat všechny možné parametry ručně I na to Python myslí a řešením je jak jinak než další hvězdička. def search(**kwds): map(check_search_key, kwds.keys()) # Do something... def check_search_key(key): if key not in ('id', 'name', 'mail', 'url'): raise AttributeError('You can't search by key "%s".' % key)

2 z 9 09.11.2015 10:02 params = {'first': 1, 'second': 2} f(**params) Mimochodem u těchto speciálních parametrů, pokud není vhodnější název pro konkrétní situaci, se většinou používají názvy args a kwds. Je to něco jako parametry self a cls u metod. Kvíz: co se stane? dict(params, **{'first': 0, 'third': 3}) Řešení: Built-in type dict se slovníkem v parametru vytváří jeho mělkou kopii. Pomocí této funkce lze vytvořit slovník i přes pojmenované parametry, kde název parametru je klíč a hodnota je (nečekaně) hodnota. (A také lze vytvořit slovník pomocí seznamu obsahující položky opět typu seznam s dvěma položkami první se použije jako klíč a druhá jako hodnota.) Pokud tyto vlastnosti sloučíme, tak nejprve vytvoříme kopii slovníku params a poté tento nově vytvořený slovník updatneme druhým slovníkem, který však musíme předat jako pojmenované parametry. Je to vlastně to samé jako následující. dict(params, first=0, third=3) A proč jsem to napsal předtím se slovníkem? Nu protože se mi zdá čitelnější první varianta, kde vidím dva slovníky a ne slovník a nějaké pojmenované parametry. Navíc klíče mohou nabývat obecných názvů, a to může být matoucí. A navíc pomocí této syntaxe lze mergnout reference dvou slovníků a ani jeden nezměnit. Výsledek bude tedy takovýto: {'second': 2, 'third': 3, 'first': 0} Generátory Když jsem ještě nebyl v Pythonu zběhlý, považoval jsem generování seznamů za špatnost. Protože se mi to nezdálo přehledné. Jenže Python není jako každý jiný jazyk je potřeba si na něj zvyknout a pak zjistíte, že je v některých věcech bezvadný. Dnes už mám generátory rád. Když někomu ukazuji Python, tak se strašně rád ptám, jak by ta dotyčná osoba udělala dvojrozměrné pole s třeba malou násobilkou. A pak ukážu, jak bych to dělal já.

3 z 9 09.11.2015 10:02 s = [] for x in range(11): row = [] for y in range(11): row.append(x*y) s.append(row) # vs. s = [[x*y for y in range(11)] for x in range(11)] Jednou se mi stalo, že se pak dotyčná osoba zeptala, jak bych tam přidal podmínku, kdybych chtěl třeba jenom řádky se sudými čísly. Prý bych teď určitě musel celý kód přepsat na normální cyklus a přidat podmínku. Tak jsem tedy ukázal, jak to musím přepsat s = [[x*y for y in range(11)] for x in range(11) if x % 2 == 0] Dál už se mě nikdo raději na nic nezeptal, ale to neznamená, že toho není víc! Nevýhoda generování seznamů (list comprehension) je v tom, že se musí celé vytvořit v paměti. Představme si problém: potřebujeme zinicializovat hodně moc instancí produktů, každý nějak zpracovat a po zpracování zahodit, protože není dále potřeba (vhodné například pro vygenerování XML souboru). Ale inicializace a zpracování z nějakých důvodů nelze mít na jednom místě (třeba MVC) a je tedy potřeba předat referenci na seznam s těmito instancemi. def get_products(): return [Product(product_id) for product_id in range(1000, 2000, 2)] def do_something_with_products(products): # Do something... do_something_with_products(get_products()) Toto řešení by v paměti vytvořilo zbytečně 500 instancí. Je lepší způsob a stačí jen vyměnit typ závorek. Místo hranatých použijeme kulaté. Tím se nevytvoří seznam s instancemi, ale pravý generátor, který lze použít v iteraci a kód pro každý prvek se vyvolá, až když je opravdu potřeba. Tedy instance produktu se zavolá vždy až když je ten produkt potřeba; nikoliv že se vytvoří nejprve všechny a pak se přes ně jen iteruje. Tím tedy v paměti budu mít pouze jednu instanci (pokud na produkt nevytvořím jinou proměnnou s referencí). l = [x for x in range(10)] # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] g = (x for x in range(10)) # <generator object <genexpr> at 0xb6d5b4dc>

4 z 9 09.11.2015 10:02 Má to ale své úskalí. Kdybych chtěl produkty řadit, tak nemohu, s generátorem to nelze. Generátor iteruje tak, jak byl napsán a pořadí nelze změnit. Takže generátor lze použít jen tehdy, když chci položkami iterovat v nezměněném pořadí nebo když ho chci používat s klíčovým slovem in. l[:4] # [0, 1, 2, 3] g[:4] # TypeError l[::-1] # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] g[::-1] # TypeError l.reverse() # ok g.reverse() # AttributeError for item in l: pass # ok for item in g: pass # ok (item for item in l if item < 5) # ok (item for item in g if item < 5) # ok if 5 in l: pass # ok if 5 in g: pass # ok Python obsahuje spoustu generátorů. Už několikrát jsem v ukázkách použil built-in funkci range, která vrací seznam s čísly, která žádám. Existuje k ní analogická funkce, která vrací něco na způsob generátoru a jmenuje se xrange (ve skutečnosti vrací xrange, který se od generátoru liší tím, že nemá metody next a podobně). range(4) # [0, 1, 2, 3] xrange(4) # xrange(4) Za zmínku stojí, že v Pythonu 3 funkce xrange už není a range se chová jako xrange. Když je v Pythonu 3 opravdu potřeba seznam místo iterátoru, tak se musí zavolat list(range(*args)). Jak by tedy vypadal ve výsledku náš problém (pouze změna v jedné funkci): def get_products(): return (Product(product_id) for product_id in xrange(1000, 2000, 2)) Od Pythonu 3 přibudou dva noví kolegové pro jednoduší generování slovníků a množin.

5 z 9 09.11.2015 10:02 # Generovani slovniku v Pythonu 2. dict((x, x**2) for x in range(10)) # Nove generovani slovniku v Pythonu 3 (predchozi samozrejme funguje taky). {x: x**2 for x in range(10)} # Generovani mnoziny v Pythonu 2. set(x for x in range(10)) # Nove generovani mnoziny v Pythonu 3 (predchozi samozrejme funguje taky). {x for x in range(10)} Na závěr si ještě jednou všechny generátory roztřídíme, ať v tom máme jasno. První ukázaný (generátor seznamů) lze v češtině spatřit také jako generátorová notace seznamů a v angličtině to je list comprehensions, případně dictionary comprehensions a set comprehensions přidané v Pythonu 3. Další ukázaná syntaxe byla generátorová notace, v angličtině generator expressions a slouží pro snadné vytvoření generátoru. Poslední ukázkou byl xrange, který sice nevrací generátor, ale chová se tak. Aby to nebylo tak snadné v Pythonu existuje ještě tzv. iterátor, přičemž generátor implementuje rozhraní iterátoru a navíc ho o něco rozšiřuje. O co konkrétně si můžete přečíst v dokumentaci (http://docs.python.org/library/stdtypes.html#iteratortypes). A téměř cokoliv lze převést na iterátor pomocí built-in funkce iter (http://docs.python.org/library/functions.html#iter). Vlastní generátory Generátory jsou hezké, dají se šikovně použít. Ale neřeší situace, kde potřebuji vyřešit velmi specifický problém. Například máme seznam instancí produktů a každý může mít načteny jen základní nebo kompletní informace zákazník si objednal několik produktů a ve svém účtu se dívá na stav své objednávky. My máme v databázi informace o produktech (ID, název, cena, ), ale informace o pohybu produktů si načítáme z externího systému. Naneštěstí načítání z externího systému není nejrychlejší, a tak se tomu chceme co nejvíce vyhnout. Takže zákazníkovi zobrazíme jen základní informace a až když si klikne na detail, načteme informace z externích systémů. Máme tedy seznam instancí a každý produkt může být v jiném stavu. Některé jsou načteny kompletně a některé mají jen základní informace o sobě. A teď je situace, kde potřebuji mít načteny kompletně všechny. Jedna možnost je na začátku každé iterace se zeptat, v jakém stavu produkt je, a případně produkt donačíst. A tento kód kopírovat na místa, kde to je všude potřeba. Zřejmě už cítíte, že to není dobré. DRY!. Proto si na to vytvoříme vlastní generátor, je to jednoduché. Napíšeme normální funkci, jen místo klíčového slova return použijeme yield a nepoužijeme ho až na konci funkce, ale v iteraci.

6 z 9 09.11.2015 10:02 def iterate_and_load_over_products(products): for product in products: if not product.is_fully_loaded(): product.load() yield product for product in iterate_and_load_over_products(products): Výhoda spočívá v tom, že se nemusí čekat, až se všechny produkty načtou, a produkt se může zpracovávat ihned, jakmile je kompletně načten. Jinými slovy nemusím půl hodiny čekat, než se mi vše načte, aby mi program spadl na nějaké chybě v kódu se zpracováním dat. Představme si situaci: přijde produkťák a zeptá se nás, za jak dlouho to bude hotové a my odpovíme, že teď se bude půl hodiny něco načítat a samotné zpracování potom bude otázka pár vteřin. Skript půl hodiny jede a pak najednou spadne na chybě, na kterou se v testovacím prostředí nenarazilo Anonymní lambda funkce Jsou situace, kde je potřeba funkce na jedno použití někde uvnitř jiné funkce. Deklarace funkce ve funkci nevypadá moc hezky (ale nebráním se tomu) a proto existují lambda funkce. square = lambda x: x**2 square(5) # 25 Je to velmi jednoduché. V ukázce jsem vytvořil anonymní funkci, která přijímá jeden parametr a vrací výsledek výrazu za dvojtečkou. Referenci na funkci jsem si uložil do proměnné square a hned na dalším řádku použil. Lambda funkce může přijímat parametrů, kolik je libo (oddělené čárkami) a dokonce nemusí být žádný (kde se dá smysluplně využít takové funkce si ještě povíme). Jednodušeji: platí to, co pro normální funkce. Výsledek funkce je vždycky výsledek výrazu, který smí být jen jeden; ale je jedno, jak moc složitý. Toť vše, už jen přidám nějaké další ukázky. # Opravdu anonymni funkce. (lambda x: x**2)(5) # Moznost vytvorit ve volani jine funkce jako parametr. dates = [datetime.date(year, 1, 1) for year in range(2000, 2020)] dates.sort(cmp=lambda x, y: cmp(x.isoweekday(), y.isoweekday())) # Dalsi podobne pouziti. map(lambda d: d.isoformat(), dates) A ještě na ně narazíme

7 z 9 09.11.2015 10:02 Konstrukce with Největší problém jsou opakující se kusy kódu. Ještě horší je, když jsou velmi důležité. A nejhorší je, když se jedná o kusy kódu, které se provedou jednou za čas, typicky odchytávání chyb a podobně. Dobře to může být vidět při práci se soubory, kde bychom se určitě neměli spoléhat na to, že se stream sám zavře a data fyzicky zapíšou na disk. try: f = open(filename, 'a') f.write('xyz') except IOError, e: f.close() else: f.close() Zkuste si takhle řešit více různých zápisů a čtení ze souboru; zblázníte se z toho. A poměrně s vysokou pravděpodobností se i dopustíte chyby. Python 2.6 přidává novou vlastnost, která s tímto problémem pomůže a jedná se o context managers. try: with open(filename, 'a') as f: f.write('xyz') except IOError, e: else: Sice je to v tomto konkrétním případě více psaní, ale už určitě nezapomeneme na zavření streamu. Důvěřujme, ale prověřujme: f = open('asdf', 'w') with f: f.write('xyz') f.write('abc') # ValueError: I/O operation on closed file Z ukázky si všimněte dvou věcí první: není potřeba použít as a hlavně té druhé: po vyskočení z bloku with se soubor automaticky zavřel. Zavřel by se, ať už by to skončilo v pořádku či nikoliv (= vyhozením výjimky). Celé je to možné díky tomu, že file objekt (který vrací funkce open ) má implementované speciální metody enter a exit. První metoda nastavuje (cokoliv nás napadne) před vstupem do bloku with a vrací to, co se nám zrovna může

8 z 9 09.11.2015 10:02 hodit (a nemusíme to použít, když nepotřebujeme). Druhá metoda se volá po dokončení bloku with a jak už jsme si řekli, zavolá se v jakémkoliv případě ať už vše proběhlo v pořádku a nebo ne. S těmito vědomostmi si můžeme vytvořit jakoukoliv třídu, kterou lze využít s klíčovým slovem with. Například jsem si udělal třídu Transaction pomáhající mi s transakcemi, abych je nemusel neustále vytvářet a commitovat, případně rollbackovat. Ukázka je zjednodušená: class Transaction(object): """Transaction object for use in with statement.""" def init (self, dbconnection): self._dbconnection = dbconnection def enter (self): self._dbconnection.transaction() cur = self._dbconnection.cursor() return cur def exit (self, type_, value, traceback): if type_ is None: self._dbconnection.commit() elif issubclass(type_, DatabaseException): self._dbconnection.rollback() else: self._dbconnection.rollback() with Transaction(singleton.dbconnection.master) as cur: sql = sqlpuzzle.insertinto('city').values({'name': 'Springfield'}) cur.execute(str(sql)) Modul sqlpuzzle z ukázky je k nalezení na GitHubu (https://github.com/horejsek/python-sqlpuzzle) a lze nainstalovat z PyPI příkazem pypi-install sqlpuzzle. Poznámka: with můžete použít už od Pythonu 2.5, ale musíte si tuto vlastnost zpřístupnit přes speciální import. Tento import však musí být jako úplně první kód v souboru. from future import with_statement Závěr

9 z 9 09.11.2015 10:02 jeho built-in funkce, oficiální moduly a další. Dnes opět zakončím odkazy se zajímavým čtením k dalšímu studiu: Více o dynamických parametrech a dalších tipech k funkcím v oficiální dokumentaci (http://docs.python.org/tutorial /controlflow.html#more-on-defining-functions). Generování seznamů (http://docs.python.org/release/2.7/tutorial/datastructures.html#list-comprehensions) a slovníku (http://docs.python.org/release/3.2/tutorial/datastructures.html#dictionaries) z oficiální dokumentace. Vlastní generátory ( yield ) v oficiální dokumentaci (http://docs.python.org/release/2.5.2/ref/yield.html). Spousta ukázek (http://www.secnetix.de/olli/python/lambda_functions.hawk) použití lambda funkce. with konstrukce (context managers) v oficiální dokumentaci (http://docs.python.org/reference /compound_stmts.html#the-with-statement). with konstrukce popsána trochu jinak (http://effbot.org/zone/python-with-statement.htm). Michal Hořejšek (https://www.zdrojak.cz/autori/michal-horejsek/) Michal dělá team leadera v Seznam.cz a hraje si na BOObook.cz. Jeho nejoblíbenějším jazykem je Python, ale nevadí mu třeba ani JavaScript a rád zkouší nové jazyky i technologie. Ve volném čase cestuje, fotí, píše (http://blog.horejsek.com), ale taky plave, jezdí na kole či tancuje. (http://www.horejsek.com/) (http://www.twitter.com/horejsek) Věděli jste, že nám můžete zasílat zprávičky (https://www.zdrojak.cz/zpravicky-new)? (Jen pro přihlášené.) Zdroj: https://www.zdrojak.cz/?p=3623