2.34. Dinamički kôd

Tri ugrađene funkcije primaju niz Python izvornog koda i pokreću ga: eval(), exec() i compile(). Zajedno omogućuju kodu da konstruira i izvodi više koda tijekom izvođenja – što je povremeno upravo pravi alat, a daleko češće izvor pogrešaka i sigurnosnih rupa.

Upozorenje

Ove funkcije izvode proizvoljan Python. Prosljeđivanje korisničkog unosa funkcijama eval ili exec – niz iz datoteke koju korisnik može uređivati, podatak primljen preko mreže, vrijednost upisana na REPL upitu – omogućuje tom unosu da učini sve što i pozivajuća skripta, sve do brisanja svake datoteke na uređaju uključujući i to. Koristite ih promišljeno, nikada unutar koda koji se izvodi automatski, i nikada na podacima koje ne kontrolirate.

2.34.1. eval

eval() izvodi jedan izraz i vraća njegovu vrijednost:

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

Izraz prema zadanim postavkama vidi globalne i lokalne varijable pozivatelja, zbog čega se name razrješava u drugom primjeru. Prosljeđivanje eksplicitnih rječnika omogućuje vam stavljanje evaluacije u izolirano okruženje (sandbox):

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

Čak i u izoliranom okruženju, eval je opasan. Postoje dobro poznate tehnike za bijeg iz takvih izoliranih okruženja; nemojte se oslanjati samo na trik s praznim __builtins__ za nepouzdan unos.

2.34.2. exec

exec() izvodi blok koda umjesto jednog izraza – naredbe, definicije funkcija, petlje – i vraća None:

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

Izlaz:

0
1
2

Blok može definirati imena koja postaju dostupna nakon toga, uz neke napomene o lokalnom u odnosu na globalni doseg. exec unutar funkcije rijetko se ponaša onako kako autor očekuje; ako vam je potreban, pokrenite ga na razini modula.

2.34.3. compile

compile() pretvara niz izvornog koda u objekt koda koji se kasnije može proslijediti funkcijama eval() ili exec(). Koristite ga kada će se isti izvorni kôd izvoditi mnogo puta – raščlamba se događa jednom, izvođenje je brže:

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

Izlaz:

0
1
4
9
16

Srednji argument je oznaka koja se pojavljuje u praćenjima (tracebacks) ako kôd podigne iznimku. Treći argument je "eval" za jedan izraz, "exec" za blok, ili "single" za naredbu interaktivnog stila koja ispisuje svoj rezultat.

2.34.4. Kada posegnuti za ovima

Gotovo nikada. Većina slučajeva upotrebe koje početnici zamišljaju ima sigurnije alternative:

  • Čitanje konfiguracijske datoteke. Koristite json – strukturirani podaci, bez izvođenja.

  • Evaluacija brojčane vrijednosti koju upisuje korisnik. Koristite int() / float() za raščlambu, zatim aritmetiku. Ako korisnik doista treba unijeti formulu, koristite mali raščlanjivač izraza, a ne eval.

Kada vam doista trebaju eval / exec / compile, izolirajte mjesto poziva, zabilježite točan niz koji se sprema izvesti i tretirajte izvorni kôd kao najsumnjiviju stvar u svom kodu.