2.34. Dinamikus kód

Három beépített függvény vesz át egy Python forráskódot tartalmazó stringet és futtatja le: eval(), exec() és compile(). Együtt lehetővé teszik, hogy a kód több kódot építsen és hajtson végre futásidőben – ami időnként pontosan a megfelelő eszköz, sokkal gyakrabban azonban hibák és biztonsági rések forrása.

Figyelem

Ezek a függvények tetszőleges Python kódot hajtanak végre. Felhasználói bemenet átadása az eval vagy exec függvénynek – egy string egy fájlból, amelyet a felhasználó szerkeszthet, egy hálózaton fogadott adatcsomag, egy REPL prompton begépelt érték – lehetővé teszi, hogy az a bemenet bármit megtegyen, amit a hívó szkript megtehetne, egészen az eszközön lévő összes fájl törléséig bezárólag. Használd őket megfontoltan, soha ne automatikusan futó kódon belül, és soha ne olyan adaton, amely felett nincs ellenőrzésed.

2.34.1. eval

Az eval() egyetlen kifejezést futtat le, és visszaadja annak értékét:

>>> eval("3 * 7")
21
>>> name = "OpenMV"
>>> eval("name.lower()")
'openmv'

A kifejezés alapértelmezetten látja a hívó globális és lokális névterét, ezért oldódik fel a name a második példában. Explicit szótárak átadásával homokozóba zárhatod a kiértékelést:

eval("a + b", {"__builtins__": None}, {"a": 1, "b": 2})

Még homokozóban is veszélyes az eval. Jól ismert technikák léteznek az ilyen homokozók megkerülésére; ne hagyatkozz önmagában az üres __builtins__ trükkre megbízhatatlan bemenet esetén.

2.34.2. exec

Az exec() egy kódblokkot futtat egyetlen kifejezés helyett – utasításokat, függvénydefiníciókat, ciklusokat – és None értéket ad vissza:

exec("for i in range(3): print(i)")

Kimenet:

0
1
2

A blokk olyan neveket definiálhat, amelyek utána elérhetővé válnak, néhány fenntartással a lokális és globális hatókör tekintetében. Az exec egy függvényen belül ritkán viselkedik úgy, ahogy az író elvárná; ha szükséged van rá, modul szinten futtasd.

2.34.3. compile

A compile() egy forrásstringet alakít kódobjektummá, amely később átadható az eval() vagy exec() függvénynek. Akkor használd, amikor ugyanaz a forrás sokszor fog lefutni – az elemzés egyszer történik meg, a végrehajtás gyorsabb:

expr = compile("x * x", "<expr>", "eval")
for x in range(5):
    print(eval(expr))

Kimenet:

0
1
4
9
16

A középső argumentum egy címke, amely a visszakövetésekben (traceback) jelenik meg, ha a kód kivételt vált ki. A harmadik argumentum "eval" egyetlen kifejezéshez, "exec" egy blokkhoz, vagy "single" egy interaktív stílusú utasításhoz, amely kiírja az eredményét.

2.34.4. Mikor nyúlj ezekhez

Szinte soha. A legtöbb esetnek, amit a kezdők elképzelnek, biztonságosabb alternatívái vannak:

  • Konfigurációs fájl beolvasása. Használd a json modult – strukturált adat, végrehajtás nélkül.

  • A felhasználó által begépelt numerikus érték kiértékelése. Használd az int() / float() függvényt az elemzéshez, majd aritmetikát. Ha a felhasználónak valóban egy képletet kell megadnia, használj egy kis kifejezéselemzőt, ne az eval függvényt.

Amikor valóban szükséged van az eval / exec / compile függvényre, izoláld a hívás helyét, naplózd a pontos stringet, amelyet végre kíván hajtani, és kezeld a forrást a kódod leggyanúsabb dolgaként.