Nacházíte se zde: Domů ‣ Ponořme se do Pythonu 3 ‣
Úroveň obtížnosti: ♦♦♢♢♢
❝ Our imagination is stretched to the utmost, not, as in fiction, to imagine things which are not really there, but just to comprehend those things which are. ❞
(Naše představivost je napjatá do krajnosti. Ne jako u fikce, abychom si představili věci, které zde nejsou, ale proto, abychom jen obsáhli věci, které jsou zde.)
— Richard Feynman
V každém programovacím jazyce najdeme určitý rys, který záměrně zjednodušuje nějakou komplikovanou věc. Pokud přicházíte se zkušenostmi z jiného jazyka, můžete to snadno přehlédnout, protože váš starý jazyk právě tu určitou věc nezjednodušoval (protože dalo práci místo toho zjednodušit něco jiného). V této kapitole se seznámíme s generátorovou notací seznamů (list comprehensions), s generátorovou notací slovníků (dictionary comprehensions) a s generátorovou notací množin (set comprehensions). Jde o tři související koncepty, jejichž jádrem je jedna velmi mocná technika. Ale nejdříve si uděláme malou odbočku ke dvěma modulům, které vám usnadní orientaci ve vašem lokálním souborovém systému.
⁂
Python 3 se dodává s modulem zvaným os
, což je zkratka pro „operační systém“. Modul os
obsahuje spoustu funkcí pro získávání informací o lokálních adresářích, souborech, procesech a proměnných prostředí — a v některých případech s nimi umožňuje manipulovat. Python se snaží co nejlépe, aby pro všechny podporované operační systémy nabízel jednotné API (aplikační programové rozhraní). Cílem je, aby vaše programy běžely na libovolném počítači a aby přitom obsahovaly co nejméně kódu, který by byl závislý na platformě.
Pokud s Pythonem právě začínáte, strávíte ještě hodně času v pythonovském shellu. V celé knize se budete setkávat s příklady, jako je tento:
examples
(příklady).
Pokud o aktuálním pracovním adresáři nic nevíte, pak krok 1 pravděpodobně selže a objeví se výjimka ImportError
. Proč? Protože Python se bude po modulu dívat ve vyhledávací cestě pro import, ale nenajde jej, protože adresář examples
se v žádném adresáři z vyhledávací cesty nenachází. Aby to prošlo, můžete udělat jednu ze dvou věcí:
examples
přidáte do vyhledávací cesty pro import.
examples
.
Aktuální pracovní adresář je neviditelný údaj, který si Python neustále udržuje v paměti. Aktuální pracovní adresář existuje vždy — ať už jste v pythonovském shellu, spouštíte svůj vlastní pythonovský skript z příkazového řádku nebo spouštíte pythonovský CGI skript na nějakém webovém serveru.
Pro vypořádání se s aktuálním pracovním adresářem nabízí modul os
dvě funkce.
>>> import os ① >>> print(os.getcwd()) ② C:\Python31 >>> os.chdir('/Users/pilgrim/diveintopython3/examples') ③ >>> print(os.getcwd()) ④ C:\Users\pilgrim\diveintopython3\examples
os
je součástí Pythonu. Můžete jej importovat kdykoliv a kdekoliv.
os.getcwd()
. Pokud používáte grafický pythonovský shell, pak se aktuální pracovní adresář zpočátku nachází v adresáři, ve kterém je umístěn spustitelný program pythonovského shellu. Při práci pod Windows to záleží na tom, kam jste Python nainstalovali. Výchozí adresář je c:\Python31
. Pokud používáte konzolový pythonovský shell, pak se aktuální pracovní adresář zpočátku nachází v adresáři, ve kterém jste spustili python3
.
os.chdir()
.
os.chdir()
jsem použil cestu v linuxovém stylu (normální lomítka, žádné písmeno disku), i když pracuji pod Windows. To je právě jedno z míst, kde se Python snaží zamaskovat rozdíly mezi operačními systémy.
Když už se bavíme o adresářích, chtěl bych vás upozornit na modul os.path
. Ten obsahuje funkce pro manipulace se jmény souborů a adresářů.
>>> import os >>> print(os.path.join('/Users/pilgrim/diveintopython3/examples/', 'humansize.py')) ① /Users/pilgrim/diveintopython3/examples/humansize.py >>> print(os.path.join('/Users/pilgrim/diveintopython3/examples', 'humansize.py')) ② /Users/pilgrim/diveintopython3/examples\humansize.py >>> print(os.path.expanduser('~')) ③ c:\Users\pilgrim >>> print(os.path.join(os.path.expanduser('~'), 'diveintopython3', 'examples', 'humansize.py')) ④ c:\Users\pilgrim\diveintopython3\examples\humansize.py
os.path.join()
sestaví cestu z jedné nebo více částí cesty. V tomto případě jednoduše spojí řetězce.
os.path.join()
před napojením jména souboru navíc přidá k cestě jedno lomítko. Místo obyčejného lomítka použila zpětné lomítko, protože jsem tento příklad pouštěl pod Windows. Pokud byste stejný příklad zkoušeli na systémech Linux nebo Mac OS X, použilo by se normální lomítko. Nepárejte se s lomítky. Používejte vždy os.path.join()
a nechejte na Pythonu, aby udělal, co je správné.
os.path.expanduser()
rozepíše cestu, která pro vyjádření domácího adresáře aktuálního uživatele používá znak ~
. Funguje to na libovolné platformě, kde mají uživatelé přidělený svůj domácí adresář, tedy na Linuxu, Mac OS X a ve Windows. Vrácená cesta neobsahuje koncové lomítko, ale to funkci os.path.join()
nevadí.
os.path.join()
přebírá libovolný počet argumentů. Jakmile jsem to zjistil, skákal jsem radostí, protože při přípravě mých nástrojů v nějakém novém jazyce je addSlashIfNecessary()
(přidejLomítkoPokudJeToNutné) jednou z těch otravných malých funkcí, které si musím vždy znovu napsat. V Pythonu takovou funkci nepište. Chytří lidé už se o to postarali za vás.
Modul os.path
obsahuje také funkce, které umí rozdělit plné cesty, jména adresářů a souborů na jejich podstatné části.
>>> pathname = '/Users/pilgrim/diveintopython3/examples/humansize.py' >>> os.path.split(pathname) ① ('/Users/pilgrim/diveintopython3/examples', 'humansize.py') >>> (dirname, filename) = os.path.split(pathname) ② >>> dirname ③ '/Users/pilgrim/diveintopython3/examples' >>> filename ④ 'humansize.py' >>> (shortname, extension) = os.path.splitext(filename) ⑤ >>> shortname 'humansize' >>> extension '.py'
split
rozdělí plnou cestu a vrátí n-tici, která obsahuje zvlášť cestu a zvlášť jméno souboru.
os.path.split()
dělá přesně tohle. Výsledek funkce split
přiřadíme do n-tice s dvěma proměnnými. Každá z proměnných získá hodnotu odpovídajícího prvku vracené dvojice.
os.path.split()
, a sice cestu k souboru.
os.path.split()
, jméno souboru.
os.path
obsahuje také funkci os.path.splitext()
, která rozdělí jméno souboru a vrací dvojici obsahující jméno souboru bez přípony a příponu. Pro jejich přiřazení do oddělených proměnných použijeme stejnou techniku.
Dalším nástrojem z pythonovské standardní knihovny je modul glob
. Umožní nám z programu snadno získat obsah nějakého adresáře. Používá typ zástupných znaků (wildcards), které už asi znáte z práce na příkazovém řádku.
>>> os.chdir('/Users/pilgrim/diveintopython3/') >>> import glob >>> glob.glob('examples/*.xml') ① ['examples\\feed-broken.xml', 'examples\\feed-ns0.xml', 'examples\\feed.xml'] >>> os.chdir('examples/') ② >>> glob.glob('*test*.py') ③ ['alphameticstest.py', 'pluraltest1.py', 'pluraltest2.py', 'pluraltest3.py', 'pluraltest4.py', 'pluraltest5.py', 'pluraltest6.py', 'romantest1.py', 'romantest10.py', 'romantest2.py', 'romantest3.py', 'romantest4.py', 'romantest5.py', 'romantest6.py', 'romantest7.py', 'romantest8.py', 'romantest9.py']
glob
zpracovává masku se zástupným znakem a vrací cesty ke všem souborům a adresářům, které masce se zástupným znakem odpovídají. V tomto příkladu je maska složena z cesty do adresáře a z „*.xml
“. Budou jí odpovídat všechny .xml
soubory v podadresáři examples
.
examples
. Funkce os.chdir()
umí pracovat i s relativními cestami.
glob
můžeme použít více zástupných znaků. Tento příklad nalezne v aktuálním pracovním adresáři všechny soubory, které končí příponou .py
a kdekoliv ve jméně souboru obsahují slovo test
.
Každý moderní souborový systém ukládá o každém souboru metadata, jako jsou: datum vytvoření, datum poslední modifikace, velikost souboru atd. Pro zpřístupnění těchto metadat poskytuje Python jednotné API. Soubor se nemusí otevírat. Vše, co potřebujete znát, je jeho jméno.
>>> import os >>> print(os.getcwd()) ① c:\Users\pilgrim\diveintopython3\examples >>> metadata = os.stat('feed.xml') ② >>> metadata.st_mtime ③ 1247520344.9537716 >>> import time ④ >>> time.localtime(metadata.st_mtime) ⑤ time.struct_time(tm_year=2009, tm_mon=7, tm_mday=13, tm_hour=17, tm_min=25, tm_sec=44, tm_wday=0, tm_yday=194, tm_isdst=1)
examples
.
feed.xml
je soubor ve složce examples
. Voláním funkce os.stat()
získáme objekt, který obsahuje několik různých typů informací o souboru (metadat).
st_mtime
zachycuje čas poslední modifikace, ale není uložen ve tvaru, který by byl moc použitelný. (Z technického pohledu je to počet sekund od Epochy, kde Epocha je definována jako první sekunda 1. ledna 1970. Vážně!)
time
je součástí standardní pythonovské knihovny. Obsahuje funkce pro převody mezi různými reprezentacemi času, pro formátování času do řetězcové podoby a pro hraní si s časovými zónami.
time.localtime()
převádí hodnotu času ze sekund-od-Epochy (z položky st_mtime
objektu vraceného funkcí os.stat()
) na použitelnější strukturu obsahující rok, měsíc, den, hodinu, minutu, sekundu atd. Tento soubor byl naposledy změněn 13. července 2009 přibližně v 17 hodin a 25 minut.
# pokračování předchozího příkladu >>> metadata.st_size ① 3070 >>> import humansize >>> humansize.approximate_size(metadata.st_size) ② '3.0 KiB'
os.stat()
vrací také velikost souboru, a to v položce st_size
. Soubor feed.xml
obsahuje 3070
bajtů.
st_size
můžeme předat funkci approximate_size()
.
V předcházející podkapitole jsme voláním funkce glob.glob()
získali seznam s relativními cestami. V prvním příkladu jsme získali cesty jako 'examples\feed.xml'
. V druhém příkladu jsme získali dokonce ještě kratší relativní cesty jako 'romantest1.py'
. Za předpokladu, že zůstaneme ve stejném pracovním adresáři, můžeme tyto relativní cesty používat pro otevření souborů nebo pro získávání jejich metadat. Ale pokud chceme vytvořit absolutní cestu — tj. takovou, která obsahuje jména všech adresářů až po kořenový adresář nebo včetně jména disku —, budeme potřebovat funkci os.path.realpath()
.
>>> import os >>> print(os.getcwd()) c:\Users\pilgrim\diveintopython3\examples >>> print(os.path.realpath('feed.xml')) c:\Users\pilgrim\diveintopython3\examples\feed.xml
⁂
Generátorová notace seznamu (anglicky list comprehension [list komprihenšn]) umožňuje stručný zápis vytvoření seznamu z jiného seznamu aplikováním funkce na všechny prvky zdrojového seznamu. (Poznámka překladatele: Pojem „list comprehension“ je znám z deklarativních jazyků a má charakter syntaktické konstrukce. V jazyce Python se „vnitřku“ deklarativního zápisu podobá generátorový výraz. Tímto způsobem byl odvozen český pojem „generátorová notace“. Někdy je pojem „list comprehension“ použit v procedurálním, dynamickém smyslu. V takové situaci můžeme uvažovat o pojmu „generátor seznamu“. Pokud se bavíme o jeho výsledku, můžeme uvažovat i o pojmu „generovaný seznam“. Vzhledem k tomu, že zavedený český pojem pro tuto konstrukci asi neexistuje — studentům příslušných oborů vysokých škol přijde po krátké chvíli anglický pojem srozumitelný —, budu volněji používat některou z uvedených variant. Někdy budu poněkud dlouhý pojem „generátorová notace seznamu“ zkracovat. Kritériem volby bude dobrá srozumitelnost.)
>>> a_list = [1, 9, 8, 4] >>> [elem * 2 for elem in a_list] ① [2, 18, 16, 8] >>> a_list ② [1, 9, 8, 4] >>> a_list = [elem * 2 for elem in a_list] ③ >>> a_list [2, 18, 16, 8]
elem * 2
a připojí výsledek na konec cílového seznamu.
V generátorové notaci seznamu můžeme využít libovolný pythonovský výraz, včetně funkcí z modulu os
, které slouží k manipulaci se soubory a adresáři.
>>> import os, glob >>> glob.glob('*.xml') ① ['feed-broken.xml', 'feed-ns0.xml', 'feed.xml'] >>> [os.path.realpath(f) for f in glob.glob('*.xml')] ② ['c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-broken.xml', 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-ns0.xml', 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed.xml']
.xml
souborů v aktuálním pracovním adresáři.
.xml
souborů a transformuje jej na seznam jmen s plnou cestou.
Generátorová notace seznamu může navíc předepisovat i filtraci položek. To znamená, že může vyprodukovat výsledek, který bude kratší než původní seznam.
>>> import os, glob
>>> [f for f in glob.glob('*.py') if os.stat(f).st_size > 6000] ①
['pluraltest6.py',
'romantest10.py',
'romantest6.py',
'romantest7.py',
'romantest8.py',
'romantest9.py']
if
na konec generátorové notace. Pro každou položku seznamu bude vyhodnocen výraz za klíčovým slovem if
. Pokud je výsledkem výrazu True
, pak bude položka zahrnuta do výstupu. Tato generátorová notace seznamu předepisuje zpracování všech souborů s příponou .py
v aktuálním adresáři. Výraz za if
zajišťuje filtraci seznamu testováním, zda je velikost každého souboru větší než 6000
bajtů. Takových souborů je šest, takže generátorová notace produkuje seznam se šesti jmény souborů.
Všechny předchozí příklady generátorové notace seznamu používaly jen jednoduché výrazy — násobení čísla konstantou, volání jedné funkce, nebo jednoduše vracely původní položky seznamu (po filtraci). Ale generátorová notace seznamu může být libovolně složitá.
>>> import os, glob >>> [(os.stat(f).st_size, os.path.realpath(f)) for f in glob.glob('*.xml')] ① [(3074, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-broken.xml'), (3386, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-ns0.xml'), (3070, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed.xml')] >>> import humansize >>> [(humansize.approximate_size(os.stat(f).st_size), f) for f in glob.glob('*.xml')] ② [('3.0 KiB', 'feed-broken.xml'), ('3.3 KiB', 'feed-ns0.xml'), ('3.0 KiB', 'feed.xml')]
.xml
, zjistí velikost každého z nich (voláním funkce os.stat()
) a vytvoří dvojice obsahující jméno souboru a absolutní cestu k souboru (voláním funkce os.path.realpath()
).
.xml
souboru se volá funkce approximate_size()
.
⁂
Generátorová notace slovníku (anglicky dictionary comprehension [dikšenri komprihenšn]) se podobá generátorové notaci seznamu, ale místo seznamu popisuje vytvoření slovníku.
>>> import os, glob >>> metadata = [(f, os.stat(f)) for f in glob.glob('*test*.py')] ① >>> metadata[0] ② ('alphameticstest.py', nt.stat_result(st_mode=33206, st_ino=0, st_dev=0, st_nlink=0, st_uid=0, st_gid=0, st_size=2509, st_atime=1247520344, st_mtime=1247520344, st_ctime=1247520344)) >>> metadata_dict = {f:os.stat(f) for f in glob.glob('*test*.py')} ③ >>> type(metadata_dict) ④ <class 'dict'> >>> list(metadata_dict.keys()) ⑤ ['romantest8.py', 'pluraltest1.py', 'pluraltest2.py', 'pluraltest5.py', 'pluraltest6.py', 'romantest7.py', 'romantest10.py', 'romantest4.py', 'romantest9.py', 'pluraltest3.py', 'romantest1.py', 'romantest2.py', 'romantest3.py', 'romantest5.py', 'romantest6.py', 'alphameticstest.py', 'pluraltest4.py'] >>> metadata_dict['alphameticstest.py'].st_size ⑥ 2509
.py
, které ve svém jméně obsahují podřetězec test
. Pak se vytvoří dvojice obsahující jméno souboru a jeho metadata (voláním funkce os.stat()
).
f
) představuje klíč slovníku. Výraz za dvojtečkou (v našem případě os.stat(f)
) je hodnota.
glob.glob('*test*.py')
.
os.stat()
. To znamená, že v tomto slovníku můžeme na základě jména souboru „vyhledat“ jeho metadata. Jednou z částí metadat je st_size
, zachycující velikost souboru. Soubor alphameticstest.py
obsahuje 2509
bajtů.
Také u generátorové notace slovníků (podobně jako u generátorové notace seznamů) můžeme přidat podmínku if
, která zajistí filtraci vyhodnocením výrazu pro každou položku vstupní posloupnosti.
>>> import os, glob, humansize >>> metadata_dict = {f:os.stat(f) for f in glob.glob('*')} ① >>> humansize_dict = {os.path.splitext(f)[0]:humansize.approximate_size(meta.st_size) \ ... for f, meta in metadata_dict.items() if meta.st_size > 6000} ② >>> list(humansize_dict.keys()) ③ ['romantest9', 'romantest8', 'romantest7', 'romantest6', 'romantest10', 'pluraltest6'] >>> humansize_dict['romantest9'] ④ '6.5 KiB'
glob.glob('*')
), získává metadata každého souboru (os.stat(f)
) a vytváří slovník, jehož klíči jsou jména souborů a k nim přiřazené hodnoty jsou metadata každého souboru.
6000
bajtů (if meta.st_size > 6000
) a takto přefiltrovaný seznam používá k vytvoření slovníku. Jeho klíče tvoří jména souborů bez přípony (os.path.splitext(f)[0]
) a hodnotami jsou přibližné velikosti těchto souborů (humansize.approximate_size(meta.st_size)
).
approximate_size()
.
Následující trik využívající generátorové notace slovníku se nám jednoho dne může hodit. Jde o vzájemnou záměnu klíčů a hodnot slovníku.
>>> a_dict = {'a': 1, 'b': 2, 'c': 3} >>> {value:key for key, value in a_dict.items()} {1: 'a', 2: 'b', 3: 'c'}
Bude to samozřejmě fungovat jen v případě, kdy jsou hodnoty ve slovníku neměnitelného typu (immutable), jako jsou řetězce nebo n-tice. Pokud totéž zkusíte se slovníkem, který obsahuje seznamy, dojde k velkolepé havárii.
>>> a_dict = {'a': [1, 2, 3], 'b': 4, 'c': 5} >>> {value:key for key, value in a_dict.items()} Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <dictcomp> TypeError: unhashable type: 'list'
⁂
Neměli bychom opomenout, že i syntaxe pro množiny zahrnuje generátorovou notaci. Pozoruhodně se podobá syntaxi pro generátorový zápis slovníků. Jediný rozdíl spočívá v tom, že množiny mají místo párů klíč: hodnota jen hodnoty.
>>> a_set = set(range(10)) >>> a_set {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} >>> {x ** 2 for x in a_set} ① {0, 1, 4, 81, 64, 9, 16, 49, 25, 36} >>> {x for x in a_set if x % 2 == 0} ② {0, 8, 2, 4, 6} >>> {2**x for x in range(10)} ③ {32, 1, 2, 4, 8, 64, 128, 256, 16, 512}
0
do 9
.
if
, která vstupní položky před zařazením do výsledné množiny filtruje.
⁂
os
(standardní dokumentace)
os
— Portable access to operating system specific features (přenositelné zpřístupnění specifických vlastností vázaných na operační systém)
os.path
(standardní dokumentace)
os.path
— Platform-independent manipulation of file names (platformově nezávislá manipulace se jmény souborů)
glob
(standardní dokumentace)
glob
— Filename pattern matching (vyhledávání souborů podle vzorků)
time
(standardní dokumentace)
time
— Functions for manipulating clock time (funkce pro manipulaci času hodin)
© 2001–11 Mark Pilgrim