Hlavní navigace

Nový chřestýš (1)

Jan Švec

Prvního prosince se objevila nová verze 2.4 oblíbeného jazyka Python. V následujícím dvoudílném seriálu si popíšeme některé nové vlastnosti této verze. V prvním dílu si ukážeme generátorové výrazy a dekorátory.

Překlad a instalace

Překlad a instalace nového Pythonu 2.4 je nadmíru snadná. Nyní stačí použít pouze „svatou trojici“ ./configure; make; make install a vše proběhne podle očekávání. Již není nutné modifikovat soubor Modules/Setup, protože (téměř) všechny moduly jsou správně nakonfigurovány. Spustíme-li našeho nově získaného chřestýše, uvidíme něco podobného:

Python 2.4 (#1, Dec 28 2004, 12:28:00)
[GCC 2.95.4 20011002 (Debian prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

Blízko k funkcionalitě

Z Pythonu, ač jde o jazyk procedurální, je z mnoha stran cítit trocha funkcionální vůně. Vše od funkcí typu map() či filter() přes lambda funkce až po výrazy, jako je stručný seznam (list comprehension), lze najít ve funkcionálních jazycích. Následně k nim přibyly iterátory a ve verzi 2.2 generátory. Konečně ve verzi 2.3 se obejvil modul itertools a verze 2.4 přináší další nášup – stručné generátory (generator expressions).

Tento výraz na první pohled vypadá obdobně jako stručný seznam. Zásadní rozdíl je ale ve funkci. Zatímco stručný seznam vrátí CELÝ vygenerovaný seznam, stručný generátor vrátí pouze iterátor, pomocí něhož se potom list generuje „za běhu“. Mezi stručným seznamem a stručným generátorem je obdobný rozdíl jako mezi obyčejnou metodou a generátorem.

Velice výhodné je jeho použití na místech, kde lze očekávat iterování přes velké množství objektů, ale při dalším zpracování není nutné používat všechny naráz. Následující příklad spočítá součet čísel dělitelných třemi ze seznamu cisla.

>>> cisla = [1, 4, 6, 8, 12, 31, 49, 51, 59, 60, 68, 70]
>>> vyber = (i for i in cisla if i % 3 == 0)
>>> soucet = sum(vyber)
>>> print soucet
129

Stručný generátor je výraz na druhé řádce kódu. Pokud bychom místo něho použili stručný seznam, nejprve by se vygeneroval ze vstupního seznamu cisla nový seznam, jež by obsahoval čísla dělitelná třemi, a následně by se spočítal jejich součet, což by v případě enormní délky seznamu cisla nebylo to pravé ořechové.

Výraz stručného generátoru je uzavřen mezi kulaté závorky (namísto hranatých závorek u stručného seznamu), jinak je jeho syntaxe stejná. V případě, že stručný generátor použijeme rovnou jako argument funkce, nemusíme další pár závorek uvádět:

>>> print sum(i for i in cisla if i % 3 == 0)
129

Nezapomeňte na to, že výrazy obsažené v generátoru se vyhodnocují až v případě potřeby, je tedy nutné dát pozor na vznik chyb. Pokud totiž v generátoru použijeme neexistující proměnnou, generátor vznikne a k chybě dojde až při jeho použití.

>>> vyber = (i for i in cisla if j % 3 == 0)
>>> soucet = sum(vyber)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 1, in <generator expression>

NameError: global name 'j' is not defined

Poslední upozornění se týká řídící proměnné. Ve verzi 2.4 a nižších po skončení stručného seznamu zůstane řídící proměnné (popř. proměnným) přiřazena poslední hodnota. Řídící proměnná stručného generátoru je však definována pouze uvnitř tohoto generátoru a po jeho skončení je nepřístupná:

>>> slova = ['Fakulta', 'aplikovanych', 'ved']
>>> print ''.join([i[0].upper() for i in slova])
FAV
>>> print i
ved
>>> print ''.join(j[0].upper() for j in slova)
FAV
>>> print j
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'j' is not defined

Toto chování je logickým důsledkem „línosti“ stručného generátoru. Vykonáním výrazu generátoru se pouze vytvoří nový objekt, ale není vyhodnocen žádný výraz a ani řídící proměnné není přiřazena žádná hodnota. V budoucnu se počítá s úpravou chování stručného seznamu tak, aby kopíroval chování svého funkcionálního bratříčka.

Dekorátory

V Pythonu verze 2.2 spolu se zavedením tzv. „new-style“ tříd přibyla možnost definování statických a třídních metod pomocí funkcí staticmethod() a classmethod(). Tyto metody přebírají odkaz na metodu nějaké třídy a vrátí novou metodu s požadovaným chováním. Např:

>>> class retezec(str):
...     def ze_souboru(cls, jmeno):
...         f = file(jmeno)
...         ret = cls(f.read())
...         f.close()
...         return ret
...     ze_souboru = classmethod(ze_souboru)
...
>>> s = retezec.ze_souboru('/etc/fstab')
>>> print s[:40]
# /etc/fstab: static file system informa

Jak je vidět, modifikace funkcí classmethod() se musí provést až po definici metody. Tím může dojít k různým opomenutím a k nechtěným chybám. Proto Python ve verzi 2.4 přináší tzv. dekorátory. Kolem tohoto doplnění jazyka se vedla velká diskuze a mnoho lidí ji dodnes nepřeneslo přes srdce. Je ovšem třeba dát za pravdu hlasům, které říkají, že zavedení dekorátorů zpřehlední samotný zápis kódu a jeho následnou údržbu. Výše uvedený příklad by s použitím dekorátorů vypadal následovně:

>>> class retezec(str):
...     @classmethod
...     def ze_souboru(cls, jmeno):
...         <telo funkce>
...
>>> s = retezec.ze_souboru('/etc/fstab')

Jak je vidět, transformace přiřazením ze_souboru=clas­smethod(ze_sou­boru) se zapíše pomocí dekorátoru. Dekorátor – jméno funkce – je uveden za znakem @. Dekorátorů může být i více a jsou uvedeny PŘED samotnou funkcí, což zvyšuje čitelnost výsledného kódu.

Pro jednu funkci lze použít i více dekorátorů. Jako jednoduchou ukázku vytvoříme dva dekorátory, dvojnasobek() a odmocnina(), které budou po aplikování na určitou funkci modifikovat její návratovou hodnotu tak, že vytvoří její dvojnásobek resp. odmocninu:

import math

def dvojnasobek(func):
  def wrapper(*args, **kwargs):
    ret = func(*args, **kwargs)
    return ret*2
  return wrapper

def odmocnina(func):
  def wrapper(*args, **kwargs):
    ret = func(*args, **kwargs)
    return math.sqrt(ret)
  return wrapper

@dvojnasobek
def f1():
  return 3.0

@odmocnina
def f2():
  return 3.0

@odmocnina
@dvojnasobek
def f3():
  return 1.0

@dvojnasobek
@odmocnina
def f4():
  return 1.0

print f1()
print f2()

Výstupem programu bude následující výpis:

6.0
1.73205080757
1.41421356237
2.0

Nyní si popíšeme funkci těchto dekorátorů. Jak již bylo řečeno, dekorátor je vlastně funkce vracející novou funkci. Díky tomuto „obalu“ je možné různými způsoby modifikovat chování původní funkce. V souladu s výše uvedeným naše dekorátory vracejí novou funkci se jménem wrapper(), která je definována v jejich těle. Samotný dekorátor přebírá jediný argument – funkci, jež se má dekorovat.

Parametr func přejde do vnořené funkce wrapper() pomocí mechanismu vnořených oborů jmen (nested scopes). Poté se při volání f1() vlastně vykoná volání odpovídajícího wrapperu. Bez použití dekorátorů by se definice a volání funkce f1() dalo zapsat jako:

def f1(): ...
f1 = dvojnasobek(f1)
f1()

V případě použití více dekorátorů je situace obdobná, ovšem dojde k dekorování více funkcí najednou. Zápisy definice a volání funkce f3() by bez použití syntaxe dekorátoru vypadaly takto:

def f3(): ...
f3 = dvojnasobek(odmocnina(f3))
f3()

Dekorátory mohou mít i své parametry. V tomto případě funkce reprezentující dekorátor převezme tento parametr a vrátí nový dekorátor odpovídající tomuto parametru. Jde dle mého názoru o poněkud krkolomnou implementaci, nicméně svůj účel splní. Ukážeme si vykonstruovaný příklad, jak se na základě parametru rozhodovat, zda vytvořit statickou, nebo třídní metodu:

def method(type):
  if type == 'static':
    return staticmethod
  elif type == 'class':
    return classmethod
  else:
    raise ValueError

class C(object):
  @method('static')
  def staticka():
    print 'staticka metoda'

  @method('class')
  def tridni(cls):
    print 'tridni metoda tridy %s' % cls.__name__

C.staticka()
C.tridni()

Výstup programu vypadá následovně:

staticka metoda
tridni metoda tridy C

Dekorátor meth() na základě svého parametru type vrací buď dekorátor staticmethod(), nebo classmethod(), který je následně aplikován na metodu C.staticka() resp. C.tridni().

Je-li v určitém místě kódu třeba kontrolovat, popř. unifikovat typ parametrů nějaké metody, lze k tomu s úspěchem použít právě dekorátory. Následující ukázka kódu vytvoří dekorátor, jemuž lze předat funkce pro konverzi parametrů. Chování demonstrujeme na příkladu moje_funkce(), která od dekorátoru obdrží první parametr zkonvertovaný na typ bool, druhý na typ seznam a třetí nechá bez úpravy.

NoUni = lambda v: v   # necha parametr beze zmeny

def uniargs(*uni):
  def create_wrapper(func):
    def wrapper(*args):
      new_args = map(lambda u,a:u(a), uni, args)
      return func(*new_args)
    return wrapper
  return create_wrapper

@uniargs(bool, list, NoUni)
def moje_funkce(b, l, i):
  print b
  print l
  print i

moje_funkce(1, (1,2), 30)
print '-'*10
moje_funkce([], 'jedna', None)

Výstup z programu:

True
[1, 2]
30
----------
False
['j', 'e', 'd', 'n', 'a']
None

Jak vidíme, náš dekorátor uniargs() pracující s obecnými hodnotami parametrů obsahuje konkrétní dekorátor create_wrapper(), jenž konečně vrací wrapper() modifikující parametry. Nakonec si uvedeme ještě jednoduché rozepsání definice moje_funkce() bez použití dekorátorů.

def moje_funkce(): ...
_muj_dekorator = uniargs(bool, list, NoUni)
moje_funkce = _muj_dekorator(moje_funkce)

Je třeba podotknout, že každý dekorátor musí být zapsán na nové řádce. Osobně nechápu, proč je to požadováno. Stejně tak by bez ztráty čitelnosti mohly být dekorátory na jedné řádce následovány definicí funkce na řádce druhé. Dokonce dokument „What's New in Python 2.4“, který je součástí dokumentace nového Pythonu, obsahuje ukázku, v níž jsou použity tři dekorátory na jedné řádce. Takováto inkonzistence vypovídá cosi o horké jehle, i když výsledek je bezesporu užitečný.

Závěrem

Obě výše probíraná rozšíření jistě přinášejí mnoho nového. Názor vývojářské komunity nebyl při jejich implementaci jednotný. Je zajímavé začíst se do různých PEPů (Python Enhancement Proposals), což jsou víceméně specifikace nových vlastností.

Velice zajímavé je třeba přečíst si starší revize PEP 289 (okolo počátku roku 2002) věnovaného stručným generátorům. Původně se zamýšlelo konstruovat stručné generátory stejným způsobem jako stručné seznamy rozšířené o klíčové slovo yield. Samotný autor Pythonu Guido van Rossum byl proti, neboť se domníval, že generátory jsou pouze okrajovou záležitostí a posunulo by to Python směrem k funkcionálním jazykům. A jak je vidět, rok se s rokem sešel a Python byl o generátorové výrazy obohacen.

V příštím dílu si povíme něco o dalších vlastnostech Pythonu 2.4, jako jsou množiny, decimální datový typ a především modul pro testování kódu – doctest.

Našli jste v článku chybu?

17. 1. 2005 20:40

JP (neregistrovaný)

Jenom menší doplnění - v novějších verzích Pythonu (od verze 2.2?) stačí použít for line in f:, efekt je stejný. Není třeba psát generátor, tato konstrukce iteruje soubor po řádkách a do paměti se žádný seznam neukládá.

17. 1. 2005 20:32

JP (neregistrovaný)

Zato já si dovedu intellisense představit docela dobře i v případě této poměrně absurdní syntaxe. Napsat rozumné IDE, které snadný zápis volání takových metod umožňuje, by neměl být žádný velký problém. A jaké výhody OOP že to přijdou vniveč? Paradigma se nezměnilo vůbec, jmenný prostor se tím nijak nezanáší, výrazové prostředky to neomezuje. Akorát se možná trošku sníží čitelnost a v klasických editorech musí napsat člověk o pár znaků víc. Žádné další nevýhody mě nenapadají.

Měšec.cz: Air Bank zruší TOP3 garanci a zdražuje kurzy

Air Bank zruší TOP3 garanci a zdražuje kurzy

Vitalia.cz: „Připluly“ z Německa a možná obsahují jed

„Připluly“ z Německa a možná obsahují jed

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET

Lupa.cz: Teletext je „internetem hipsterů“

Teletext je „internetem hipsterů“

Vitalia.cz: Cena stejného léku se liší i o tisíce

Cena stejného léku se liší i o tisíce

Podnikatel.cz: Udávání a účtenková loterie, hloupá komedie

Udávání a účtenková loterie, hloupá komedie

Vitalia.cz: Típněte to! Kde všude si už nezakouříte?

Típněte to! Kde všude si už nezakouříte?

Vitalia.cz: Mondelez stahuje rizikovou čokoládu Milka

Mondelez stahuje rizikovou čokoládu Milka

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

120na80.cz: Na ucho teplý, nebo studený obklad?

Na ucho teplý, nebo studený obklad?

Lupa.cz: Insolvenční řízení kvůli cookies? Vítejte v ČR

Insolvenční řízení kvůli cookies? Vítejte v ČR

DigiZone.cz: Rádio Šlágr má licenci pro digi vysílání

Rádio Šlágr má licenci pro digi vysílání

Podnikatel.cz: Snížení DPH na 15 % se netýká všech

Snížení DPH na 15 % se netýká všech

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Podnikatel.cz: Udávání kvůli EET začalo

Udávání kvůli EET začalo

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Vitalia.cz: 4 příčiny zápachu z úst a jak s ním zatočit

4 příčiny zápachu z úst a jak s ním zatočit

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu