Python 3.0 až 3.5

cs in code

Python 2.7 se hodně používá a zřejmě nějaký ten pátek ještě bude. Přechod na Python 3 se ale nevyhnutelně blíží a při takovém přechodu se skočí rovnou na nějakou nejposlednější verzi. Momentálně to může být 3.4 či dokonce před pár dny vypuštěný Python 3.5. Říkal jsem si, co vlastně všechno takového hezkého bylo přidáno od verze 3.0 do 3.5 a nebylo backportováno? Prošel jsem si všechny změny a řekl si, že se podělím o to, co mne zaujalo.

Python 3.0: Print!

print() je funkce. S tím bude těžké sžít se. První třetí verze se objevila v roce 2009 a stále jsem se nenaučil psát závorky.

Další velká změna je, že text je opravdu text a nejde mixovat s binárními daty. Takže žádné fungování při testech a poté v provozu neustálé ošetřování výjimek UnicodeDecodeError.

Všude jsou iterátory. Žádné range vs. xrange, ale pouze range vracející iterátor. Další funkce dříve vracející seznamy také jedou na iterátorech, jako třeba map, filter, zip, … Ale pozor, items, keys apod. nad slovníkem nevrací iterátor, nýbrž view. To je zase trochu něco jiného.

super() bez parametrů!

A detail možnost rozbalit tuple s hvězdičkou: (a, *rest, b) = range(5).

Python 3.1: Vůbec nic

Vše důležité bylo backportnuto i do Pythonu 2.7. Nový Python si holt teprve sedal. :-)

Python 3.2: První featury

První krůčky k asynchronnímu Pythonu, knihovna concurrent.futures.

Konečně si nebudeme muset dělat sami řešení na cachování výsledků funkcí, aneb ať žije lru_cache!

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

Ve functools přibyla ještě jedna zajímavost a to, že wraps navíc přidává __wrapped__ odkazující na původní funkci. Takže jde introspektnout, vrátit atp. :-)

OrderedDict dostal novou metodu move_to_end. Jen škoda, že opak udělali ošklivě parametrem…

Je možné mít configuráky s proměnnými díky ExtendedInterpolation. Mimochodem lze předat konfiguraci i jako slovník, což dodává proměnným ještě větší šťávu!

>>> parser = ConfigParser(interpolation=ExtendedInterpolation())
>>> parser.read_dict({'buildout': {'directory': '/home/ambv/zope9'}})
>>> parser.read_string("""
    [buildout]
    find-links = ${buildout:directory}/downloads/dist
    """)
>>> parser['buildout']['find-links']
'/home/ambv/zope9/downloads/dist'

Vzniknul modul argparse. Ten byl teda backportnut i do Pythonu 2.7. Ale chci se pochlubit, že existuje i argparsedialog (jen pro trojku).

Python 3.3: Rozjíždíme…

Je to tu. Tak kolik aplikací 3.3ka složí? :-) Slovník nemá definované pořadí. V dokumentaci bylo upozornění, že se nejedná o náhodné řazení, ale rozhodně se na to nemá spoléhat. Kvůli bezpečnostní chybě už nelze pořadí předvídat.

Nová syntaxe yield from pro delegování generatorů. Především pro to, co přijde v Pythonu 3.4, aby se později stalo v 2.5 zbytečným. :-)) Zkratka pro:

>>> for item in iterable:
...    yield item

>>> yield from iterable

Virtualenv už není třeba, součástí Pythonu je nový pyvenv, který řeší všechny problémy, které nemohly tooly třetích stran vyřešit na 100 procent.

__init__.py není povinný! Ano, NE-NÍ povinný. Jen otázka, zda je to dobrý nápad. Protože pak je možné naimportovat jakýkoliv adresář…

Pokud se odchytí výjimka a ta vyhodí novou výjimku, budou vidět obě. Často zbytečné, ale věřte, že v praxi se stávají i chyby při zpracování chyby, a pak je akorát původní chyba občas nedohledatelná.

>>> class C:
...     def __init__(self, extra):
...         self._extra_attributes = extra
...     def __getattr__(self, attr):
...         try:
...             return self._extra_attributes[attr]
...         except KeyError:
...             raise AttributeError(attr)
...
>>> C({}).x
Traceback (most recent call last):
  File "", line 6, in __getattr__
KeyError: 'x'
 During handling of the above exception, another exception occurred:
 Traceback (most recent call last):
  File "", line 1, in
  File "", line 8, in __getattr__
AttributeError: x

Přes atribut __qualname__ půjde nově zjistit přesně, kde byla funkce či třída definována.

>>> class C:
...     def meth(self):
...         pass
>>> C.meth.__name__
'meth'
>>> C.meth.__qualname__
'C.meth'

Vytváření souborů a vyhodit výjimku, pokud už existuje, půjde s novým módem x.

>>> open('/tmp/somefile', 'x')
<_io.TextIOWrapper name='/tmp/aaa' mode='x' encoding='ANSI_X3.4-1968'>
>>> open('/tmp/somefile', 'x')
Traceback (most recent call last):
  File "", line 1, in
FileExistsError: [Errno 17] File exists: '/tmp/somefile'

datetime instance dostala novou metodu timestamp. Ale že to trvalo, že? :-)

>>> some_datetime.timestamp()
1442266005.577331

Nový hezký Pythonic modul ipaddress. Díky tomu lze zapsat například: (IPv4Address('127.0.0.1') + 42).is_private.

mock je součástí unittestu!

Je zpět u'' pro lepší port z dvojky! Není to jediná drobnost, která ulehčuje přechod na trojku. Takže pokud přechod, tak minimálně na… 3.4 (ne, to není překlep, jen dnes dřívější nemá moc smysl).

Python 3.4: Balíky, balíky, balíky, …

ensurepip je nový balík zajišťující přítomnost instalací Pythonu, i když se jedná o projekt mimo samotný Python. Jinými slovy pip po ruce po instalaci Pythonu!

Nový balík pro asynchronní aplikace AsyncIO. To je kde se využije delegování generátorů. AsyncIO je totiž na generátorech tak trochu postavené.

Vlastně až balík pathlib mne nahlodal projít podrobně, co vše v Pythonu 3.x bylo přidáno a upraveno.

>>> p = Path('/etc')
>>> q = p / 'init.d' / 'reboot'
>>> q
PosixPath('/etc/init.d/reboot')
>>> q.resolve()
PosixPath('/etc/rc.d/init.d/halt')

Třeba takový enum se bude taky hodit…

>>> from enum import Enum
>>> class Color(Enum):
...     red = 1
...     green = 2
...     blue = 3
>>> type(Color.red)
<enum 'Color'>
>>> isinstance(Color.green, Color)
True
>>> print(Color.red.name)
red

Nebo různé statistické metody v modulu statistics

Modul functools se rozrostl o další užitečnosti. První z nich je partialmethod, což dodává super nové možnosti:

>>> class Cell(object):
...     def __init__(self):
...         self._alive = False
...     @property
...     def alive(self):
...         return self._alive
...     def set_state(self, state):
...         self._alive = bool(state)
...     set_alive = partialmethod(set_state, True)
...     set_dead = partialmethod(set_state, False)

A pokud vám chybělo method overloading, pak to lze aspoň trochu dohnat se singledispatch. I když nedoporučoval bych takový přístup.

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)
...
>>> @fun.register(int)
... def _(arg, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> fun("Hello, world.")
Hello, world.
>>> fun("test.", verbose=True)
Let me just say, test.
>>> fun(42, verbose=True)
Strength in numbers, eh? 42

Občas se může stát, že se do reguláru zapomene přidat dolar nebo se při nějakých změnách vytratí atp., proto další metoda fullmatch.

Zrovna nedávno jsme řešili, že naše aplikace žere docela dost paměti a občas neúměrně moc. Chtěli jsme to pořádně zanalyzovat, ale nebylo čím. Mile mne překvapil modul tracemalloc. S ním to bude sranda debugovat. :-)

Funkce min a max požírají parametr default, řešící současné vyhazování ValueError při prázdné iterable proměnné.

Python 3.5: To nejlepší nakonec

Už žádné yield from s AsyncIO. Vznik nových klíčových slov async a await. Tedy abych byl přesný, pravými klíčovými slovy budou až od Pythonu 3.7, ale je dobré se jim vyhnout už nyní.

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

Type hinty. Druhá velká věc, o které se hodně mluví. Za mne se moc těším na možnost sem tam vynutit typ a pomoct tak i editorům v napovídání. Vlastně nemusím, jelikož se stub fily lze využít už nyní. Ale přímo v kódu je přímo v kódu. Konečně žádné cusotm dekorátory či asserty na začátku funkce.

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

Jen se mi moc nelíbí jak se budou zapisovat nějaké složitější typy s dodávanou knihovnou typing. No, diskutovalo kolem toho spoustu chytrých lidí, tak uvidíme, jaké to bude v praxi.

Krom nových klíčových slov a type hintů se dostal do Pythonu i nový operýtor @ pro násobení matic. Známá knihovna numpy přijde s podporou ve verzi 1.10. Matematici se mohou na co těšit. :-)

>>> x = numpy.ones(3)
>>> x
array([ 1., 1., 1.])
 >>> m = numpy.eye(3)
>>> m
array([[ 1., 0., 0.],
       [ 0., 1., 0.],
       [ 0., 0., 1.]])
 >>> x @ m
array([ 1., 1., 1.])

Kolikrát jste potřebovali zmergovat slovník a používali pro to ne úplně jasnou konstrukci dict(first, **second)? Tak nově lze pěkně hezky: {**first, **second}. Samozřejmostí je funkčnost i se seznamy, sety apod.

A to víte, že existuje možnost zabalit Pythoní aplikaci do zipu a přímo spustit? Se zipapp už ano. Zajímavá metoda pro drobnosti, které nepatří do PyPI a je zbytečné dělat jiný, třeba debianí, balík.

configparser ještě jednou. Je možné s předáním converters přidat další get* metody.

>>> parser = ConfigParser(converters={'list': [item.strip() for item in

value.split() if item.strip()]}) >>> parser.read_string("""[section] ... value = a b c d""") >>> parser.get('section', 'value') 'a b c d' >>> parser.getlist('section', 'value') ['a', 'b', 'c', 'd']

Pár zajímavých věcí bylo přepsáno do céčka. Například OrderedDict či lru_cache. A spoustu dalších optimalizací přes všechny releasy, jako třeba zrychlení načtení interpretu, rychlejší dumpování všeho druhu (pickle, marshall, json), rychlejší volání get property, optimalizace podmínky s výčtem (tedy lepší psát if x in {a, b, c}), …

Mne nejvíce potěšilo…


Takže… taky byste nejradši hned zahodili současný codebase a použili Python 3.5? :-)





You may also like