2 | ||
Editor: mol
Time: 2013/09/04 21:58:25 GMT+2 |
||
Note: fixed wikilink |
changed: - <h1>10 pastí jazyka Python</h1> <hr> <p>(C) 2003 <a href= "mailto:hans@zephyrfalcon.org?subject=Python%20pitfalls">Hans Nowak</a>. Written: 2003.08.13. Last update: 2003.09.05.<br> Díky Blake Winton, Joe Grossberg, Steve Ferg, Lloyd Kvam za hodnotné připomínky.</p> <p>(C) 2005 Překlad <a href="http://www.geon.wz.cz">Pavel Kosina</a> a <a href="http://www.skil.cz/python">Petr Přikryl</a>. Přeloženo: srpen 2005<br> anglický originál leží http://zephyrfalcon.org/labs/python_pitfalls.html</p> <hr> <p>Nejde nutně o vady na kráse či nedodělky. Jsou to spíše vedlejší účinky vlastností jazyka, o které často zakopávají nováčci a někdy i zkušení programátoři. Při neúplném pochopení podstatných rysů chování jazyka Python se můžete spálit.</p> <p>Tento dokument má být jakýmsi průvodcem pro ty, pro které je jazyk Python něčím novým. Dozvědět o pastech a léčkách brzy je lepší, než narazit na ně ve vytvářeném kódu těsně před termínem odevzdání :-} Tento dokument <b>není</b> míněn jako kritika jazyka. Jak jsem již řekl, většina těchto pastí není způsobena vadami jazyka.</p> <h3>1. Nepořádné odsazování</h3> <p>Možná trochu laciné téma na úvod. Nicméně, mnozí nováčci mají zkušenost s jazyky, kde mezery "nehrají roli". <em>Cestou slepých uliček</em><sup class="trnote" id="B_indent" title="... jak formuloval jeden nedoceněný český velikán" ><a href="#P_indent">[1]</a></sup> mohou dojít až k nemilému překvapení, že je Python trestá za zlozvyk nepořádného odsazování.</p> <p><strong>Řešení</strong>: Dodržujte správné odsazování. Používejte buď mezery, nebo tabulátory<sup class="trnote" id="B_spaces" title="Doporučuje se dávat přednost mezerám."><a href="#P_spaces" >[2]</a></sup>, ale nikdy to nemíchejte. Kvalitní editor pomáhá.</p> <h3 id="pitfall2">2. Přiřazení, neboli jména a objekty</h3> <p>Lidé přicházející od staticky typovaných jazyků, jako je Pascal nebo C, často předpokládají, že Python zachází s proměnnými a s přiřazováním stejně, jako jejich oblíbený jazyk. Na první pohled to tak skutečně vypadá:</p> <pre> a = b = 3 a = 4 print a, b # 4, 3 </pre> <p>Dostávají se však do potíží, když začnou používat měnitelné objekty. Často pak hned přicházejí s tvrzením, že Python zachází s měnitelnými a neměnitelnými objekty rozdílně.</p> <pre> a = [1, 2, 3] b = a a.append(4) print b # b je nyní také [1, 2, 3, 4] </pre> <p>Stalo se to, že výraz <code>a = [1, 2, 3]</code> provedl dvě věci: 1. vytvořil objekt, v tomto případě seznam s hodnou [1, 2, 3]; 2. svázal ho se jménem <code>a</code> v lokálním prostoru jmen. Výraz <code>b = a</code> pak svázal jméno <code>b</code> s tím samým seznamem (na který již odkazuje <code>a</code>)<sup class="trnote" id="B_assign" title="a ještě jinými slovy..."><a href="#P_assign">[3]</a></sup>. Jakmile si toto uvědomíte, bude již méně obtížné pochopit, co vlastně <code>a.append(4)</code> dělá... že mění seznam, na který se odkazuje jak <code>a</code> tak <code>b</code>.</p> <p>Domněnka, že se s měnitelnými a neměnitelnými objekty při přiřazování zachází rozdílně, je mylná. Při přiřazování <code>a = 3</code> a <code>b = a</code> se děje přesně stejná věc, jako u výše uvedeného seznamu. Jména <code>a</code> i <code>b</code> nyní odkazují na stejný objekt — na číslo s hodnotou 3. Protože však čísla jsou neměnitelná (immutable), nepozorujete žádný vedlejší efekt <sup class="trnote" id="B_assign2" title="...detaily"><a href="#P_assign2">[4]</a></sup>.</p> <p><strong>Řešení</strong>: Přečtěte si <a href= "ObjektyJazykaPython" title="">toto</a>. Abyste se zbavili nechtěných vedlejších efektů, kopírujte (používejte metodu copy, operátory řezu (slice), atd). Python nikdy implicitně nekopíruje.</p> <h3>3. Operátor +=</h3> <p>V jazycích jako třeba C, jsou rozšířené přiřazovací operátory jako <code>+=</code> zkratkami pro delší výrazy. Například,</p> <pre> x += 42; </pre> <p>je syntaktická pomůcka (angličani říkají <em>syntactic sugar</em>) pro</p> <pre> x = x + 42; </pre> <p>Takže byste si mohli myslet, že tomu tak bude i v jazyce Python. Na první pohled to tak skutečně vypadá:</p> <pre> a = 1 a = a + 42 # a je 43 a = 1 a += 42 # a je 43 </pre> <p>Ale u měnitelných objektů (mutable) zápis <code>x += y</code> nemusí nutně vyjadřovat totéž, jako zápis <code>x = x + y</code>. Uvažujme seznamy:</p> <pre> >>> z = [1, 2, 3] >>> id(z) 24213240 >>> z += [4] >>> id(z) 24213240 >>> z = z + [5] >>> id(z) 24226184 </pre> <p>Příkaz <code>x += y</code> mění seznam na místě a má stejný důsledek jako metoda <code>extend</code>. Příkaz <code>z = z + y</code> vytváří nový seznam a sváže ho se znovu použitým jménem <code>z</code>, což je ale něco jiného, než u předchozího příkazu. Jde o jemný rozdíl vedoucí k delikátním a těžko zachytitelným chybám.</p> <p>Aby toho nebylo dost, vede to také k překvapivému chování, když se míchají měnitelné a neměnitelné kontejnery:</p> <pre> >>> t = ([],) >>> t[0] += [2, 3] Traceback (most recent call last): File "<input>", line 1, in ? TypeError: object doesn't support item assignment >>> t ([2, 3],) </pre> <p>N-tice, samozřejmě, nepodporují přiřazování svých prvků. Jenže po provedení <code>+=</code> se seznam uvnitř <b>změnil</b>! Důvod je opět v tom, že <code>+=</code> mění seznam na místě. Přiřazení prvku n-tice sice nefunguje, ale když se stane výjimka, tak prvek již byl na místě změněn.</p> <p>Tuto léčku já osobně považuji za vadu na kráse<sup class="trnote" id="B_tmod" title="Ale co je vlastně špatně?"><a href="#P_tmod">[5]</a></sup> :-).</p> <p><strong>Řešení</strong>: podle vašeho postoje k tomuto problému můžete buď se kompletně používání vyvarovat += nebo to používat jen pro čísla anebo se s tím naučit žít ...</p> <h3>4. Atributy tříd versus atributy instancí</h3> <p>Zde se chybuje nejméně ve dvou věcech. Tak za prvé, nováčci pravidelně přidávají atributy do třídy (místo do instance) a diví se, když jsou pak tyto atributy sdíleny mezi instancemi:</p> <pre> >>> class Foo: ... bar = [] ... def __init__(self, x): ... self.bar.append(x) ... >>> f = Foo(42) >>> g = Foo(100) >>> f.bar, g.bar ([42, 100], [42, 100]) </pre> <p>Nejde o vadu, ale šikovný rys, kterého můžeme v řadě situací využít. Nepochopení vyplývá z faktu, že byly použity atributy třídy a ne atributy instance. Může to být i tím, že se atributy instancí v Pythonu vytvářejí jinak, než v dalších jazycích. V jazycích C++, Object Pascal a dalších se deklarují ve těle třídy.</p> <p>Další (malá) léčka spočívá v tom, že <code>self.foo</code> může odkazovat na dvě věci: na atribut instance <code>foo</code> nebo — pokud tento neexistuje — na atribut třídy <code>foo</code>. Porovnejte:</p> <pre> >>> class Foo: ... a = 42 ... def __init__(self): ... self.a = 43 ... >>> f = Foo() >>> f.a 43 </pre> <p>a druhý případ</p> <pre> >>> class Foo: ... a = 42 ... >>> f = Foo() >>> f.a 42 </pre> <p>V prvním příkladě <code>f.a</code> odkazuje na atribut instance s hodnotou 43. Má přednost před atributem třídy s hodnotou 42. V druhém příkladě žádný atribut instance <code>a</code> neexistuje, takže <code>f.a</code> odkazuje na atribut třídy.</p> <p>Následující ukázka oba případy kombinuje:</p> <pre> >>> class Foo: ... ... bar = [] ... def __init__(self, x): ... self.bar = self.bar + [x] ... >>> f = Foo(42) >>> g = Foo(100) >>> f.bar [42] >>> g.bar [100] </pre> <p>V příkazu <code>self.bar = self.bar + [x]</code> neodpovídají zápisy obou <code>self.bar</code> stejnému odkazu... Druhý zápis odkazuje na atribut třídy <code>bar</code>. Výsledek je poté svázán s atributem instance.</p> <p><strong>Řešení</strong>: Tento rozdíl může být matoucí, ale není nepochopitelný. Atributy tříd používejte v situacích, když chcete něco sdílet mezi více instancemi třídy. Abyste se vyhnuli nejednoznačnosti, můžete se na ně odkazovat zápisem <code>self.__class__.<em>jmeno</em></code> místo zápisu <code>self.<em>jmeno</em></code> i v případech, kdy neexistuje žádný atribut instance tohoto jména. Pro atributy, které mohou v každé instanci nabývat jiné hodnoty, používejte atributy instancí a odkazujete se na ně přes self.<em>jmeno</em>.</p> <p><strong>Aktualizace:</strong> Vícero lidí poznamenává, že pasti číslo 3 a 4 se dají kombinovat do ještě zábavnějšího hlavolamu:</p> <pre> >>> class Foo: ... bar = [] ... def __init__(self, x): ... self.bar += [x] ... >>> f = Foo(42) >>> g = Foo(100) >>> f.bar [42, 100] >>> g.bar [42, 100] </pre> <p>Důvod pro toto chování je ten, že <code>self.bar += <em>něco</em></code> není stejné jako <code>self.bar = self.bar + <code>něco</code></code>. Zápis <code>self.bar</code> zde vyjadřuje odkaz na <code>Foo.bar</code>, takže <code>f</code> i <code>g</code> aktualizují stejný seznam.</p> <h3>5. Měnitelné implicitní argumenty</h3> <p>Tato past trápí začátečníky znovu a znovu. Ve skutečnosti to je varianta pasti číslo 2, kombinovaná s neočekávaným chováním implicitních argumentů. Uvažujme následující funkci:</p> <pre> >>> def popo(x=[]): ... x.append(666) ... print x ... >>> popo([1, 2, 3]) [1, 2, 3, 666] >>> x = [1, 2] >>> popo(x) [1, 2, 666] >>> x [1, 2, 666] </pre> <p>To se dalo čekat. Ale teď:</p> <pre> >>> popo() [666] >>> popo() [666, 666] >>> popo() [666, 666, 666] </pre> <p>Možná jste čekali, že výstup bude ve všech případech [666]... vždyť když voláme popo() bez argumentů, bere se přeci [] jako implicitní, že jo? Ne. Implicitní argument se volá *jednou* a to když se funkce *vytváří* a ne když se volá. (Jinými slovy, u funkce <code>f(x=[])</code> se x nepřiřazuje pokaždé, když se funkce volá. Do <code>x</code> se přiřadí [], jen když se funkce definuje<sup class="trnote" id="B_mda" title="... opakování je matka moudrosti"><a href="#P_mda">[6]</a></sup>. Pokud se jedná o měnitelný objekt, a ten se změnil, bude příští volání funkce za svůj implicitní argument považovat stejný seznam, který už ale má jiný obsah.</p> <p><strong>Řešení</strong>: Toto chování může být někdy užitečné. Ale obecně byste si na tyto vedlejší efekty měli dávat pozor.</p> <h3>6. !UnboundLocalError</h3> <p>Tato chyba se podle manuálu objeví v případě, kdy se jméno "odkazuje na lokální proměnnou, která ještě dosud nebyla navázána (bound)". To zní tajemně. Nejlepší to bude ukázat na malém příkladě:</p> <pre> >>> def p(): ... x = x + 2 ... >>> p() Traceback (most recent call last): File "<input>", line 1, in ? File "<input>", line 2, in p UnboundLocalError: local variable 'x' referenced before assignment </pre> <p>Uvnitř <code>p</code> nemůže být výraz <code>x = x + 2</code> proveden, protože <code>x</code> ve výrazu <code>x + 2</code> ještě nemá žádnou hodnotu. To zní rozumně. Nemůžete se odkazovat na jméno, které ještě neexistuje. Ale zvažme následující:</p> <pre> >>> x = 2 >>> def q(): ... print x ... x = 3 ... print x ... >>> q() Traceback (most recent call last): File "<input>", line 1, in ? File "<input>", line 2, in q UnboundLocalError: local variable 'x' referenced before assignment </pre> <p>Tento úsek kódu by se vám mohl zdát správný -- nejprve se vytiskne 2 (hodnota globální proměnné x), pak se lokální proměnné x přiřadí 3 a její hodnota se vytiskne (3). Takhle to však nefunguje. Je to dáno pravidly pro rozsah viditelnosti (platnosti, použitelnosti jmen). Jsou vysvětlena v referenční příručce:</p> <blockquote> <p>Pokud je vazba jména provedena uvnitř bloku, jde o lokální proměnnou tohoto bloku. Pokud je vazba jména<sup class="trnote" id="B_namebinding" title="Podrobnější vysvětlení..."><a href="#P_namebinding">[7]</a></sup> provedena na úrovni modulu, jde o globální proměnnou. (Proměnné bloku kódu modulu jsou lokální a globální.) Pokud je proměnná použita v kódu bloku, ale není zde definovaná, jedná se o volnou proměnnou.</p> <p>Pokud není jméno vůbec nalezeno, vyvolá se výjimka <code>NameError</code>. Pokud se jméno odkazuje na lokální proměnnou, pro kterou dosud nebyla provedena vazba, vyvolá se výjimka <code>UnboundLocalError</code>.</p> </blockquote> <p>Jinými slovy: Uvnitř funkce může proměnná být lokální nebo globální, ale ne obojetná. (Nezáleží na tom, jestli později provedete změnu vazby.) Ve výše uvedeném příkladu Python určí, že proměnná <code>x</code> je lokální (na základě zmíněných pravidel). Ale při následném provádění funkce se narazí na příkaz <code>print x</code> a <code>x</code> ještě nemá žádnou hodnotu... a tudíž je to chyba.</p> <p>Povšimněte si, že pokud by bylo tělo funkce složeno pouze z jednoho řádku <code>print x</code> nebo z řádků <code>x = 3; print x</code> bylo by to naprosto v pořádku.</p> <p><strong>Řešení:</strong> Používání lokálních a globálních proměnných tímto způsobem nemíchejte.</p> <h3>7. Chyby při zaokrouhlování desetinných čísel</h3> <p>Při tisku hodnot desetinných čísel (float) může být výsledek někdy překvapující. Aby byly věci ještě zajímavějšími, mohou se reprezentace vracené funkcemi str() a repr() lišit. Ukázka říká vše:</p> <pre> >>> c = 0.1 >>> c 0.10000000000000001 >>> repr(c) '0.10000000000000001' >>> str(c) '0.1' </pre> <p>V dvojkové soustavě (kterou používá procesor) nelze řadu čísel vyjádřit přesně. Skutečná hodnota se hodnotě zapsané v desítkové soustavě pouze blíží.</p> <p><strong>Řešení</strong>: Více informací se dozvíte z následujícího <a href="http://vik.sh.cvut.cz/~tnt/python/tut/node14.html">tutoriálu</a>. <h3>8. Spojování řetězců</h3> <p><strong>Poznámka překladatele:</strong> Odstraněno, již neplatí. Uvádělo se zde, že je méně výhodné několikanásobné slučování (sčítání) řetězců. Místo toho se radilo převézt řetězec na seznam, pak několikanásobné append u seznamu a zpětný převod na řetězec. Následující script ukazuje neplatnost tohoto pravidla u Python 2.5, pravděpodobně vlivem efektivnějšího provádění smyček při operacích s řetězci. <pre> import timeit def f(): s = "" for i in range(100000): s = s + "abcdefg"[i % 7] t=timeit.Timer("f()","from __main__ import f") print t.timeit(1) # vysledek python 2.5: 0.0705371772111 def g(): z = [] for i in range(100000): z.append("abcdefg"[i % 7]) return ''.join(z) t=timeit.Timer("g()","from __main__ import g") print t.timeit(1) # vysledek python 2.5: 0.0885903096623 </pre> <h3>9. Binární režim pro soubory</h3> <p>Nebo spíše, používání binárního režimu <b>není</b> tím, co způsobuje zmatek. Některé operační systémy, jako Windows, dělají rozdíl mezi binárními a textovými soubory. Pro ilustraci si uveďme, jak lze v jazyce Python otvírat soubory v binárním nebo textovém režimu:</p> <pre> f1 = file(jmenosouboru, "r") # text f2 = file(jmenosouboru, "rb") # binárně </pre> <p>V textovém režimu, mohou být řádky zakončované znakem "nová řádka" a/nebo "návrat vozíku" (<code>\n</code>, <code>\r</code>, nebo <code>\r\n</code>). Binární režim si na něco takového nehraje. Když ve Windows čteme ze soubor v textovém režimu, reprezentuje Python konce řádku znakem <code>\n</code> (universální). Ale v binárním režimu dostaneme <code>\r\n</code>. Při čtení dat proto můžeme v každém z těchto režimů získat velmi rozdílné výsledky.</p> <p>Existují systémy, které nerozlišují mezi textovým a binárním režimem. Například v Unixu jsou soubory otevírány vždy v binárním módu. Díky tomu může kód psaný pro Unix a otevírající soubor v režimu 'r' dávat jiné výsledky při spuštění pod Windows. Může se také stát, že někdo přicházející z Unixu může použít příznak 'r' i ve Windows a bude nemile překvapen výsledky.</p> <p><strong>Řešení</strong>: Používejte správné flagy — 'r' pro textový režim (i na Unixu), 'rb' na binární režim.</p> <h3>10. Zachytávání několika výjimek najednou</h3> <p>Někdy potřebujete v jednom <code>except</code> zachytit několik výjimek v jednom. Automaticky nás napadne nás, že by mohlo fungovat následující:</p> <pre> try: ...něco co vyvolá chybu... except IndexError, ValueError: # "mělo by" zachytit chyby IndexError a ValueError # špatně! </pre> <p>Tohle bohužel nefunguje. Důvody se stanou jasnějšími při porovnání s kódem:</p> <pre> >>> try: ... 1/0 ... except ZeroDivisionError, e: ... print e ... integer division or modulo by zero </pre> <p>První "argument" v klauzuli except uvádí třídu výjimky, druhý uvádí volitelné jméno, které bude navázáno na aktuální objekt vyvolané výjimky. Takže v předchozím chybném kódu by klauzule <code>except</code> zachytila <code>IndexError</code> a jméno <code>ValueError</code> by svázala s objektem výjimky. To asi není, co jsme chtěli. ;-)</p> <p>Tohle funguje lépe:</p> <pre> try: ...něco co vyvolá chybu... except (IndexError, ValueError): # správně zachytí IndexError a ValueError </pre> <p><strong>Řešení</strong>: Když odchytáváte několik výjimek v jedné klausuli <code>except</code>, používejte závorky na vytvoření n-tice s výjimkami.</p> <hr> <h3>Jaké další nástrahy tu jsou? Napadají mne snad:</h3> <ul> <li>zpětné lomítka v řetězcích (bez raw), obzvláště u cest Windows/DOS</li> <li>dělení celých čísel (v dalších verzích bude snad změněno)</li> </ul> <h3>Příbuzné odkazy:</h3> <ul> <li><a href="http://www.ferg.org/projects/python_gotchas.html" title="">Python Gotchas</a> od Steve Ferg</li> <li><a href="http://www.amk.ca/python/writing/warts.html" title="">Python Warts</a> od Andrew Kuchling</li> <li><a href="http://mechanicalcat.net/cgi-bin/log/2003/09/02#anti-pitfalls" title="">Python anti-pitfalls</a> od Richard Jones</li> </ul> <hr> <p>Poznámky překladatele:</p> <table summary="Poznámky překladatele."> <tr id="P_indent"><td><a href="#B_indent">[1]</a></td> <td><em>"... Tudy cesta nevede. Vyfukováním kouře do umyvadla s vodou zlato opravdu nevzniká."</em> – J.C.</td> </tr> <tr id="P_spaces"><td><a href="#B_spaces">[2]</a></td> <td>S mezerami nikdy problémy nebyly. S tabulátory ano. Někteří si myslí, že se tabulační pozice nemají nastavovat po 8 sloupcích.</td> </tr> <tr id="P_assign"><td><a href="#B_assign">[3]</a></td> <td>Takže v tuto chvíli existuje jeden seznam, jeden objekt, na který ukazují dvě jména.</td> </tr> <tr id="P_assign2"><td><a href="#B_assign2">[4]</a></td> <td>Přiřazením <code>a = 4</code> se zruší vazba na celočíselný objekt s hodnotou 3 a vznikne vazba na celočíselný objekt s hodnotou 4.</td> </tr> <tr id="P_tmod"><td><a href="#B_tmod">[5]</a></td> <td>N-tice je sice neměnná, ale udržuje pouze odkazy na jiné objekty. Odkazy se skutečně měnit nemohou. Zápis <code>t[0]</code> reprezentuje odkaz na seznam. Nikde se ale neříká, že by n-tice nemohla obsahovat odkazy na měnitelné objekty, které mohou být navíc navázány i na jiná jména a tudíž měněny odjinud. Chyba tedy nespočívá v tom, že se změnil obsah seznamu, ale v tom, že vůbec vznikla výjimka.</td> </tr> <tr id="P_mda"><td><a href="#B_mda">[6]</a></td> <td>... když Python při spuštění programu poprvé kód zpracovává a u funkcí si zapamatovává právě tyto implicitní argumenty (a také proměnné i další deklarace uvnitř funkcí). Vytváří se při tom vnitřní objekt, který reprezentuje zkompilovanou funkci. A o tom to je.</td> </tr> <tr id="P_namebinding"><td><a href="#B_namebinding">[7]</a></td> <td>Jak již bylo vysvětleno u <a href="#pitfall2">pasti číslo 2</a>, proměnnou se v Pythonu rozumí jméno, které se odkazuje na objekt. Přiřazením hodnoty proměnné se provede pouze svázání jména proměnné s uvedeným objektem tak, že se ve vnitřním slovníku vytvoří dvojice <em>(jméno, odkaz na objekt)</em>. Této akci se říká provedení vazby jména. Pokud ve vnitřním slovníku neexistuje položka s klíčem odpovídajícím jménu, pak vazba nebyla provedena. Zmíněných vnitřních slovníků, které Python využívá, je více. V jednom z nich jsou zachyceny vazby jmen globálních proměnných. Pro každou lokální úroveň je vytvořen příslušný (jiný, oddělený, další) vnitřní slovník.</td> </tr> </table>
(C) 2003 Hans
Nowak. Written: 2003.08.13. Last update: 2003.09.05.
Díky Blake Winton, Joe Grossberg, Steve Ferg, Lloyd Kvam za
hodnotné připomínky.
(C) 2005 Překlad Pavel Kosina
a Petr Přikryl. Přeloženo: srpen 2005
anglický originál leží
http://zephyrfalcon.org/labs/python_pitfalls.html
Nejde nutně o vady na kráse či nedodělky. Jsou to spíše vedlejší účinky vlastností jazyka, o které často zakopávají nováčci a někdy i zkušení programátoři. Při neúplném pochopení podstatných rysů chování jazyka Python se můžete spálit.
Tento dokument má být jakýmsi průvodcem pro ty, pro které je jazyk Python něčím novým. Dozvědět o pastech a léčkách brzy je lepší, než narazit na ně ve vytvářeném kódu těsně před termínem odevzdání :-} Tento dokument není míněn jako kritika jazyka. Jak jsem již řekl, většina těchto pastí není způsobena vadami jazyka.
Možná trochu laciné téma na úvod. Nicméně, mnozí nováčci mají zkušenost s jazyky, kde mezery "nehrají roli". Cestou slepých uliček[1] mohou dojít až k nemilému překvapení, že je Python trestá za zlozvyk nepořádného odsazování.
Řešení: Dodržujte správné odsazování. Používejte buď mezery, nebo tabulátory[2], ale nikdy to nemíchejte. Kvalitní editor pomáhá.
Lidé přicházející od staticky typovaných jazyků, jako je Pascal nebo C, často předpokládají, že Python zachází s proměnnými a s přiřazováním stejně, jako jejich oblíbený jazyk. Na první pohled to tak skutečně vypadá:
a = b = 3 a = 4 print a, b # 4, 3
Dostávají se však do potíží, když začnou používat měnitelné objekty. Často pak hned přicházejí s tvrzením, že Python zachází s měnitelnými a neměnitelnými objekty rozdílně.
a = [1, 2, 3] b = a a.append(4) print b # b je nyní také [1, 2, 3, 4]
Stalo se to, že výraz a = [1, 2, 3]
provedl dvě
věci: 1. vytvořil objekt, v tomto případě seznam s hodnou [1, 2,
3]; 2. svázal ho se jménem a
v lokálním prostoru
jmen. Výraz b = a
pak svázal jméno
b
s tím samým seznamem (na který již odkazuje
a
)[3].
Jakmile si toto uvědomíte, bude již méně obtížné pochopit, co
vlastně a.append(4)
dělá... že mění seznam, na který se
odkazuje jak a
tak b
.
Domněnka, že se s měnitelnými a neměnitelnými objekty při
přiřazování zachází rozdílně, je mylná. Při přiřazování a =
3
a b = a
se děje přesně stejná věc, jako u výše
uvedeného seznamu. Jména a
i b
nyní
odkazují na stejný objekt — na číslo s hodnotou 3. Protože
však čísla jsou neměnitelná (immutable), nepozorujete žádný vedlejší
efekt [4].
Řešení: Přečtěte si toto. Abyste se zbavili nechtěných vedlejších efektů, kopírujte (používejte metodu copy, operátory řezu (slice), atd). Python nikdy implicitně nekopíruje.
V jazycích jako třeba C, jsou rozšířené přiřazovací operátory jako
+=
zkratkami pro delší výrazy. Například,
x += 42;
je syntaktická pomůcka (angličani říkají syntactic sugar) pro
x = x + 42;
Takže byste si mohli myslet, že tomu tak bude i v jazyce Python. Na první pohled to tak skutečně vypadá:
a = 1 a = a + 42 # a je 43 a = 1 a += 42 # a je 43
Ale u měnitelných objektů (mutable) zápis x += y
nemusí nutně vyjadřovat totéž, jako zápis
x = x + y
. Uvažujme seznamy:
>>> z = [1, 2, 3] >>> id(z) 24213240 >>> z += [4] >>> id(z) 24213240 >>> z = z + [5] >>> id(z) 24226184
Příkaz x += y
mění seznam na místě a má stejný
důsledek jako metoda extend
. Příkaz
z = z + y
vytváří nový seznam a
sváže ho se znovu použitým jménem z
, což je ale něco
jiného, než u předchozího příkazu. Jde o jemný rozdíl vedoucí
k delikátním a těžko zachytitelným chybám.
Aby toho nebylo dost, vede to také k překvapivému chování, když se míchají měnitelné a neměnitelné kontejnery:
>>> t = ([],) >>> t[0] += [2, 3] Traceback (most recent call last): File "<input>", line 1, in ? TypeError: object doesn't support item assignment >>> t ([2, 3],)
N-tice, samozřejmě, nepodporují přiřazování svých prvků. Jenže po
provedení +=
se seznam uvnitř změnil! Důvod je
opět v tom, že +=
mění seznam na místě. Přiřazení prvku
n-tice sice nefunguje, ale když se stane výjimka, tak prvek již byl
na místě změněn.
Tuto léčku já osobně považuji za vadu na kráse[5]? :-).
Řešení: podle vašeho postoje k tomuto problému můžete buď se kompletně používání vyvarovat += nebo to používat jen pro čísla anebo se s tím naučit žít ...
Zde se chybuje nejméně ve dvou věcech. Tak za prvé, nováčci pravidelně přidávají atributy do třídy (místo do instance) a diví se, když jsou pak tyto atributy sdíleny mezi instancemi:
>>> class Foo: ... bar = [] ... def __init__(self, x): ... self.bar.append(x) ... >>> f = Foo(42) >>> g = Foo(100) >>> f.bar, g.bar ([42, 100], [42, 100])
Nejde o vadu, ale šikovný rys, kterého můžeme v řadě situací využít. Nepochopení vyplývá z faktu, že byly použity atributy třídy a ne atributy instance. Může to být i tím, že se atributy instancí v Pythonu vytvářejí jinak, než v dalších jazycích. V jazycích C++, Object Pascal a dalších se deklarují ve těle třídy.
Další (malá) léčka spočívá v tom, že self.foo
může
odkazovat na dvě věci: na atribut instance foo
nebo
— pokud tento neexistuje — na atribut třídy
foo
. Porovnejte:
>>> class Foo: ... a = 42 ... def __init__(self): ... self.a = 43 ... >>> f = Foo() >>> f.a 43
a druhý případ
>>> class Foo: ... a = 42 ... >>> f = Foo() >>> f.a 42
V prvním příkladě f.a
odkazuje na atribut instance s
hodnotou 43. Má přednost před atributem třídy s hodnotou 42. V
druhém příkladě žádný atribut instance a
neexistuje,
takže f.a
odkazuje na atribut třídy.
Následující ukázka oba případy kombinuje:
>>> class Foo: ... ... bar = [] ... def __init__(self, x): ... self.bar = self.bar + [x] ... >>> f = Foo(42) >>> g = Foo(100) >>> f.bar [42] >>> g.bar [100]
V příkazu self.bar = self.bar + [x]
neodpovídají
zápisy obou self.bar
stejnému odkazu... Druhý zápis
odkazuje na atribut třídy bar
. Výsledek je poté svázán
s atributem instance.
Řešení: Tento rozdíl může být matoucí, ale není
nepochopitelný. Atributy tříd používejte v situacích, když chcete
něco sdílet mezi více instancemi třídy. Abyste se vyhnuli
nejednoznačnosti, můžete se na ně odkazovat zápisem
self.__class__.jmeno
místo zápisu
self.jmeno
i v případech, kdy neexistuje žádný
atribut instance tohoto jména. Pro atributy, které mohou v každé
instanci nabývat jiné hodnoty, používejte atributy instancí a
odkazujete se na ně přes self.jmeno.
Aktualizace: Vícero lidí poznamenává, že pasti číslo 3 a 4 se dají kombinovat do ještě zábavnějšího hlavolamu:
>>> class Foo: ... bar = [] ... def __init__(self, x): ... self.bar += [x] ... >>> f = Foo(42) >>> g = Foo(100) >>> f.bar [42, 100] >>> g.bar [42, 100]
Důvod pro toto chování je ten, že
self.bar += něco
není stejné jako
self.bar = self.bar +
.
Zápis něco
self.bar
zde vyjadřuje odkaz na
Foo.bar
, takže f
i g
aktualizují stejný seznam.
Tato past trápí začátečníky znovu a znovu. Ve skutečnosti to je varianta pasti číslo 2, kombinovaná s neočekávaným chováním implicitních argumentů. Uvažujme následující funkci:
>>> def popo(x=[]): ... x.append(666) ... print x ... >>> popo([1, 2, 3]) [1, 2, 3, 666] >>> x = [1, 2] >>> popo(x) [1, 2, 666] >>> x [1, 2, 666]
To se dalo čekat. Ale teď:
>>> popo() [666] >>> popo() [666, 666] >>> popo() [666, 666, 666]
Možná jste čekali, že výstup bude ve všech případech [666]?...
vždyť když voláme popo() bez argumentů, bere se přeci [] jako
implicitní, že jo? Ne. Implicitní argument se volá *jednou* a to
když se funkce *vytváří* a ne když se volá. (Jinými slovy, u funkce
f(x=[])
se x nepřiřazuje pokaždé, když se funkce volá.
Do x
se přiřadí [], jen když se funkce definuje[6]. Pokud se jedná o
měnitelný objekt, a ten se změnil, bude příští volání funkce za svůj
implicitní argument považovat stejný seznam, který už ale má jiný
obsah.
Řešení: Toto chování může být někdy užitečné. Ale obecně byste si na tyto vedlejší efekty měli dávat pozor.
Tato chyba se podle manuálu objeví v případě, kdy se jméno "odkazuje na lokální proměnnou, která ještě dosud nebyla navázána (bound)". To zní tajemně. Nejlepší to bude ukázat na malém příkladě:
>>> def p(): ... x = x + 2 ... >>> p() Traceback (most recent call last): File "<input>", line 1, in ? File "<input>", line 2, in p UnboundLocalError: local variable 'x' referenced before assignment
Uvnitř p
nemůže být výraz x = x + 2
proveden, protože x
ve výrazu x + 2
ještě nemá žádnou hodnotu. To zní rozumně. Nemůžete se odkazovat
na jméno, které ještě neexistuje. Ale zvažme následující:
>>> x = 2 >>> def q(): ... print x ... x = 3 ... print x ... >>> q() Traceback (most recent call last): File "<input>", line 1, in ? File "<input>", line 2, in q UnboundLocalError: local variable 'x' referenced before assignment
Tento úsek kódu by se vám mohl zdát správný -- nejprve se vytiskne 2 (hodnota globální proměnné x), pak se lokální proměnné x přiřadí 3 a její hodnota se vytiskne (3). Takhle to však nefunguje. Je to dáno pravidly pro rozsah viditelnosti (platnosti, použitelnosti jmen). Jsou vysvětlena v referenční příručce:
Pokud je vazba jména provedena uvnitř bloku, jde o lokální proměnnou tohoto bloku. Pokud je vazba jména[7] provedena na úrovni modulu, jde o globální proměnnou. (Proměnné bloku kódu modulu jsou lokální a globální.) Pokud je proměnná použita v kódu bloku, ale není zde definovaná, jedná se o volnou proměnnou.
Pokud není jméno vůbec nalezeno, vyvolá se výjimka
NameError
. Pokud se jméno odkazuje na lokální proměnnou, pro kterou dosud nebyla provedena vazba, vyvolá se výjimkaUnboundLocalError
.
Jinými slovy: Uvnitř funkce může proměnná být lokální nebo
globální, ale ne obojetná. (Nezáleží na tom, jestli později
provedete změnu vazby.) Ve výše uvedeném příkladu Python určí, že
proměnná x
je lokální (na základě zmíněných pravidel).
Ale při následném provádění funkce se narazí na příkaz
print x
a x
ještě nemá žádnou
hodnotu... a tudíž je to chyba.
Povšimněte si, že pokud by bylo tělo funkce složeno pouze z
jednoho řádku print x
nebo z řádků x = 3; print
x
bylo by to naprosto v pořádku.
Řešení: Používání lokálních a globálních proměnných tímto způsobem nemíchejte.
Při tisku hodnot desetinných čísel (float) může být výsledek někdy překvapující. Aby byly věci ještě zajímavějšími, mohou se reprezentace vracené funkcemi str() a repr() lišit. Ukázka říká vše:
>>> c = 0.1 >>> c 0.10000000000000001 >>> repr(c) '0.10000000000000001' >>> str(c) '0.1'
V dvojkové soustavě (kterou používá procesor) nelze řadu čísel vyjádřit přesně. Skutečná hodnota se hodnotě zapsané v desítkové soustavě pouze blíží.
Řešení: Více informací se dozvíte z následujícího tutoriálu.
Poznámka překladatele: Odstraněno, již neplatí. Uvádělo se zde, že je méně výhodné několikanásobné slučování (sčítání) řetězců. Místo toho se radilo převézt řetězec na seznam, pak několikanásobné append u seznamu a zpětný převod na řetězec. Následující script ukazuje neplatnost tohoto pravidla u Python 2.5, pravděpodobně vlivem efektivnějšího provádění smyček při operacích s řetězci.
import timeit def f(): s = "" for i in range(100000): s = s + "abcdefg"[i % 7] t=timeit.Timer("f()","from __main__ import f") print t.timeit(1) # vysledek python 2.5: 0.0705371772111 def g(): z = [] for i in range(100000): z.append("abcdefg"[i % 7]) return ''.join(z) t=timeit.Timer("g()","from __main__ import g") print t.timeit(1) # vysledek python 2.5: 0.0885903096623
Nebo spíše, používání binárního režimu není tím, co způsobuje zmatek. Některé operační systémy, jako Windows, dělají rozdíl mezi binárními a textovými soubory. Pro ilustraci si uveďme, jak lze v jazyce Python otvírat soubory v binárním nebo textovém režimu:
f1 = file(jmenosouboru, "r") # text f2 = file(jmenosouboru, "rb") # binárně
V textovém režimu, mohou být řádky zakončované znakem "nová
řádka" a/nebo "návrat vozíku" (\n
, \r
,
nebo \r\n
). Binární režim si na něco takového nehraje.
Když ve
Windows čteme ze soubor v textovém režimu, reprezentuje Python konce
řádku znakem \n
(universální). Ale v binárním režimu
dostaneme \r\n
. Při čtení dat proto můžeme v každém z
těchto režimů získat velmi rozdílné výsledky.
Existují systémy, které nerozlišují mezi textovým a binárním režimem. Například v Unixu jsou soubory otevírány vždy v binárním módu. Díky tomu může kód psaný pro Unix a otevírající soubor v režimu 'r' dávat jiné výsledky při spuštění pod Windows. Může se také stát, že někdo přicházející z Unixu může použít příznak 'r' i ve Windows a bude nemile překvapen výsledky.
Řešení: Používejte správné flagy — 'r' pro textový režim (i na Unixu), 'rb' na binární režim.
Někdy potřebujete v jednom except
zachytit několik
výjimek v jednom. Automaticky nás napadne nás, že by mohlo fungovat
následující:
try: ...něco co vyvolá chybu... except IndexError, ValueError: # "mělo by" zachytit chyby IndexError a ValueError # špatně!
Tohle bohužel nefunguje. Důvody se stanou jasnějšími při porovnání s kódem:
>>> try: ... 1/0 ... except ZeroDivisionError, e: ... print e ... integer division or modulo by zero
První "argument" v klauzuli except uvádí třídu výjimky, druhý
uvádí volitelné jméno, které bude navázáno na aktuální objekt
vyvolané výjimky. Takže v předchozím chybném kódu by klauzule
except
zachytila IndexError
a jméno
ValueError
by svázala s objektem výjimky. To asi není,
co jsme chtěli. ;-)
Tohle funguje lépe:
try: ...něco co vyvolá chybu... except (IndexError, ValueError): # správně zachytí IndexError a ValueError
Řešení: Když odchytáváte několik výjimek v jedné
klausuli except
, používejte závorky na vytvoření n-tice
s výjimkami.
Poznámky překladatele:
[1] | "... Tudy cesta nevede. Vyfukováním kouře do umyvadla s vodou zlato opravdu nevzniká." – J.C. |
[2] | S mezerami nikdy problémy nebyly. S tabulátory ano. Někteří si myslí, že se tabulační pozice nemají nastavovat po 8 sloupcích. |
[3] | Takže v tuto chvíli existuje jeden seznam, jeden objekt, na který ukazují dvě jména. |
[4] | Přiřazením a = 4 se zruší vazba na celočíselný
objekt s hodnotou 3 a vznikne vazba na celočíselný objekt s
hodnotou 4. |
[5] | N-tice je sice neměnná, ale udržuje pouze odkazy na jiné objekty.
Odkazy se skutečně měnit nemohou. Zápis t[0] reprezentuje
odkaz na seznam. Nikde se ale neříká, že by n-tice nemohla obsahovat
odkazy na měnitelné objekty, které mohou být navíc navázány i na jiná
jména a tudíž měněny odjinud. Chyba tedy nespočívá v tom, že se změnil
obsah seznamu, ale v tom, že vůbec vznikla výjimka. |
[6] | ... když Python při spuštění programu poprvé kód zpracovává a u funkcí si zapamatovává právě tyto implicitní argumenty (a také proměnné i další deklarace uvnitř funkcí). Vytváří se při tom vnitřní objekt, který reprezentuje zkompilovanou funkci. A o tom to je. |
[7] | Jak již bylo vysvětleno u pasti číslo 2, proměnnou se v Pythonu rozumí jméno, které se odkazuje na objekt. Přiřazením hodnoty proměnné se provede pouze svázání jména proměnné s uvedeným objektem tak, že se ve vnitřním slovníku vytvoří dvojice (jméno, odkaz na objekt). Této akci se říká provedení vazby jména. Pokud ve vnitřním slovníku neexistuje položka s klíčem odpovídajícím jménu, pak vazba nebyla provedena. Zmíněných vnitřních slovníků, které Python využívá, je více. V jednom z nich jsou zachyceny vazby jmen globálních proměnných. Pro každou lokální úroveň je vytvořen příslušný (jiný, oddělený, další) vnitřní slovník. |