Nový chřestýš (2)

31. 1. 2005
Doba čtení: 8 minut

Sdílet

Druhá část povídání o vlastnostech nové verze jazyka Python. Dnes se budeme věnovat především novému vestavěnému typu množina a dvěma zajímavým modulům, které se dostaly do standardní knihovny, subprocess a doctest.

Pořádek v hadech

Nejprve musím uvést na pravou míru jména jednotlivých druhů hadů v angličtině. Nedopatřením jsem do své hlavy uložil Python jako chřestýše a od té doby jsem s tím žil až do teď, kdy jsem se rozhodl po něm pojmenovat článek. Tak tedy Python není chřestýš, nýbrž krajta nebo hroznýš, přičemž buď označuje celou čeleď hroznýšovitých, nebo pouze podčeleď krajtu.

Několik menších změn

Nejprve si řekneme o několika dalších menších změnách, které by, na rozdíl od novinek uvedených minule, neměly změnit život většiny programů:

  • Sbližování typů Integer a Long integer. Ve verzi 2.4 je možné používat (hodně) velké literály pro zápis celých čísel, přičemž pokud je není možno reprezentovat jako typ int, automaticky se převedou na typ long. Podobně nyní pracují i některé aritmetické operace, jako jsou posuny apod.
  • Nový číselný typ Decimal pro ty, kterým nestačí datový typ float. Najdete ho v modulu decimal. I přes jeho nesporné výhody – dokáže totiž přesně reprezentovat desetinná čísla až do zadaného počtu desetinných míst – jde podle mého o dar nedar, časem bychom totiž museli mít i typ Trial pro trojkovou soustavu, neboť číslo 1/3 nezapíšeme správně ani v desítkové ani v binární soustavě, ale právě v soustavě trojkové, a pak by přibyl Pental atd.
  • Vylepšený příkaz import, nyní můžete u konstrukce „from MODUL import“ použít seznam jmen uzavřený v závorce, díky čemuž lze uvést seznam přes několik řádek bez nutnosti uvozování konce řádku zpětným lomítkem.
  • Nové vnitřní funkce jako např. reverse() a sorted() umožňující iteraci přes obrácenou, popř. seřazenou posloupnost. Dále jde o vylepšení metody seznamů sort(), jíž lze předat pojmenované argumenty cmp (bylo již v dřívějších verzích), key a reverse, umožňující změnit chování řazení. Pomocí cmp lze použít vlastní porovnávací funkce. Argument key je zase funkce vracející porovnávací klíč prvku a argument reverse značí požadavek na vrácení prvků v opačném pořadí. Tyto argumenty lze použít rovněž pro funkci sorted().
  • Možnost spustit modul jako hlavní modul pomocí parametru -m. Spustíte-li tedy python -m pdb jmeno_souboru, můžete rovnou ladit.
  • Odstranění „featury“, jež při chybě, která vznikla při importu modulu, nechala tento modul zčásti iniciovaný v sys.modules. Nyní je špatně iniciovaný modul odstraněn, a další import tedy může být úspěšný.
  • Nakonec další, alespoň pro mě velice kontroverzní změna, jež z objektu None vytvořila konstantu. Poněvadž jazyk s něčím jako konstanta vůbec neoperuje, je dle mého chybným krokem vytvoření i třeba jediné konstanty. Časem bude každý chtít svou vlastní konstantu, a to i přesto, že None má speciální postavení. Proč nezavést další, jako jsou True, False atd.? Nikdo rozumný přece něco jako „None = 42“ nemůže ani napsat.

Datový typ množina

S novou verzí jazyka přibyly dva nové datové typy – množina a konstantní množina – reprezentované typy set a frozenset. Tyto množiny slouží jako neřazená kolekce neměnných hodnot, tj. v množině nemohou být např. seznamy, ale pouze hashovatelné objekty. Konstantní množina může být použita jako klíč v asociativním poli nebo jako prvek jiné množiny.

Množiny podporují mnoho operací, jako je například získání kardinality (pomocí funkce len()), vkládání a odstraňování prvků, vytváření množin z jiných datových typů, přičemž duplikované prvky jsou odstraněny. Lze rovněž počítat běžné matematické operace jako sjednocení, průnik, symetrickou diferenci (XOR) nebo rozdíl množin. Rovněž lze množinou prvků iterovat pomocí cyklu for a testovat, zda je prvek obsažen v množině, pomocí operátoru in.

>>> A = set('tentotext')
>>> print A
set(['x', 'e', 't', 'o', 'n'])
>>> B = A.copy()
>>> B.add('m')
>>> for i in B:
... print i
...
e
m
o
n
t
x 

Všechny operace mohou být realizovány jednak pomocí metod objektů, jednak pomocí operátorů. Tak například sjednocení lze vytvořit pomocí A.union(B) nebo A | B, obdobně průnik pomocí A.intersection(B) nebo A & B. Operace prováděné pomocí metod lze aplikovat i např. na množiny a řetězce (na rozdíl od operátorového tvaru):

>>> C = set('abc')
>>> C.union('xyz')
set(['a', 'c', 'b', 'y', 'x', 'z']) 

V případě, kdy se výše zmíněné operátorové operace provádějí na množinách rozdílných typů, má vždy výsledek typ prvního operandu. Všechny operace mají ještě aktualizační tvar:

>>> D = set('12345')
>>> D.intersection_update('456')
>>> print D
set(['5', '4'])

>>> D |= set('abc')
>>> print D
set(['a', 'c', 'b', '5', '4']) 

Množiny lze porovnávat pomocí operátorů ==, <=, >=, které jsou definovány pomocí podmnožin. Výraz A == B platí právě tehdy, když A je podmnožinou B a B je podmnožinou A, A <= B, pokud A je podmnožinou B. Ovšem kvůli těmto relacím nelze definovat uspořádání, neboť pro množiny, které nejsou ve vztahu podmnožina/nad­množina neplatí ani jedna relace. Proto např. nelze uspořádat posloupnost množin.

Modul subprocess

Díky tomuto modulu se unifikuje spouštění podprocesů. Pomocí jednotného rozhraní nahrazuje funkce modulů os a popen2 pro vytvoření a komunikaci s podprocesem. Obsahuje definici třídy Popen a funkce call().

Každá instance reprezentuje jeden podproces. Chování a způsob spolupráce s jednotlivými podprocesy lze modifikovat pomocí argumentů konstruktoru třídy Popen. Pomocí nich lze modifikovat argumenty předané podprocesu, typ standardního vstupu (roura, deskriptor souboru, souborový objekt) a prostředí podprocesu. Vše lze nalézt v dokumentaci modulu, proto si zde ukážeme pouze jedno z možných použití:

from subprocess import *
dpkg = Popen(['dpkg', '-L', 'tetex-doc'], stdout=PIPE)
grep = Popen(['grep', 'pdftex'], stdin=dpkg.stdout, stdout=PIPE)
output = grep.stdout.read() 

Výše uvedený příklad vytvoří dva podprocesy. První spustí příkaz dpkg, jenž vypíše všechny soubory balíčku tetex-doc. Výstup je poslán do roury, která je vstupem příkazu grep, ten ze seznamu vybere soubory obsahující v názvu či v cestě řetězec pdftex. Výstup je následně poslán do roury, která je přístupná jako atribut stdout vytvořené instance. Jde o klasický souborový objekt, čili pomocí metody read() jej můžeme načíst.

Modul doctest

Poslední modul, který si dnes ukážeme, realizuje velmi zajímavou myšlenku. Python již od začátku své existence používá tzv. dokumentační řetězce. Tyto řetězce slouží jako dokumentace jednotlivých modulů, tříd nebo funkcí a existují nástroje, které tyto řetězce dokáží vyextrahovat a sestavit z nich dokumentaci (za všechny například skript pydoc).

Dokumentace se nejlépe udržuje konzistentní s aktuálním stavem programu, pokud se nechá aktualizovat zároveň s úpravami kódu. Autoři modulu doctest dotáhli tuto myšlenku k dokonalosti. Mnoho programátorů v dokumentačních řetězcích uvádí i příklady použití. Pokud je zapíší jako kopii interaktivního sezení s interpretrem jazyka, lze je pomocí tohoto modulu znovu vykonat a porovnat skutečný a očekávaný výstup programu.

Budeme pracovat s modulem (sum.py) obsahujícím funkci pro výpočet součtu prvků seznamu (verze se zvýrazněnou syntaxí):

# -*- coding: iso-8859-2 -*-
def sum(l):
   '''Sečte posloupnost prvků

   Použití:
      >>> sum([1, 2, 3])
      6
      >>> sum([])
      0
      >>> sum(['a', 2, 3])
      Traceback (most recent call last):
      ...
      ValueError: bad sequence
   '''
   try:
      s = l[0]
      for i in l[1:]:
         s+=i
      return s
   except IndexError:
      return 0
   except TypeError:
      raise ValueError, 'bad sequence'

if __name__ == '__main__':
   import doctest
   doctest.testmod() 

Následně můžeme vykonat všechny testy modulu díky vykonání tohoto modulu pomocí příkazu python sum.py. Implicitně modul doctest vypíše hlášení pouze v případě nalezení chyby. Chcete-li znát detailní zprávu o všech testech v modulu, stačí použít python sum.py -v.

To nejdůležitější se děje na konci modulu. Načte se modul doctest a spustí se jeho funkce testmod(), která provede otestování aktuálního modulu. Projde všechny dokumentační řetězce a vyhledává v nich části interaktivního sezení. Ty posléze vykonává a porovnává požadovaný výstup se skutečným, přičemž při neshodě vypíše chybové hlášení.

V našem případě postupně provádí volání funkce sum([1, 2, 3]), sum([]) a sum([‚a‘, 2, 3) a porovnává je s požadovanými výstupy 6, 0, přičemž dokáže správně zpracovat i hlášení o výjimkách, viz třetí ukázku v dokumentačním řetězci. Tato hlášení pozná podle hlavičky začínající Traceback (most recent call last):. Následující traceback je ignorován až po část, kde ze objeví jméno výjimky spolu s detailem o ní. Celý traceback je možné jako v příkladu výše nahradit třemi tečkami. Modulu doctest lze rovněž nastavit, aby ignoroval detail výjimky, tj. vše za dvojtečkou oddělující jméno výjimky od zbytku řádky. Pokud modul najde chybu (tj. rozdíl ve výstupech), vypíše o ní zprávu (zde chyba spočívá ve špatně uvedené očekávané hodnotě v příkladu výše):

bitcoin_skoleni

***************************************
File "test.py", line 6, in __main__.sum
Failed example:
 sum([1, 2, 3])
Expected:
 'Chyba'
Got:
 6
***************************************
1 items had failures:
 1 of 3 in __main__.sum
***Test Failed*** 1 failures. 

Kromě funkcí a tříd definovaných v modulu lze do slovníku __test__ definovaného na globální úrovni modulu uložit další třídy a funkce, v nichž budou vyhledávány dokumentační testy. To může být využito k napsání specializovaného modulu určeného k testování celého balíčku modulů. V tomto případě jsou totiž různé třídy a funkce definovány v různých modulech, a pokud bychom je pouze importovali do testovacího modulu, doctest by jejich testy nevykonal, neboť v testovacím modulu nejsou definovány.

Jak jsme viděli, Python 2.4 je dalším vývojovým krokem v historii jazyk. Bohužel si nejsem jist, nakolik jsou různé změny uvážené. Zatímco generátorové výrazy a dekorátory uvedené v předchozí části mě velmi těší, definování různých „konstant“ a datových typů jako Decimal pouze zhoršuje čistotu jazyka a zbytečně nabobtnává standardní knihovnu, která by dozajista také potřebovala hlubokou liposunkci.