Jste zde

Zneužívání funkce isinstance() v Pythonu

Programátoři v Pythonu, kteří k tomuto jazyku přešli z některého staticky typovaného jazyka (C++, Java, ...), mívají problém s tím, že se snaží používat koncepty, které mají zažité z jiných jazyků, ale které v Pythonu nemají co dělat. Jeden z nich se týká zneužívání funkce isinstance() a cílem tohoto příspěvku je na něj poukázat a vysvětlit, v čem je problém.

Úvod

Na úvod je třeba si uvědomit dvě věci:

  • Zjednodušeně řečeno, funkce isinstance() zjišťuje, zda je předaný objekt instancí předané třídy (ať u přímo, nebo je instancí nějaké podtřídy předané třídy). Ve skutečnosti toho umí o něco víc, ale nás se bude týkat toto.
  • Python je tzv. "duck-typed" (bez překladu). Toto znamená, že sémantika objektu je určena tím, jaké metody (atributy) implementuje, a nikoliv tím, jaké je tento objekt třídy (C++) či jaké rozhraní implementuje (Java).

V čem je tedy hlavní problém?

Co programátora normálně zajímá, je typ objektu, se kterým pracuje. Problém nastává, pokud je programátor zvyklý na to, že typ = třída. Toto je sice pravda v jazycích, jako je C++, ale v jazycích jako je Python či Smalltalk se typ a třída rozlišuje (s tím souvisí i rozlišení pojmů podtyp a podtřída). Programátor, který toto nepochopí, se bude pomocí funkce isinstance() (či jiných podobných mechanismů) snažit přemoci typovací systém Pythonu ve víře, že toto je správná cesta, protože na to byl v jiných jazycích zvyklý. Vystihuje to tento citát z wikipedie:

"Duck typing is aided by habitually not testing for the type of arguments in method and function bodies, relying on documentation, clear code, and testing to ensure correct use. Users of statically typed languages new to dynamically typed languages are usually tempted to add such static (before run-time) type checks, defeating the benefits and flexibility of duck typing, and constraining the language's dynamism."

Následující kus kódu ukazuje právě toto špatné použití funkce isinstance() (převzato odtud):

if isinstance(param, dict):
    value = param[member]

Pythonovské řešení je buď (toto řešení se navíc drží principu "It's better to ask for forgiveness than for permission."):

try:
    value = param[member]
except TypeError:
    # do something else

Nebo, v případě, že se jedná o jakousi aserci (to se týká i kódu jako assert isinstance(methodParam, SomeClass)), tuto aserci vypustit (pokud objekt nebude implementovat metodu __setitem__(), tak to odchytí typový systém Pythonu). Nás totiž nezajímá, zda předaný parametr je instancí nějaké třídy -- nás pouze zajímá, pokud podporuje danou operaci (kontrakt). Typický problém, který v těchto případech může nastat, je, pokud v testech předáme metodám spoléhajícím na isinstance() "falešný objekt" (mock či stub -- bez překladu).

Další problémy

Doporučuji si přečíst článek Manuela de la Pena na téma The Open/Close principle and isinstance in python, který vysvětluje, že neopatrným užitím funkce isinstance() můžete jednoduše porušit dva základní principy OOP: Open/Close principle a Liskov substitution principle. Dalším článek je isinstance() considered harmful (ukazuje některá další nevhodná použití funkce isinstance()).

Závěr

V tomto příspěvku jsem se snažil vysvětlit, proč není dobré používat v jazyce Python funkci isinstance() k dodatečné kontrole typů. Neříkám, že tato funkce nemá své opodstatněné použití, ale rozhodně by se neměla objevovat jako běžná součást pythonovoského kódu jen kvůli tomu, že programátor není seznámen s filosofií jazyka Python. Je třeba si uvědomit, že koncepty z jednoho jazyka nelze libovolně používat v jiných jazycích, pokud je daný jazyk stavěn na jiných základech.

Přidat komentář