Podíváme se na to, jak řešení zdánlivě jednoduchého úkolu může vést na chybu v programu.
Původní kód
K sepsání tohoto příspěvku mě inspirovala tato nekorektní odpověď na stackoverflow.com. Mějme za úkol napsat funkci v Pythonu, která zjistí, zda je předaný soubor neprázdný. Rychlým vyhledáním dojdeme k tomu, že v Pythonu se ke zjištění velikosti souboru dá použít os.path.getsize()
. První pokus tedy může vypadat takto:
# Returns True if the given file is non-empty, False otherwise. def is_non_empty_file(path): return os.path.getsize(path) > 0
Pokud však daný soubor neexistuje, tak os.path.getsize()
vyhodí výjimku FileNotFoundError
. Autor oné odpovědi však chtěl, aby jeho funkce nevyhodila výjimku. Vyřešil to následovně:
# Returns True if the given file exists and is non-empty, False otherwise. def is_non_empty_file(path): return os.path.isfile(path) and os.path.getsize(path) > 0
Nejdříve tedy zkontroluje, zda soubor existuje a teprve potom si zjistí jeho velikost.
Proč takto ne?
Představte si následující situaci:
- Uživatel zavolá
is_non_empty_file(path)
na existujícím souboru. - Zavolá se
os.path.isfile(path)
, která vrátíTrue
. - Někdo shodou okolností nyní onen soubor z disku smaže (např. jiný program).
- Zavolá se
os.path.getsize(path)
. Jelikož však daný soubor již neexistuje, dojde k vyhození výjimkyFileNotFoundError
. V popisu funkceis_non_empty_file()
však nic o vyhození výjimky není, na což se volající kód může spoléhat. A hle, chyba je na světě.
Jak to udělat lépe?
V podobných případech, kdy hrozí riziko souběhu (angl. race condition), je potřeba použít funkci, která neprázdnost souboru zjistí atomicky. K tomu lze využít např. os.stat()
:
import os import stat # Returns True if the given regular file exists, is accessible, and non-empty. # Otherwise, it returns False. def is_non_empty_file(path): try: s = os.stat(path) return stat.S_ISREG(s.st_mode) and s.st_size > 0 except OSError: return False
Po zavolání os.stat()
se na základě výsledku provede kontrola, zda se jedná o regulérní soubor (a tedy nikoliv např. o adresář nebo speciální zařízení) a zda je soubor neprázdný. Výjimku OSError
je potřeba odchytávat z toho důvodu, že daný soubor nemusí existovat či nemusí být přístupný. V prvním případě by došlo k vyhození FileNotFoundError
, ve druhém pak k vyhození PermissionError
. Obě tyto výjimky dědí z OSError
, takže nám stačí odchytávat ten. Související možná výhoda je, že OSError
nám pokrývá i chyby typu "nepodařilo se načíst data z disku". Pokud bychom na chyby při čtení z disku chtěli reagovat jinak, tak by bylo potřeba funkci upravit.
rly?
is_non_empty_file
Re: rly?
Ahoj, díky moc za postřeh, toto jsem si neuvědomil :). Funkce
os.path.getsize()
se nedívá na to, o jaký typ cesty se jedná (zda je to adresář, regulérní soubor, či speciální zařízení). K detekci pouze regulérních souborů je tedy potřeba použít jiný způsob. Příspěvek jsem upravil.Pro úplnost, takto vypadala funkce původně (před úpravou):