Apa perbedaan antara eval, exec, dan compile?

428

Saya telah melihat evaluasi dinamis kode Python, dan menemukan eval()dan compile()fungsi, dan execpernyataan itu.

Dapatkah seseorang tolong jelaskan perbedaan antara evaldan exec, dan bagaimana berbagai mode compile()cocok?

andrewdotnich
sumber

Jawaban:

517

Jawaban singkatnya, atau TL; DR

Pada dasarnya, evaldigunakan untuk eval uate satu yang dihasilkan secara dinamis ekspresi Python, dan execdigunakan untuk exec ute yang dihasilkan secara dinamis kode Python hanya untuk efek samping.

evaldan execmemiliki dua perbedaan ini:

  1. evalhanya menerima satu ekspresi , execdapat mengambil blok kode yang memiliki pernyataan Python: loop try: except:,, classdan fungsi / metode definitions dan sebagainya.

    Ekspresi dalam Python adalah apa pun yang Anda dapat miliki sebagai nilai dalam penugasan variabel:

    a_variable = (anything you can put within these parentheses is an expression)
  2. eval mengembalikan nilai dari ekspresi yang diberikan, sedangkan execmengabaikan nilai kembali dari kodenya, dan selalu mengembalikan None(dalam Python 2 itu adalah pernyataan dan tidak dapat digunakan sebagai ekspresi, jadi itu benar-benar tidak mengembalikan apa pun).

Dalam versi 1.0 - 2.7, execadalah pernyataan, karena CPython diperlukan untuk menghasilkan berbagai jenis objek kode untuk fungsi yang digunakan execuntuk efek sampingnya di dalam fungsi.

Dalam Python 3, execadalah fungsi; penggunaannya tidak berpengaruh pada bytecode yang dikompilasi dari fungsi di mana ia digunakan.


Jadi pada dasarnya:

>>> a = 5
>>> eval('37 + a')   # it is an expression
42
>>> exec('37 + a')   # it is an expression statement; value is ignored (None is returned)
>>> exec('a = 47')   # modify a global variable as a side effect
>>> a
47
>>> eval('a = 47')  # you cannot evaluate a statement
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    a = 47
      ^
SyntaxError: invalid syntax

The compiledalam 'exec'modus mengkompilasi sejumlah pernyataan menjadi bytecode yang secara implisit selalu kembali None, sedangkan di 'eval'modus itu menyusun satu ekspresi menjadi bytecode yang kembali nilai ekspresi itu.

>>> eval(compile('42', '<string>', 'exec'))  # code returns None
>>> eval(compile('42', '<string>', 'eval'))  # code returns 42
42
>>> exec(compile('42', '<string>', 'eval'))  # code returns 42,
>>>                                          # but ignored by exec

Dalam 'eval'mode (dan dengan evalfungsi jika sebuah string dilewatkan), compilemenimbulkan pengecualian jika kode sumber berisi pernyataan atau hal lain di luar satu ekspresi:

>>> compile('for i in range(3): print(i)', '<string>', 'eval')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    for i in range(3): print(i)
      ^
SyntaxError: invalid syntax

Sebenarnya pernyataan "eval hanya menerima satu ekspresi" hanya berlaku ketika sebuah string (yang berisi kode sumber Python ) diteruskan ke eval. Kemudian secara internal dikompilasi ke bytecode menggunakan compile(source, '<string>', 'eval')Di sinilah perbedaannya berasal.

Jika codeobjek (yang berisi bytecode Python ) diteruskan ke execatau eval, mereka berperilaku identik , kecuali untuk fakta yang execmengabaikan nilai kembali, masih Noneselalu kembali . Jadi mungkin digunakan evaluntuk mengeksekusi sesuatu yang memiliki pernyataan, jika Anda hanya compilemengubahnya menjadi bytecode sebelum alih-alih mengirimkannya sebagai string:

>>> eval(compile('if 1: print("Hello")', '<string>', 'exec'))
Hello
>>>

berfungsi tanpa masalah, meskipun kode yang dikompilasi berisi pernyataan. Itu masih kembali None, karena itu adalah nilai kembali dari objek kode yang dikembalikan compile.

Dalam 'eval'mode (dan dengan evalfungsi jika sebuah string dilewatkan), compilemenimbulkan pengecualian jika kode sumber berisi pernyataan atau hal lain di luar satu ekspresi:

>>> compile('for i in range(3): print(i)', '<string>'. 'eval')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    for i in range(3): print(i)
      ^
SyntaxError: invalid syntax

Jawaban yang lebih panjang, alias detail berdarah

exec dan eval

The execfunction (yang pernyataan di Python 2 ) digunakan untuk mengeksekusi pernyataan atau program yang dibuat secara dinamis:

>>> program = '''
for i in range(3):
    print("Python is cool")
'''
>>> exec(program)
Python is cool
Python is cool
Python is cool
>>> 

The evalfungsi melakukan hal yang sama untuk ekspresi tunggal , dan mengembalikan nilai dari ekspresi:

>>> a = 2
>>> my_calculation = '42 * a'
>>> result = eval(my_calculation)
>>> result
84

execdan evalkeduanya menerima program / ekspresi untuk dijalankan baik sebagai str, unicodeatau bytesobjek yang mengandung kode sumber, atau sebagai codeobjek yang berisi bytecode Python.

Jika a str/ unicode/ bytesberisi kode sumber diteruskan ke exec, itu berperilaku setara dengan:

exec(compile(source, '<string>', 'exec'))

dan evaljuga berperilaku setara dengan:

eval(compile(source, '<string>', 'eval'))

Karena semua ekspresi dapat digunakan sebagai pernyataan dalam Python (ini disebut Exprnode dalam tata bahasa abstrak Python ; kebalikannya tidak benar), Anda selalu dapat menggunakan execjika Anda tidak memerlukan nilai kembali. Artinya, Anda bisa menggunakan salah satu eval('my_func(42)')atau exec('my_func(42)'), perbedaannya adalah yang evalmengembalikan nilai yang dikembalikan oleh my_func, dan execmembuangnya:

>>> def my_func(arg):
...     print("Called with %d" % arg)
...     return arg * 2
... 
>>> exec('my_func(42)')
Called with 42
>>> eval('my_func(42)')
Called with 42
84
>>> 

Dari 2, hanya execmenerima kode sumber yang berisi pernyataan, seperti def, for, while, import, atau class, pernyataan penugasan (alias a = 42), atau seluruh program:

>>> exec('for i in range(3): print(i)')
0
1
2
>>> eval('for i in range(3): print(i)')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    for i in range(3): print(i)
      ^
SyntaxError: invalid syntax

Keduanya execdan evalmenerima 2 argumen posisi tambahan - globalsdan locals- yang merupakan lingkup variabel global dan lokal yang dilihat kode. Ini default ke globals()dan locals()dalam lingkup yang disebut execatau eval, tetapi kamus apa pun dapat digunakan untuk globalsdan apa saja mappinguntuk locals(termasuk dicttentu saja). Ini dapat digunakan tidak hanya untuk membatasi / memodifikasi variabel yang dilihat kode, tetapi sering juga digunakan untuk menangkap variabel yang dibuat oleh execkode yang digunakan:

>>> g = dict()
>>> l = dict()
>>> exec('global a; a, b = 123, 42', g, l)
>>> g['a']
123
>>> l
{'b': 42}

(Jika Anda menampilkan nilai keseluruhan g, itu akan jauh lebih lama, karena execdan evalmenambahkan modul built-in __builtins__ke global secara otomatis jika hilang).

Dalam Python 2, sintaks resmi untuk execpernyataan tersebut sebenarnya exec code in globals, locals, seperti pada

>>> exec 'global a; a, b = 123, 42' in g, l

Namun sintaks alternatif exec(code, globals, locals)selalu diterima juga (lihat di bawah).

compile

The compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)built-in dapat digunakan untuk mempercepat pemanggilan ulang kode yang sama dengan execatau evaldengan menyusun sumber menjadi codeobjek terlebih dahulu. The modeparameter kontrol jenis kode fragmen compilefungsi menerima dan jenis bytecode menghasilkan. Pilihannya adalah 'eval', 'exec'dan 'single':

  • 'eval'mode mengharapkan satu ekspresi, dan akan menghasilkan bytecode bahwa ketika dijalankan akan mengembalikan nilai ekspresi itu :

    >>> dis.dis(compile('a + b', '<string>', 'eval'))
      1           0 LOAD_NAME                0 (a)
                  3 LOAD_NAME                1 (b)
                  6 BINARY_ADD
                  7 RETURN_VALUE
  • 'exec'menerima segala jenis konstruksi python dari ekspresi tunggal ke seluruh modul kode, dan menjalankannya seolah-olah itu adalah pernyataan tingkat atas modul. Objek kode kembali None:

    >>> dis.dis(compile('a + b', '<string>', 'exec'))
      1           0 LOAD_NAME                0 (a)
                  3 LOAD_NAME                1 (b)
                  6 BINARY_ADD
                  7 POP_TOP                             <- discard result
                  8 LOAD_CONST               0 (None)   <- load None on stack
                 11 RETURN_VALUE                        <- return top of stack
  • 'single'adalah bentuk terbatas 'exec'yang menerima kode sumber yang berisi pernyataan tunggal (atau beberapa pernyataan dipisahkan oleh ;) jika pernyataan terakhir adalah pernyataan ekspresi, kode bytec yang dihasilkan juga mencetak reprnilai dari ekspresi itu ke output standar (!) .

    Sebuah if- elif- elserantai, loop dengan else, dan trydengan yang except, elsedan finallyblok dianggap sebagai pernyataan tunggal.

    Sumber fragmen yang berisi 2 pernyataan tingkat atas adalah kesalahan untuk 'single', kecuali dalam Python 2 ada bug yang kadang-kadang memungkinkan beberapa pernyataan tingkat atas dalam kode; hanya yang pertama dikompilasi; sisanya diabaikan:

    Dalam Python 2.7.8:

    >>> exec(compile('a = 5\na = 6', '<string>', 'single'))
    >>> a
    5

    Dan dengan Python 3.4.2:

    >>> exec(compile('a = 5\na = 6', '<string>', 'single'))
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<string>", line 1
        a = 5
            ^
    SyntaxError: multiple statements found while compiling a single statement

    Ini sangat berguna untuk membuat cangkang Python interaktif. Namun, nilai ekspresi tidak dikembalikan , bahkan jika Anda evalmenghasilkan kode.

Jadi perbedaan terbesar execdan evalsebenarnya berasal dari compilefungsi dan modenya.


Selain mengkompilasi kode sumber ke bytecode, compilemendukung kompilasi pohon sintaksis abstrak (parse pohon kode Python) menjadi codeobjek; dan kode sumber menjadi pohon sintaksis abstrak ( ast.parseditulis dengan Python dan panggilan biasa compile(source, filename, mode, PyCF_ONLY_AST)); ini digunakan misalnya untuk memodifikasi kode sumber dengan cepat, dan juga untuk pembuatan kode dinamis, karena seringkali lebih mudah untuk menangani kode sebagai pohon node daripada baris teks dalam kasus kompleks.


Meskipun evalhanya memungkinkan Anda untuk mengevaluasi string yang berisi ekspresi tunggal, Anda dapat evalseluruh pernyataan, atau bahkan seluruh modul yang telah compiled menjadi bytecode; yaitu, dengan Python 2, printadalah pernyataan, dan tidak dapat evaldipimpin langsung:

>>> eval('for i in range(3): print("Python is cool")')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    for i in range(3): print("Python is cool")
      ^
SyntaxError: invalid syntax

compiledengan 'exec'modus menjadi codeobjek dan Anda dapat eval itu ; yang evalfungsi akan kembali None.

>>> code = compile('for i in range(3): print("Python is cool")',
                   'foo.py', 'exec')
>>> eval(code)
Python is cool
Python is cool
Python is cool

Jika seseorang melihat ke dalam evaldan execkode sumber di CPython 3, ini sangat jelas; mereka berdua memanggil PyEval_EvalCodedengan argumen yang sama, satu-satunya perbedaan adalah yang execsecara eksplisit kembaliNone .

Perbedaan sintaksis execantara Python 2 dan Python 3

Salah satu perbedaan utama dalam Python 2 adalah itu execadalah pernyataan dan evalmerupakan fungsi bawaan (keduanya adalah fungsi bawaan pada Python 3). Ini adalah fakta yang diketahui bahwa sintaks resmi execdalam Python 2 adalah exec code [in globals[, locals]].

Tidak seperti mayoritas panduan porting Python 2-ke-3 tampaknya menyarankan , pernyataan dalam CPython 2 dapat juga digunakan dengan sintaks yang terlihat persis seperti pemanggilan fungsi di Python 3. Alasannya adalah bahwa Python 0.9.9 memiliki built-in dalam fungsi! Dan fungsi bawaan itu diganti dengan pernyataan di suatu tempat sebelum rilis Python 1.0 . exec execexec(code, globals, locals)exec

Karena diinginkan untuk tidak merusak kompatibilitas dengan Python 0.9.9, Guido van Rossum menambahkan retasan kompatibilitas pada tahun 1993 : jika codeitu adalah tuple dengan panjang 2 atau 3, dan globalsdan localstidak dimasukkan ke dalam execpernyataan sebaliknya, maka codeakan ditafsirkan seolah-olah elemen ke-2 dan ke-3 dari tuple adalah globalsdan localsmasing - masing. Retasan kompatibilitas tidak disebutkan bahkan dalam dokumentasi Python 1.4 (versi online paling awal yang tersedia) ; dan dengan demikian tidak diketahui banyak penulis panduan dan alat porting, sampai didokumentasikan lagi pada November 2012 :

Ekspresi pertama mungkin juga merupakan tuple dengan panjang 2 atau 3. Dalam hal ini, bagian opsional harus dihilangkan. Bentuknya exec(expr, globals)setara dengan exec expr in globals, sedangkan bentuknya exec(expr, globals, locals)setara dengan exec expr in globals, locals. Bentuk tuple execmenyediakan kompatibilitas dengan Python 3, di mana execfungsi daripada pernyataan.

Ya, dalam CPython 2.7 bahwa itu dengan mudah disebut sebagai opsi kompatibilitas-maju (mengapa membingungkan orang-orang bahwa ada opsi kompatibilitas mundur sama sekali), padahal sebenarnya ada di sana untuk kompatibilitas mundur selama dua dekade .

Jadi sementara execadalah pernyataan dalam Python 1 dan Python 2, dan fungsi bawaan di Python 3 dan Python 0.9.9,

>>> exec("print(a)", globals(), {'a': 42})
42

telah memiliki perilaku yang sama dalam setiap versi Python yang dirilis secara luas; dan bekerja di Jython 2.5.2, PyPy 2.3.1 (Python 2.7.6) dan IronPython 2.6.1 juga (pujian kepada mereka mengikuti perilaku tidak berdokumen CPython erat).

Apa yang tidak dapat Anda lakukan di Pythons 1.0 - 2.7 dengan retas kompatibilitasnya, adalah menyimpan nilai pengembalian execke dalam variabel:

Python 2.7.11+ (default, Apr 17 2016, 14:00:29) 
[GCC 5.3.1 20160413] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a = exec('print(42)')
  File "<stdin>", line 1
    a = exec('print(42)')
           ^
SyntaxError: invalid syntax

(yang tidak akan berguna dalam Python 3, seperti execbiasa mengembalikan None), atau meneruskan referensi ke exec:

>>> call_later(exec, 'print(42)', delay=1000)
  File "<stdin>", line 1
    call_later(exec, 'print(42)', delay=1000)
                  ^
SyntaxError: invalid syntax

Mana pola yang mungkin digunakan seseorang, meskipun tidak mungkin;

Atau gunakan dalam pemahaman daftar:

>>> [exec(i) for i in ['print(42)', 'print(foo)']
  File "<stdin>", line 1
    [exec(i) for i in ['print(42)', 'print(foo)']
        ^
SyntaxError: invalid syntax

yang merupakan penyalahgunaan pemahaman daftar (gunakan forloop sebagai gantinya!).

Antti Haapala
sumber
Apakah [i for i in globals().values() if hasattr(i, '__call__')][0]pernyataan atau ungkapan? Jika itu ekspresi, mengapa saya tidak bisa menggunakannya dengan @sebagai dekorator?
Mario
itu adalah ekspresi. 42juga merupakan ekspresi, dan Anda tidak dapat menggunakannya dengan @sebagai dekorator.
Antti Haapala
Sintaks dekorator adalah decorator ::= "@" dotted_name ["(" [parameter_list [","]] ")"] NEWLINE; yaitu Anda tidak dapat menggunakan ekspresi sewenang-wenang sebagai dekorator, HANYA pengidentifikasi (mungkin bertitik), diikuti oleh argumen panggilan opsional.
Antti Haapala
1
Bukan apa pun yang bisa diletakkan di sisi kanan penugasan dan masih kompilasi adalah ekspresi. Misalnya, a = b = cadalah pernyataan yang benar-benar valid, seperti halnya sisi kanannya b = c- yang bukan merupakan ekspresi.
Tom
194
  1. execbukan ekspresi: pernyataan dalam Python 2.x, dan fungsi dalam Python 3.x. Ini mengkompilasi dan segera mengevaluasi pernyataan atau set pernyataan yang terkandung dalam string. Contoh:

    exec('print(5)')           # prints 5.
    # exec 'print 5'     if you use Python 2.x, nor the exec neither the print is a function there
    exec('print(5)\nprint(6)')  # prints 5{newline}6.
    exec('if True: print(6)')  # prints 6.
    exec('5')                 # does nothing and returns nothing.
  2. evaladalah fungsi bawaan ( bukan pernyataan), yang mengevaluasi ekspresi dan mengembalikan nilai yang dihasilkan ekspresi. Contoh:

    x = eval('5')              # x <- 5
    x = eval('%d + 6' % x)     # x <- 11
    x = eval('abs(%d)' % -100) # x <- 100
    x = eval('x = 5')          # INVALID; assignment is not an expression.
    x = eval('if 1: x = 4')    # INVALID; if is a statement, not an expression.
  3. compileadalah versi tingkat lebih rendah dari execdan eval. Itu tidak menjalankan atau mengevaluasi pernyataan atau ekspresi Anda, tetapi mengembalikan objek kode yang dapat melakukannya. Mode adalah sebagai berikut:

    1. compile(string, '', 'eval')mengembalikan objek kode yang akan dieksekusi telah Anda lakukan eval(string). Perhatikan bahwa Anda tidak dapat menggunakan pernyataan dalam mode ini; hanya satu (satu) ekspresi yang valid.
    2. compile(string, '', 'exec')mengembalikan objek kode yang akan dieksekusi telah Anda lakukan exec(string). Anda dapat menggunakan sejumlah pernyataan di sini.
    3. compile(string, '', 'single')seperti execmode, tetapi akan mengabaikan segalanya kecuali untuk pernyataan pertama. Perhatikan bahwa if/ elsepernyataan dengan hasilnya dianggap sebagai pernyataan tunggal.
Max Shawabkeh
sumber
40
Dalam Python 3, exec()sekarang sebenarnya adalah sebuah fungsi.
Tim Pietzcker
2
Karena (seperti yang Anda tunjukkan), execadalah pernyataan dalam versi yang Anda targetkan, menipu untuk memasukkan parens tersebut, dan jika Anda mencoba menggunakan in globals, locals, juga buggy.
Mike Graham
2
@MikeGraham exec mendukung tanda kurung dan fungsi seperti doa dalam Python 2 .
Antti Haapala
2
@AnttiHaapala sejauh tugas 'mendukung tanda kurung' karena Anda dapat melakukannya x = (y), itu mungkin benar. Pernyataan-berubah-fungsi lainnya adalah print; bandingkan hasil print(1, 2, 3)dalam python 2 dan 3.
habnabit
1
@habnabit tidak seperti itu. Silakan baca bagian bawah jawaban saya di sini dan kaget.
Antti Haapala
50

exec adalah untuk pernyataan dan tidak mengembalikan apa pun. eval adalah untuk ekspresi dan mengembalikan nilai ekspresi.

ekspresi berarti "sesuatu" sedangkan pernyataan berarti "melakukan sesuatu".

Wu Li
sumber
9
Paragraf kedua adalah penyederhanaan sehingga hampir menjadi sebuah kebohongan, sebuah ekspresi dapat melakukan banyak hal jika menyertakan panggilan fungsi.
Antti Haapala