Úvod k seriálu
Python je velmi zajímavý jazyk. Opravdu je. Na první pohled může vypadat obyčejně, jen s trochu dobrými vychytávkami, avšak po detailnějším zkoumání je tomu jinak. S Pythonem jde vývoj rychle. Nemusíte stále dokola řešit stejné věci. Python pomáhá nové vlastnosti naprogramovat znovupoužitelně.
Co z Pythonu vám chci ukázat? Něco se hodí do příručky pro zelenáče, něco vyžaduje mít jisté znalosti. Zahrnul jsem vše, co mi přišlo zajímavé, a o čem se málo ví. Vznikl tak seriál, kde si snad každý najde to svoje. Dnes začneme ukázkou syntaktických záležitostí a postupně se můžete těšit na různé užitečné tipy, návrhové vzory po pythonovsku a nakonec se těšte na povídání o metatřídách.
Všechny ukázky plně fungují v Pythonu 2.x. Úpravy pro Python 3 jsou minimální, takže se o nich ani nezmiňuji. Pokud však existuje v Pythonu 3 něco navíc nebo se tam vyskytuje nějaká zásadní změna, tak se o tom zmíním.
Začínáme
Pořadí bude, jak už jsem zmínil, od toho nejlehčího, tak schválně, kde odpadnete. :-) Pojďme nyní na první ukázku!
(lambda row: ' '.join(str(cell) if isinstance(cell, (int, long)) else ' ' if cell is None else (cell[::-1].capitalize() if not idx else cell) for idx, cell in enumerate(row) if cell))([6*7, 'is', None, 'rewsna'][::-1])
Uff, z ukázky jsou vidět dvě věci, možná tři: Za prvé, je to nečitelné. Takovým konstrukcím se určitě vyhýbat! Za druhé – v Pythonu je možné prasit, i když nabádá ke štábní kultuře. Proto programujte s otevřenýma očima. A za třetí, jen jsem vás chtěl trochu vylekat. Klid. A teď už se do toho opravdu pusťme.
Ternární operátor
Začneme ternárním operátorem. Já jsem ho dlouho neznal a vadilo mi, že v Pythonu není (ach, jak pošetilé je důvěřovat knížkám). Nejprve jsem to tedy dělal klasicky přes podmínku.
if y: x = 'some string' else: x = 'other string'
Zdlouhavé. Když už nic lepšího, alespoň jsem si to zjednodušoval (což lze ve všech jazycích), ač trochu neefektivně. V dnešní době však není podstatné mít efektivitu neustále na paměti.
x = 'other string' if y: x = 'some string'
Když tu jsem jednoho dne prozřel – našel jsem náhražku za ternární operátor!
x = y and 'some string' or 'other string'
Bohužel netrvalo moc dlouho a narazil jsem na problém, kde to nevyhovuje. To když přiřazovaná hodnota při kladném vyhodnocení podmínky je v booleanském smyslu falešná. Ukázka objasní:
x = False and 'a' or 'b' # 'b' x = True and 'a' or 'b' # 'a' x = False and '' or 'b' # 'b' x = True and '' or 'b' # 'b' - ajajaj
Pokud jste tedy nuceni používat Python starší než 2.5 a chcete využívat této konstrukce, dejte si pozor! Případně použijte menší trik (bohužel už to je nepřehledné a raději bych se tomu vyhnul):
x = (True and [''] or ['b'])[0]
Nebudu už ale napínat – jakže vypadá (bezchybný) ternární operátor v Pythonu dostupný od Pythonu 2.5? Takhle:
x = 'some string' if y else 'other string'
Na první pohled se to může zdát trochu nelogické, ale jde jen o zvyk. Zkuste si říct v hlavě klasický ternární operátor ?:
. Budete říkat něco jako „když je tohle takhle, tak tamto, jinak tamhleto.“ Zatímco tento lze hezky česky přečíst jako „tamto když je tohle takhle, jinak tamhleto.“ Což mně osobně přijde lepší.
A lze samozřejmě také hezky řetězit…
x = 'variable is %s' % 'one' if y == 1 else 'two' if y == 2 else 'three'
Místo řetězení je ale už lepší použít klasické podmínky. Nebo alespoň přidat závorky, protože to u složitějších konstrukcí může oklamat i takového Tuvoka!
I cyklus má svůj blok else
Řekl bych, že o tom ví velmi málo lidí. A opravdu for
cyklus může mít blok else
(pravděpodobně záviděl podmínce, holomek). Mockrát se to nepoužije, to je pravda, ale ani tak kvůli nevhodnosti, jako kvůli nezažití. Už jsem si na něj párkrát vzpomněl a byl mi užitečný. Proto se o něm zmiňuji.
Vezměme si příklad: máme seznam automobilů a u jednoho (nebo i u více) potřebujeme odemknout kufr. Pokud ale nenajdeme automobil, u kterého ten kufr potřebujeme odemknout, musíme zavolat nějakou rutinu, třeba informovat uživatele, že pravděpodobně někdo automobil odcizil. Normálně by se napsalo něco jako:
obj = None for item in listOfCars: if item.isCarWhichILookingFor(): obj = item obj.unlockTrunk() break if obj is None: print 'Somebody stole car!'
Je to poměrně upovídané řešení. A co je upovídané, u toho je větší šance vzniku chyb. Proto si teď ukážeme, jak funguje blok else
u cyklu. Slovy: jestliže cyklus doběhne bez přerušení až nakonec, provede se i blok else
, jinak nikoliv. Ukázka:
for x in range(5): if x == 1: break else: print "else" # Zde se else nevypise. for x in range(5): pass else: print "else" # Zde se else vypise.
S novými vědomostmi zkusíme původní ukázku upravit. Zřejmě už tušíte jak.
for item in listOfCars: if item.isCarWhichILookingFor(): obj.unlockTrunk() break else: print 'Somebody stole car!'
Blok else
nezáviděl pouze cyklus for
, ale také while
. Funguje to pochopitelně úplně stejně.
x = 1 while x <= 5: try: print '%d. attempt to connect to the database.' % x connectToDatabase() except ConnectTimeoutException: x += 1 else: break else: print 'Connection Error!'
Iterace v opačném pořadí
Z předešlého příkladu máme seznam automobilů. S tímto seznamem chceme něco udělat (třeba vypsat do souboru a odeslat kamarádovi), ale v opačném pořadí. Céčkař by pravděpodobně psal šílená céčkovská céčka (nic proti :-).
for i in range(len(listOfCars)-1, -1, -1): obj = listOfCars[i] # Do something...
Ale my jsme přeci pythonisti! Takže zkusme ukázku upravit:
listOfCars .reverse()
for item in listOfCars:
# Do something...
Bohužel to ale zmodifikuje celý seznam. Mohu být zrovna uvnitř nějaké metody, která tento seznam dostala parametrem, a přeci nebudu někomu měnit objekty pod rukama. Pravděpodobně by programátor volající naši metodu nebyl zrovna nadšen. Proto sáhneme po built-in funkci, která iteruje nad seznamem (nebo jakékoliv jiné seznamové instanci) v opačném pořadí.
for item in reversed(listOfCars): # Do something...
Teď už to je dobré. Samotný list neměním a nemusím se moc upisovat. Ale stále to lze zkrátit. Pomůžu si takzvanou slice notation ( listOfCars[…]
). A že to nejde? Že lze pouze ze seznamu určovat „výřezy“? Kdepak…
for item in listOfCars[::-1]: # Do something...
Ona slice notace má totiž ještě třetí parametr, o kterém moc lidí neví, a ten určuje, o kolik se má iterace posouvat na další položku; defaultně o jednu. Tedy [2:3]
je to samé, jako [2:3:1]
, tedy vezme to třetí položku (jedná se o interval zprava otevřený). Pokud napíšu [0:6:2]
, tak to vycucne první, třetí (skok o dva) a pátou (zase skok o dva) položku; dál už to nepůjde, protože je to omezené zprava.
Slice notace lze dokonce zapsat i built-in metodou slice
a tuto instanci (kterou ta metoda vrátí) použít jako klasickou slice notaci.
sl = slice(None, None, -1) if reversedOrder else slice(None) for item in listOfCars[sl]: # Do something...
Tím si můžeme připravit scénář průchodu seznamem v jedné funkci a vykonat iteraci v druhé. Podle mého názoru slice notace už nemůže být lepší!
Hrátky s proměnnými
Čas od času je potřeba vrátit z funkce více hodnot. Řešit tuto potřebu lze různě, od vracení seznamu čí slovníku přes vracení nějaké jiné speciální instance až po rozdělení na více funkcí. Záleží na okolnostech, co je zrovna vhodnější. Například můžeme chtít mít funkci, která z nějakého vstupu rozparsuje klíč a hodnotu:
def parseSameInput(input): # Hloupy Honza pro prezentacni ucely. return input.split('=') output = parseSomeInput('key=value') k = output[0] v = output[1]
Řešení to je jednoduché, ale stále to není po pythonsku. Kdo by se chtěl takhle upisovat? Existuje snadnější způsob a řekl bych, že už jej znáte, jen si neuvědomujete možnost ho použít kdekoliv.
output = parseSomeInput('hello=world') k, v = output
Tento způsob se vlastně používá hojně při iteraci slovníku, to staré známé for k, v in d.iteritems()
. Ukázka lze však ještě více zjednodušit (všimněte si, že funkci není třeba nijak upravovat).
k, v = parseSomeInput('foo=bar')
A jak to funguje? Nijak zázračně, stačí si uvědomit, že jakmile uděláme čárku, je z toho tuple. A tuple lze rozložit do více proměnných.
a = 3, 5 b = (3, 5) if a == b: print "Promenne jsou stejne!"
Tím lze i snadno prohodit proměnné – pamatujete si na váš první řadící algoritmus bubble sort, kde jste prohazovali proměnné, a museli si nadefinovat pomocnou proměnnou? Zkusme to nyní znovu v Pythonu.
tmp = a a = b b = tmp # vs. a, b = b, a
Tím ale naše možnosti nekončí, nový Python 3 zvládne ještě jednu specialitu. Ono je to ve skutečnosti logická věc, ale ne každému to může dojít (a vývojářům při vývoji Pythonu 2 ani nedošlo) – tuple (nebo seznam) smí obsahovat více prvků, než do kolika proměnných má být rozložen.
one, two, three, *fourfivesix = range(1, 7)
Jen je potřeba nezapomenout na hvězdičku u poslední proměnné, která má pojmout zbytek.
A když už jsem u toho definování proměnných, tak ještě přidám zmínku na built-in funkce locals
a globals
. První zmíněná vrací referenci na slovník s lokálními proměnnými a druhá s globálními proměnnými. Když se nad tím člověk zamyslí, tak lze klidně vytvořit následující absurdum.
locals().update(dict((chr(k), v) for k, v in zip(range(97,102), range(1,6))))
Předchozí ukázka v lokálním kontextu vytvoří pět proměnných a až e s postupnými hodnotami 1 až 5. Zkuste si to rozluštit sami.
Závěr
Jak je vidět, Python hodně přispívá k rychlému vývoji. Byla by velká škoda ho minimálně nezkusit, protože dnešními tipy to nekončí, dnes to byly spíš jen takové srandičky. Příště budeme pokračovat a konkrétně se podíváme na některé pokročilejší techniky jako generátory, lambda funkce či with
konstrukce. Dnes už jen přidám pár odkazů pro další studium:
- Boolean operace v oficiální dokumentaci.
- Na wikipedii se můžete dočíst, jak lze také upravit trik
and or
, aby vždy fungoval. - Další trik, jak simulovat ternární operátor.
- Ternární operátory v oficiální dokumentaci.
- Slice notation v oficiální dokumentaci.
- Další užitečný příklad, kde lze použít vícenásobné přiřazování.
Přehled komentářů