Na konci června byl vydán Python 3.7. Pojďme se spolu podívat na vybrané novinky, které tato nová verze Pythonu přináší.
Oficiální shrnutí změn je zde. Úplný seznam pak lze nalézt v changelogu. V textu níže předpokládám znalost Pythonu 3.6.
Odložené vyhodnocování anotací
V Pythonu je možné anotovat funkce (od verze 3.0) a proměnné (od verze 3.6) přiřazením dodatečných informací. Jedno z možných využití anotací jsou od Pythonu 3.5 tzv. type hints, které umožní analyzátorům jako je mypy provést typovou kontrolu, aniž by muselo dojít ke spuštění programu (statická typová kontrola). Příklad z PEP 484:
def greeting(name: str) -> str: return 'Hello ' + name
Než se začnete radovat/děsit, vězte, že Python zůstává nadále dynamicky typovaným jazykem a typové anotace jsou pouze volitelné a nemají žádný vliv na výsledek běhu programu.
Po zavedení type hints se narazilo na dva problémy s anotacemi:
- V anotacích lze používat pouze identifikátory, které byly v době definice anotované funkce/proměnné dostupné. Nebylo tedy možné používat např. typy, jejichž definice se ve zdrojovém kódu objevila až později (angl. forward references).
- Přítomnost anotací má negativní vliv na dobu spuštění programu.
Python 3.7 tyto problémy řeší zavedením tzv. odloženého vyhodnocení anotací (angl. postoponed evaluation of annotations). Funguje to tak, že anotace jsou v době parsování zdrojového kódu ukládány ve formě řetězců a vyhodnocení anotací proběhne až na vyžádání přes typing.get_type_hints()
. V Pythonu 3.7 je tedy možné v anotacích použít typy, které budou definované až později:
def foo() -> MyClass: return MyClass() class MyClass: pass
V Pythonu 3.6 by kód výše vyhodil výjimku NameError: name 'MyClass' is not defined
.
Jelikož má tato změna dopad na zpětnou kompatibilitu, tak je nutné ji explicitně povolit (v každém modulu samostatně):
from __future__ import annotations
Od Pythonu 4.0 se bude jednat o výchozí nastavení.
Detaily: PEP 563
Nový modul: dataclasses
Nový dekorátor dataclasses.dataclass()
zjednodušuje vytváření tříd, jež primárně pracují s daty. Představme si, že chceme vytvořit třídu reprezentující bod v dvoudimenzionálním prostoru, jejíž instance je možné vytvořit, zpřístupnit souřadnice, navzájem porovnat a vypsat. Dříve bychom si toto všechno museli naimplementovat sami. Od Pythonu 3.7 máme lepší možnost:
import dataclasses @dataclasses.dataclass class Point: x: int = 0 y: int = 0
Pro naši třídu se automaticky vygeneruje __init__(self, x=0, y=0)
, přístup k atributům, __eq__()
, __repr__()
, případně volitelně porovnání na nerovnost či __hash__()
pro umožnění umístění instancí do množin či slovníků.
Koncepčně to připomíná collections.namedtuple()
s tím rozdílem, že dataclass()
umožňuje vytvořit třídu, jejíž instance budou modifikovatelné. Vzpomeňte si, že instance namedtuple()
jsou po vytvoření vždy nemodifikovatelné. Dekorátor dataclass()
je tedy univerzálnější, než namedtuple()
.
Detaily: PEP 557, Raymond Hettinger: Dataclasses (PyCon 2018)
Možnost úpravy získávání atributů z modulu
Python 3.7 zavádí možnost definovat __getattr__()
na úrovni modulu. Tato funkce, mající jako argument název atributu, musí vrátit buď onen atribut, nebo vyhodit výjimku AttributeError
. Slouží tedy k nahookování mechanismu získávání atributů v modulu.
import logging def __getattr__(attr_name): logging.info(f'accessed {attr_name}') globals()[attr_name]
Podobně je pak možné v modulu definovat __dir__()
, která se použije při volání funkce dir()
nad daným modulem.
Detaily: PEP 562
Garantované zachování pořadí vkládání do slovníku
V Pythonu 3.6 došlo k úpravám týkajícím se slovníků, které umožnily zachování pořadí definice atributů ve třídách (PEP 520) a při předávání argumentů do funkcí přes kwargs
(PEP 468). Python 3.7 jde v tomto ohledu ještě dál tím, že garantuje zachování pořadí prvků při vkládání do slovníku:
d = {'a': 1, 'b': 2, 'c': 3} assert [k for k in d] == ['a', 'b', 'c']
Pokud používáte CPython (standardní implementace Pythonu), tak u něj bylo pořadí vkládání prvků zachováno již ve verzi 3.6, ale jednalo se pouze o implementační detail. Od verze 3.7 je zachování pořadí veřejným požadavkem na všechny implementace Pythonu.
Detaily: dokumentace k dict, issue 33609
Nová metoda pro str
, bytes
a bytearray
Do standardních typů str
, bytes
a bytearray
přibyla nová metoda isascii()
, která umí ověřit, zda je řetězec složen pouze ze znaků ze znakové sady ASCII:
'Jalapeno'.isascii() # True 'Jalapeño'.isascii() # False
Detaily: issue 32677
Vestavěná funkce breakpoint()
Přibyla vestavěná funkce breakpoint(), jejíž zavolání vás přesune přímo do debuggeru od místa zavolání:
# ... breakpoint() # ...
Slouží jako náhrada za následující kód, který trpí několika problémy (zbytečně dlouhý, snadno se v něm udělá chyba, je limitovaný na pdb
a některé lintery si stěžují, že je na jednom řádku více příkazů):
# ... import pdb; pdb.set_trace() # ...
Detaily: PEP 553
Funkce mohou mít víc než 255 parametrů
Věděli jste, že dříve nešlo funkcím předat více než 255 argumentů? Pokud se zrovna plácáte po čele a říkáte si, který vůl by definoval funkci s 255 parametry, tak vězte, že na toto umělé omezení šlo narazit v generovaném kódu. Od Pythonu 3.7 tento limit odpadá.
Detaily: issue 12844
ImportError
u from ... import ...
ukáže, odkud daný modul pochází
Pokud importujete jméno z modulu přes from ... import ...
, tak Python nyní do ImportError
výjimky uloží informaci o tom, odkud daný modul pochází. To se hodí při ladění problémů, např. pokud se snažíte něco importovat z modulu ve virtuálním prostředí, ale ve skutečnosti se použije starší verze ze systému:
# Python 3.6: # # ImportError: cannot import name 'foo' # # Python 3.7: # # ImportError: cannot import name 'foo' from 'requests' # (/usr/lib/python3.7/site-packages/requests/__init__.py) from requests import foo
Detaily: issue 29546
async
a await
jsou nyní klíčová slova
async
a await
jsou od Pythonu 3.7 klíčová slova. Dříve byl jejich význam závislý na kontextu a tudíž je bylo možné použít i jako identifikátory:
def foo(async=1, await=2): return async + await
To od verze 3.7 není možné, a tak při pokusu o spuštění kódu výše dojde k vyhození výjimky SyntaxError: invalid syntax
.
Detaily: issue 30406
Nový modul: contextvars
Nový modul contextvars
přináší možnost vytváření a použití tzv. kontextových proměnných. Ty jsou koncepčně podobné tzv. thread-local proměnným (každé vlákno má svou vlastní kopii dané proměnné), ale s tím rozdílem, že kontextové proměnné fungují korektně v asynchronním kódu. V asynchronním kódu totiž běží jen jediné vlákno.
Příklad použití v rámci asyncio
je k vidění zde.
Detaily: PEP 567
Novinky ve standardní knihovně
Níže bych chtěl vypíchnout několik modulů, ve kterých došlo k vylepšením.
time
Do standardního modulu time
přibylo několik variant existujících funkcí, které pro větší přesnost vrací výsledek v nanosekundách:
import time time.time() # Např. 1534680332.789262 time.time_ns() # Např. 1534680332789296548
Pokud vám tedy mikrosekundy nestačí, použijte variantu s _ns()
na konci.
collections
collections.namedtuple()
nyní umožňuje specifikovat výchozí hodnoty pro atributy:
import collections Point = collections.namedtuple('Point', ('x', 'y'), defaults=(0, 0)) p = Point() # Stejný význam, jako Point(0, 0).
Toto vylepšení přináší pojmenované ntice blíže datovým třídám z Pythonu 3.7 (viz jedna z předchozích novinek).
datetime
Nová metoda datetime.datetime.fromisoformat()
umožňuje vytvořit datetime.datetime
objekt z ISO 8601 reprezentace:
import datetime d = datetime.datetime.fromisoformat('2018-08-19T11:42:32.716405')
Bohužel, tato nová metoda neumí naparsovat libovolné datum podle ISO 8601, pouze to, které vrací datetime.datetime.isoformat()
.
multiprocessing
Do multiprocessing.Process
přibyla nová metoda kill()
, která na Unixových platformách pošle SIGKILL
:
import multiprocessing def foo(): while True: pass p = multiprocessing.Process(target=foo) p.start() p.kill()
Pokud byste chtěli zaslat SIGTERM
, tak byste použili terminate()
.
asyncio
Mnoho novinek, viz oficiální seznam.
subprocess
První novinkou je, že funkce subprocess.run()
přijímá nový parametr capture_output
, který automaticky nastaví odchytávání standardního výstupu a chybového výstupu do řetězce (podobně, jako byste u subprocess.Popen()
použili stdout=subprocess.PIPE
a stderr=subprocess.PIPE
). Výhodou je kratší a přehlednější kód:
import subprocess p = subprocess.run(['echo', 'hello'], capture_output=True) print(p.stdout) # b'hello\n'
Druhou novinkou je, že subprocess.run()
a subprocess.Popen()
nyní přijímají nový parametr text
jako alias pro universal_newlines
. Tento parametr má podobný význam, jako textový režim pro čtení a zápis z/do souboru: vstupem a výstupem programu bude text (str
) namísto bajtů (bytes
) a dojde k automatické konverzi konců řádků (\r\n
→ \n
):
import subprocess p = subprocess.run(['echo', 'Jalapeño'], capture_output=True) print(p.stdout) # b'b'Jalape\xc3\xb1o\n' p = subprocess.run(['echo', 'Jalapeño'], capture_output=True, text=True) print(p.stdout) # 'Jalapeño\n'
Výhodou nového parametru je především zčitelnění kódu, protože universal_newlines
je poněkud kryptické jméno pro to, co tento parametr dělá.
os
Do standardního modulu os
přibyla funkce os.register_at_fork()
, která na Unixových systémech umožní zaregistrování funkcí, které se mají provést před voláním os.fork()
, po volání os.fork()
v rodičovském procesu a po volání os.fork()
v potomkovi:
import os os.register_at_fork( before=lambda: print('before'), after_in_parent=lambda: print('after - parent'), after_in_child=lambda: print('after - child'), ) # Následující volání vytiskne: # before # after - parent # after - child os.fork()
Z pochopitelných důvodů funguje pouze na Unixových systémech.
unittest.mock
Do standardního modulu unittest.mock
přibyla funkce seal()
, která umožní zakázat čtení a zápis neexistujících atributů:
from unittest import mock m = mock.Mock() m.a = 1 mock.seal(m) m.a = 2 # OK m.b = 2 # AttributeError: Cannot set mock.b
Lze si pomocí ní zaručit, že v testovaném kódu omylem nedojde k přístupům na nedefinované atributy.
Optimalizace
Na závěr jen zmíním, že jako s každým vydáním nové verze Pythonu, i v této došlo k mnoha urychlením. Seznam optimalizací je k vidění zde.
Zdrojové kódy
Všechny zdrojové kódy jsou k dispozici u mě na GitHubu, takže si vše můžete vyzkoušet.