Jste zde

Co je nového v Pythonu 3.7

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:

  1. 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).
  2. 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.

Přidat komentář