Disassembler

Artificial intelligence is no match for natural stupidity.
14září2015

Novinky v Pythonu 3.5


Včera byla vydána nová verze mého nejoblíbenějšího multiparadigmatického a multiplatformího skriptovacího jazyka. Vývojová větev Pythonu 3.x je tu s námi od roku 2008, ale jeho počátky sahají daleko hlouběji, až někam do roku 1991. Za tu dlouhou dobu se mu podařilo vyrůst do krásy a použitelnosti a drtivá většina věcí v něm dává perfektní smysl, což se za tu dobu spoustě jiných jazyků nepodařilo (Koukám se na tebe, Javo).

PEP talk


Python definuje své funkce, podobu jazyka, způsob zápisu a zacházení s kódem pomocí PEP (Python enhancement proposals). Díky tomu je relativně jednoduché nevyhovující část implementace vzít a upravit, případně od základu přepsat. Tato metodika se v praxi již osvědčila. Určitě jste už slyšeli o nějaké IETF a jejich RFC. Díky této sérii jasných definic je pak možno implementovat Python nad různými nižšími programovacími jazyky. V současnosti jsou nejrozšířenějšími implementacemi CPython, běžící nad C, Jython, fungující nad Javou, IronPython, využívající .NET nebo Mono a PyPy, což je takový zajímavý kousek, protože jde vlastně o Python implementovaný v Pythonu (někde vzadu v rohu se ale však stále krčí C).

Další perfektní věc je, že Python programátora nutí udržovat si pořádek v kódu, protože místo klasických uzávěr { a } využívá velikosti odsazení k tomu, aby zjistil, které řádky jsou v těle funkce nebo podmínky a které už jsou mimo. Pominu-li jazyk samotný, tak už jen z tohoto důvodu si myslím, že je pro výuku začínajících programátorů daleko vhodnější než nějaký zkostnatělý Pascal, protože si díky němu začátečníci osvojí dobré návyky, použitelné i v dalších jazycích. Samozřejmě nesmím opomenout vychválit list comprehension, což je syntaktický cukr, usnadňující práci s datovými strukturami, který v praxi velice často využívám. Pokud jazyk nic takového nemá, většinou se musím zalamovat s lambda expressions nebo ještě hůř, s obyčejným ošklivým plnotučným for cyklem.

Co mi naopak v CPython a PyPy implementacích (což jsou jediné, o kterých si mysl, že je má smysl provozovat na serverech) vadí, je kurvítko s akronymem GIL. Global Interpreter Lock je kostlivec ve skříni, kterého tu nechala původní CPython implementace, jejíž správa paměti nebyla thread-safe. Z toho důvodu musel existovat zámek (mutex), který zajišťoval, že bajtkód nemůže být zpracováván více vlákny zároveň, aby nedocházelo souběhům. A modří už vědí. Takováto brzda na úrovni interpreteru pak neodmyslitelně znamená obrovskou ztrátu výkonu u vícevláknových aplikací, protože efektivně umožňuje běh pouze jednoho vlákna v daný okamžik, a to i na vícejádrových systémech. Nutno podotknout, že problém je viditelný pouze u vláken, která se zrovna snaží provádět kód, takže čekání na I/O a další věci, odehrávající se mimo interpret Pythonu, postiženy nejsou. Tento nedostatek se dá obejít využitím multiprocessing modulu, který místo vláken vytvoří celé podprocesy, každý s vlastním GILem a data mezi nimi par přehazuje pomocí front a rour. Tím si však programátor zadělá na celou řádku nových neočekávaných situací.

Harder, better, faster, stronger


Co je tedy v Pythonu 3.5 nového, co by stálo za řeč? Tak třeba nová syntaxe pro asynchronní programování. Představte si scénář, kdy uživatel klikne na čudlík a spustí tím nějaký časově náročný proces. Okno mu zašedne a systém zahlásí, že aplikace neodpovídá. Komu se něco podobného v jeho programátorských začátcích nestalo, ať zvedne ruku. Problém je, že okýnko aplikace běží ve stejném vláknu jako onen časově náročný proces. Normálně se taková věc řeší použitím různých workerů (v Pythonu pak použitím modulu asyncio a jeho coroutines), které vytvoří nové vlákno, udělají si svou práci a nějakým callbackem pak dají vědět, že jest dokonáno. Tenhle typ problému je třeba v reálném světě řešit prakticky neustále, takže si nějaká chytrá hlava řekla, že by se na tadyto vícevláknové drbání hodil nějaký syntaktický cukr. Nevím, v jak hluboké minulosti se tenhle nápad zrodil, ale já jej využívám od té doby, co se vyskytl v .NET 4.5. Metodu, která by načítala data z databáze a následně je zpracovávala, by se naivně řešila třeba takto

def read_data():
    data = db.fetch('SELECT ...')
    # zpracuj data

Ale díky nové syntaxi je ji možno zapsat neméně složitě

async def read_data():
    data = await db.fetch('SELECT ...')
    # zpracuj data

Čímž je zajišťeno, že v případě zácpy v databázi nezamrzne celá aplikace, ale funkce bude na vedlejším vlákně poslušně čekat, dokud nedostane data (nebo výjimku z databázového ovladače), zatímco ostatní komponenty aplikace pojedou vesele dál.

Dalšími změnami v syntaxi jsou pak přidání operátoru pro násobení matic (pro mě relativně nezajímavé, ale někoho určitě potěší) a povolení vícenásobného rozbalování parametrů funkcí. Pokud jste například potřebovali do funkce předat keyword parametry z dvou různých slovníků, museli jste je předtím explicitně sloučit.

kwargs = dict(kw_arguments)
kwargs.update(more_arguments)
function(**kwargs)

V Pythonu 3.5 už si ale můžete pár řádků ušetřit a zlepšit tak čitelnost svého kódu.

function(**kw_arguments, **more_arguments)

Pak bych třeba zmínil nový modul pro type hinty. Doposud bylo možno hintovat skrze anotace, ale neexistoval žádný univerzálně uznávaný způsob. Nyní se dá zapsat

def greeting(name: str) -> str:
    return 'Hello ' + name

a hned bude jasné, jaký typ parametru se očekává a jaký bude funkcí vrácen. Je důležité zmínit, že na rozdíl třeba od PHP, bude Pythonu úplně fuk, jaký datový typ tam ve finále přistane, protože neprovádí žádnou runtime analýzu. Tu ponechává na externí nástroje, jakým je třeba mypy.

Dále stojí za zmínku optimalizace funkce os.walk(), resp. zbrusu nová funkce os.scandir(), jež walk na pozadí využívá. Některá systémová volání se totiž při procházení adresářů prováděla nadbytečně, takže na linuxu by teď měl být os.walk() 2-3x rychlejší, na Windows dokonce až 9x. OrderedDict je na tom dokonce ještě lépe, protože byl přepsán do C, takže práce s ním je nyní 4-100x rychlejší. SSL modul, mimo to, že mu ve výchozím stavu vypnuli SSLv3, byl zproštěn vazeb na síťový buffer a bylo mu umožněno hrát si s daty jen v paměti. A úplně na závěr zmíním ještě novinky v subprocess modulu, kde přibyla nová třída CompletedProcess a metoda run(), která nahrazuje metody call(), check_call() a check_output() a instanci této třídy vrací, čímž zjednodušuje a zefektivňuje celou práci se spouštěním externích procesů a jejich následným sběrem.

Sečteno a podtrženo, Python 3.5 přináší pouze samé dobré věci, ale s updaty na produkčních serverech bych ještě pár dní počkal, protože už v tuto chvíli je do verze 3.5.1 připraveno víc jak 15 drobných oprav a dodělků a v následujících dnech ještě možná něco přibude.

Celou zprávu o novinkách v Pythonu 3.5 si pak můžete přečíst přímo u zdroje.