Hlavní navigace

Létající cirkus (18)

25. 10. 2002
Doba čtení: 8 minut

Sdílet

Týden se s týdnem sešel a my jsme se opět setkali u našeho občasníku věnovaného jazyku Python. Jak jsem již na konci minulého dílu předeslal, dnes se budeme věnovat především utilitkám, které nám při práci s Pythonem vydatně pomáhají. Jde o debugger, profiler a shell jazyka Python - ipython.

DEBUGGER

Každý program má chyby. To je jedno ze základních pravidel programování. Proto nikoho nepřekvapí, že existuje mnoho postupů, jak se chyb vyvarovat. Může jít o používání průhledných postupů a konstrukcí, používání jednoduchých funkcí apod. Pokud jsme však již zjistili, že náš kód obsahuje chybu, můžeme použít několik metod, jak ji odhalit.

Tou nejzákladnější a velmi oblíbenou metodou je používání příkazu assert. Ten má jeden povinný a jeden nepovinný argument. První je podmínka, pokud její logická hodnota nabývá log. 0 (tj. tato podmínka není splněna), dojde k výjimce AssertionError, pokud byl příkazu předán ještě druhý argument, bude tento použit jako parametr výjimky. Připomeňme, že příkaz assert pracuje pouze, pokud je hodnota interní proměnné __debug__ rovna 1 (což platí vždy kromě případu, kdy je interpretr spuštěn s volbou -O).

Další postup je používání kontrolních výpisů. Kontrolní výpisy jsou jednoduchou možností, jak se přesvědčit, že program pracuje, jak má. Další v Pythonu oblíbený postup spočívá v používání interaktivního interpretru, zde jednak můžeme provést jednotlivé části kódu a okamžitě sledovat, co se děje, jednak můžeme provést i tzv. suchý běh, kdy si sami spouštíme řádek za řádkem a sledujeme stav proměnných.

Pokud výše uvedené možnosti selžou, přijde na řadu debugger, ladící program, v němž můžeme kontrolovat provádění jednotlivých částí našeho kódu. Přestože debugger je velice silný nástroj, jeho přílišné používání v místech, kde nám mohou pomoci kontrolní výpisy, příliš nedoporučuji.

Velkou výhodou Pythonu je, že debugger je vlastně program kompletně napsaný v Pythonu samotném, nejde o žádnou službu jádra interpretru apod. Debugger podporuje nastavování zarážek (breakpointů), které mohou být dokonce podmíněné, krokování kódu na úrovni zdrojového kódu, výpis zdrojového kódu, prohlížení zásobníku volaných funkcí a vyhodnocování libovolných příkazů jazyka Python v kontextu vybraného rámce ze zásobníku volaných funkcí.

Veškerý kód debuggeru je součástí modulu pdb. Debugger se (což nikoho asi nepřekvapí) ovládá pomocí řádkového rozhraní, jež je implementováno za použití modulu cmd. Tento modul nabízí několik funkcí, kterými se debugger spouští. Jde především o tyto:

  • run() – této funkci předáme jeden argument – řetězec – reprezentující příkaz, který se má ladit, je možné jí předat i další dva argumenty – asociativní pole, které jsou použity jako globální (resp. lokální) prostor jmen.
  • set_trace() – funkce bez parametrů, která vyvolá debugger v místě svého zavolání.
  • pm() – další funkce bez parametrů, spustí post-mortem debugger, který umožní zjištění příčin, za nichž vznikla výjimka.

Další možností, kterak spustit debugger nad určitým programem, je přímé spuštění modulu pdb, kterému jako argument předáme jméno laděného programu:

% python /usr/local/lib/python2.2/pdb.py muj_program.py

Po spuštění debuggeru můžeme provádět kontrolovaný běh programu. Zde si uvedeme pouze některé příkazy, jejich kompletní popis najdete v dokumentaci jazyka. První množinou příkazů jsou příkazy, které spouští kód laděného příkazu. Jde o příkaz step, který vykonává jedinou činnost – v případě, že program volá nějakou funkci, debugger začne tuto funkci trasovat. Podobným příkazem je next. Ten však funkce spouští jako jediný příkaz a neprovádí jejich trasování. Příkaz continue spustí program od aktuálního místa, program poběží, dokud nebude zastaven nějakou zarážkou. Obdobně příkaz return, ten vykoná zbytek aktuální funkce, zastaví se buď na konci funkce, nebo na příkazu return, který způsobil ukončení funkce.

Další sada příkazů se týká nastavování zarážek. Nejdůležitějším je příkaz break, jemuž můžeme předat buď jméno souboru a číslo řádku, na němž má zarážku nastavit, nebo jméno funkce, pak bude zarážka aktivována na prvním příkazu této funkce. Dále je možné uvést ještě podmínku, při které bude zarážka aktivní. Při nastavení zarážky příkaz vytiskne číslo breakpointu, které bude možné použít v dalších příkazech. Jedním z nich je příkaz clear, jenž odstraní zarážku danou číslem.

Velice užitečné jsou příkazy pro práci se zásobníkem volaných funkcí. Jedná se o trojici příkazů: where, up a down. Jejich význam je zřejmý z názvu, takže jen ve zkratce: where vytiskne zásobník volaných funkcí, přičemž funkce na jeho vrcholu je vytištěna jako poslední, aktuální rámec pak reprezentuje šipka. Up skočí na rámec funkce, která zavolala funkci v aktuálním rámci (pohybujeme se zásobníkem směrem vzhůru), down naopak posune ukazatel o jednu funkci hlouběji (pohyb dolů).

Následují příkazy bez rozdělení do skupin: příkaz list vytiskne zdrojový kód v okolí aktuálního řádku. Je možné mu předat i rozsah řádek, odkud kam má kód vytisknout. Příkaz args vytiskne seznam argumentů aktuální funkce. Velice užitečný je i příkaz help, jenž vytiskne nápovědu ke každému příkazu debuggeru. Pro ukončení debuggeru slouží příkaz quit.

Všechny příkazy můžeme zkrátit (např. step = s, next = n …), pokud příkaz předaný debuggeru není jeho vlastním příkazem, je považován za příkaz jazyka Python a debugger ho takto spustí. Pokud bychom se chtěli vyhnout nejednoznačnostem, můžeme použít příkaz !. Všechny znaky zapsané za ním bere debugger jako pythonovské příkazy.

PROFILER
I když jste již svůj kód zbavili většiny chyb, může na vás číhat další nepříjemnost. Tou je pomalý běh programu. Program v jazyce Python je interpretovaný, což pro něj zcela jednoznačně znamená pomalejší vykonávání než u jazyků kompilovaných. Přesto má programátor možnost, jak běh zrychlit. Může totiž kritické části kódu přeorganizovat nebo v případě nejhorším přepsat do C, čímž získá výhody (i nevýhody) kompilovaného kódu.

A právě pro zjištění těchto kritických míst nabízí Python profiler, čili program, který přesně určí, jakou funkci program vykonával a jak dlouho, kolikrát ji během své činnosti zavolal, a další, pro programátora velice užitečné informace. Mnohdy se programátor nestačí divit, předpokládal, že program tráví nejvíce času uvnitř nějaké dlouhé smyčky, náhle ale zjistí, že jeho kód nejvíce brzdí špatně napsaná funkce volaná z tohoto cyklu.

Profiler jazyka Python je deterministický, což znamená, že po každém příkazu spočítá dobu, jak dlouho byl vykonáván, a zaznamená i další užitečné informace. Naproti tomu existují i profilery statistické. Ty v pravidelných intervalech vzorkují ukazatel aktuální instrukce a podle těchto vzorků určují, kde program strávil nejvíce času.

Profiler jazyka Python umožňuje profilování pouze kódu Pythonu, jakékoli externí moduly v C jsou pro něj skryty, čas v nich strávený počítá jako čas funkce, která funkce těchto externích modulů volala. Pokud však kód v C volá funkci Pythonu, je již tato klasicky profilována. (Pozn.: Obdobná pravidla platí pro debugger pdb.)

Všechna funkcionalita profileru je založena na modulu profile. Spolu s tímto modulem zmíníme i modul pstats, který slouží k interpretaci výsledků profileru. Veškeré možnosti profileru nám zpřístupňuje funkce profile.run(). Její první (povinný) argument je řetězec – příkaz, který má být profilován. Dále je možné uvést i další řetězec. Ten bude použit jako jméno souboru, do nějž budou uloženy statistiky, které vyprodukoval profiler. K interpretaci těchto statistik je určena třída pstats.Stats. Pokud funkci run() nepředáme jméno souboru, vytiskne profilovací zprávu, obdobně můžeme modul profile spustit i jako hlavní modul, pak také vytiskne zprávu, která může vypadat podobně:

main()
2706 function calls (2004 primitive calls) in 4.504 CPU seconds

Ordered by: standard name

ncalls tottime percall cumtime percall filename:lineno(function)
  2   0.006   0.003   0.953   0.477 pobject.py:75(save_objects)
43/3   0.533   0.012   0.749   0.250 pobject.py:99(evaluate)
 ...

Jak vidíte, zpráva obsahuje několik sloupců. Jejich význam si nyní postupně objasníme.

  • ncalls – počet volání funkce, je-li ve tvaru 43/3, pak druhé číslo udává počet primitivních volání funkce (to jsou všechna volání kromě rekurzívních, tj. případů, kdy funkce volá sama sebe)
  • tottime – čistý čas strávený ve všech voláních funkce, neobsahuje čas strávený ve funkcích jí volaných
  • precall – v obou případech jde o přepočet na jedno volání funkce
  • cumtime – čas strávený ve všech voláních funkce a všech její „podfunkcích“
  • filename – identifikuje funkci, jde o jméno souboru a číslo řádku, jméno funkce následuje v závorce

Jestliže jste funkci run() předali jméno souboru, je profilovací zpráva uložena do tohoto souboru. Jelikož „doba života“ profilovacích protokolů je velice krátká, není zaručena kompatibilita formátu tohoto souboru mezi jednotlivými verzemi jazyka.

Pro interpretaci profilovacích protokolů je zde modul pstats. Jeho třída Stats, jejímuž konstruktoru předáte jméno souboru s protokolem, podporuje mnoho metod, díky nimž můžete manipulovat s výsledky profilování a získat tím přesně to, co chcete. Zde si uvedeme pouze nejzákladnější metody:

  • sort_stats() – seřadí zprávu podle zadaného klíče (‚calls‘, ‚cumulative‘, ‚file‘, ‚module‘, ‚pcalls‘, ‚line‘, ‚name‘, ‚nfl‘, ‚stdname‘, ‚time‘)
  • reverse_order() – zrevertuje pořadí prvků, první bude poslední atd.
  • print_stats() – vytiskne profilovací zprávu, je možné jí předat argumenty, pomocí nichž lze vyfiltrovat pouze ty funkce, jež nás zajímají
  • print_callers() – vytiskne seznam funkcí, které volaly každou funkci v profilovacím protokolu
  • print_callees() – vytiskne seznam funkcí, které byly volány z každé funkce v protokolu

IPYTHON
Pokud jste někdy používali interaktivní mód Pythonu, jistě víte, že jde o velice silný nástroj. Když si k tomu ještě přičtete přístup k debuggeru, dokumentačním řetězcům, shellovým příkazům a mnoho dalšího, dostanete IPython.

root_podpora

Jde o velice zdařilý program a vřele ho doporučuji všem programátorům a uživatelům jazyka Python. Kromě výše uvedených vlastností zmiňme ještě další:

  • Přidávání vlastních příkazů, které lze používat z prostředí IPythonu.
  • Barevné a číselné rozlišení vstupu/výstupu Pythonového shellu.
  • Vytvářené logů, do nichž se ukládají spouštěné příkazy, posléze je možné tyto logy „přehrát“, čímž získáme (nebo nezískáme :) stav předchozího sezení.
  • Je možné ho volat z libovolného uživatelského kódu, čímž získáme další výborný ladicí nástroj.
  • Automatické doplňování závorek. Např. funkci „sin(3)“ lze volat i jako „sin 3“.

PŘÍŠTĚ
V příštím dílu našeho seriálu se podíváme na další významný balík modulů – PIL (Python Imaging Library). Předešlu i obsah dalších dílů seriálu. Čeká nás ještě probrání modulů NumPy, distribuce modulů pomocí distutils a základů rozhraní Python/C API.

Byl pro vás článek přínosný?