Eval, exec ve compile arasındaki fark nedir?


428

Python kodunun dinamik değerlendirmesine bakıyordum ve eval()ve compile()fonksiyonları ve execifadesiyle karşılaştım.

Birisi evalve arasındaki farkı ve execfarklı compile()uyum biçimlerini nasıl açıklayabilir ?

Yanıtlar:


517

Kısa cevap veya TL; DR

Temel olarak, evaliçin kullanılan Eval uate tek dinamik olarak oluşturulmuş Python ekspresyonu ve execiçin kullanılan exec dinamik olarak sadece yan etkileri için Python kodu oluşturulur ute.

evalve execşu iki farklılığa sahip olun:

  1. evalSadece kabul tek bir ifade , execdöngüler,: Python ifadeleri olan bir kod bloğu alabilir try: except:, classve fonksiyonu / yöntem defböyle devam initions ve.

    Python'daki bir ifade, değişken atamada değer olarak sahip olabileceğiniz her şeydir:

    a_variable = (anything you can put within these parentheses is an expression)
  2. eval verilen ifadenin değerini döndürür , oysa execdönüş değerini kodundan yok sayar ve her zaman döndürür None(Python 2'de bir deyimdir ve bir ifade olarak kullanılamaz, bu yüzden gerçekten hiçbir şey döndürmez).

1.0 - 2.7 sürümlerinde execbir deyim vardı, çünkü CPython'un fonksiyon execiçindeki yan etkileri için kullanılan fonksiyonlar için farklı bir kod nesnesi üretmesi gerekiyordu .

Python 3'te execbir fonksiyondur; kullanımının, kullanıldığı işlevin derlenmiş bayt kodu üzerinde hiçbir etkisi yoktur.


Temel olarak:

>>> 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

compileİçinde 'exec'modunda bir bayt içine tabloların herhangi bir sayıda derler dolaylı olarak zaman döner Noneoysa 'eval'modunda bir derler tek bayt içine ekspresyonu bu döner bu ifadenin değeri.

>>> 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

In 'eval'(ve dolayısıyla modunda evalbir dize geçirilen eğer fonksiyonu), compilekaynak kod ifadeleri veya tek ifadesi ötesinde başka bir şey içeriyorsa bir istisna yükseltir:

>>> 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

Aslında "eval sadece tek bir ifadeyi kabul eder" ifadesi yalnızca bir dize (Python kaynak kodu içeren ) iletildiğinde geçerlidir eval. Sonra dahili bytecode kullanarak derlenir compile(source, '<string>', 'eval')Bu fark gerçekten geliyor.

Bir ederse code(Python içeren nesne bayt kodu ) geçirilir execya eval, onlar aynı şekilde davranır gerçeği hariç, exechala dönen, dönüş değeri yok sayar Nonehep. Bu nedenle, evaldeyim içeren bir şeyi yürütmek mümkündür , eğer compiledize olarak geçirmek yerine bayt koduna girerseniz :

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

derlenmiş kod deyimleri içermesine rağmen sorunsuz çalışır. Yine de döner None, çünkü bu, döndürülen kod nesnesinin dönüş değeridir compile.

In 'eval'(ve dolayısıyla modunda evalbir dize geçirilen eğer fonksiyonu), compilekaynak kod ifadeleri veya tek ifadesi ötesinde başka bir şey içeriyorsa bir istisna yükseltir:

>>> 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

Daha uzun cevap, yani kanlı detaylar

exec ve eval

exec(Oldu fonksiyonu Python 2'de bir açıklama ) dinamik olarak oluşturulan bir açıklama veya programı çalıştırmak için kullanılır:

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

evalFonksiyon için aynısını yapar tek bir ifade , ve ifade değerini verir:

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

execve evalher ikisi de bir program / ifadesi olarak çalıştırılabilir kabul str, unicodeya da bytesya da bir şekilde, bir nesne içeren kaynak kodu codenesne Python bayt kodu ihtiva eder.

Bir str/ unicode/ bytesiçeren kaynak kodu iletildiyse exec, aşağıdakilere eşit davranır:

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

ve evalbenzer şekilde aşağıdakilere eşdeğer davranır:

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

Tüm ifadeler Python'da ifadeler olarak kullanılabileceğinden (bunlara ExprPython soyut dilbilgisinde düğümler denir ; tam tersi doğru değildir), execdönüş değerine ihtiyacınız yoksa her zaman kullanabilirsiniz . Yani, ya eval('my_func(42)')da exec('my_func(42)'), evaldöndürülen değeri döndüren my_funcve execonu atayan farkı kullanabilirsiniz :

>>> 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
>>> 

2 arasından yalnızca execifadeler içermektedir kaynak kodu kabul gibi def, for, while, import, veya class, atama deyimi (aka a = 42), veya tüm programlar:

>>> 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

Hem execve eval- 2 ek pozisyonel argümanları kabul globalsve locals- küresel ve yerel değişken kod gördüğünü kapsamları vardır. İçin Bu varsayılan globals()ve locals()denilen o kapsamındaki execveya eval, ancak herhangi bir sözlük kullanılabilir için globalsve herhangi mappingiçin locals(dahil dicttabii ki). Bunlar yalnızca kodun gördüğü değişkenleri kısıtlamak / değiştirmek için değil, aynı zamanda executed kodun oluşturduğu değişkenleri yakalamak için de kullanılır :

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

(Eğer bütün şeklinde değerini görüntülemek varsa g, bunun nedeni, çok daha uzun olacaktır execve evalyerleşik ins olarak modülü eklemek __builtins__eksik mesajı otomatik olarak globaller kadar).

Python 2'de, resmi sözdizimi execaçıklamada aslında exec code in globals, localsolduğu gibi,

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

Ancak alternatif sözdizimi exec(code, globals, locals)de her zaman kabul edilmiştir (aşağıya bakınız).

compile

compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)Yerleşik ile aynı kod tekrarlanan çağırmaları hızlandırmak için kullanılabilir execya da evalbir içine kaynağını derleyerek codeönceden nesne. modeParametre, kod parçası tür compilefonksiyonu kabul eder ve ürettiği bayt kodu tür. Seçimler 'eval', 'exec've 'single':

  • 'eval'modu tek bir ifade bekler ve çalıştırıldığında o ifadenin değerini döndürecek bayt kodu üretir :

    >>> 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'tek ifadelerden tüm kod modüllerine kadar her türlü python yapısını kabul eder ve bunları modül üst düzey ifadeleriymiş gibi yürütür. Kod nesnesi şunu döndürür 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'son ifade bir ifade deyimi ise, tek bir deyim 'exec'içeren (veya birbirinden ayrılmış birden çok deyim ) bir kaynak kodunu kabul eden sınırlı bir biçimdir , sonuçta elde edilen bayt kodu da bu ifadenin değerini standart çıktıya (!) yazdırır .;repr

    Bir if- elif- elsezinciri, bir döngü elseve trykendi diliyle except, elseve finallybloklar tek deyim olarak kabul edilir.

    2 üst düzey ifade içeren bir kaynak parçası için bir hatadır 'single', ancak Python 2'de bazen kodda birden fazla üst düzey ifadeye izin veren bir hata vardır; sadece ilki derlenir; geri kalanı yok sayılır:

    Python 2.7.8'de:

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

    Ve Python 3.4.2'de:

    >>> 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

    Bu etkileşimli Python kabukları yapmak için çok yararlıdır. Ancak, ortaya çıkan kod olsa bile , ifadenin değeri döndürülmezeval .

Böylece fonksiyon ve modlarından en büyük ayrımı execve evalaslında gelir compile.


Kaynak kodu bayt koduna derlemeye ek olarak, soyut sözdizimi ağaçlarının (Python kodunun ayrıştırma ağaçları) nesnelere compilederlenmesini destekler ; ve soyut sözdizimi ağaçlarına kaynak kodu ( Python ile yazılmış ve sadece çağrılar ); bunlar, örneğin kaynak kodunu anında değiştirmek ve ayrıca dinamik kod oluşturmak için kullanılır, çünkü kodu karmaşık durumlarda metin satırları yerine bir düğüm ağacı olarak işlemek daha kolaydır.codeast.parsecompile(source, filename, mode, PyCF_ONLY_AST)


evalYalnızca tek bir ifade içeren bir dizeyi değerlendirmenize izin verirken , bir deyimin evaltamamını veya compilebayt koduna dönüştürülmüş bir modülü bile yapabilirsiniz ; yani, Python 2 printile bir deyimdir ve evaldoğrudan yönlendirilemez:

>>> 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

compileonunla 'exec'bir içine modunda codenesne ve mümkün eval it ; evalfonksiyon dönecektir 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

CPython 3'te kaynak koduna bakar evalve execbakarsanız, bu çok belirgindir; her ikisi de PyEval_EvalCodeaynı argümanlarla çağrılır, tek fark execaçıkça dönendirNone .

execPython 2 ve Python 3 arasındaki sözdizimi farkları

Python önemli farklardan biri 2'ye yani execbir ifadedir ve evalbir dahili fonksiyonu (hem yerleşik işlevler Python 3'te). execPython 2'nin resmi sözdiziminin olduğu bilinen bir gerçektir exec code [in globals[, locals]].

Python 2-to-3 çoğunluğunun aksine süpürmede kılavuzları görünüyor önermek , execCPython 2'de bildiride de bu sözdizimi ile kullanılabilir görünüyor aynen böyle execPython 3'te işlev çağırma Python 0.9.9 yapmamızın en önemli sebebi olduğunu exec(code, globals, locals)yerleüik işlevde! Ve bu yerleşik işlev, Python 1.0 sürümünden önce bir yerdeexec ifadeyle değiştirildi .

Python 0.9.9 ile geriye dönük uyumluluğun kırılmaması arzu edildiğinden, Guido van Rossum 1993'te bir uyumluluk kesmek ekledi : eğer code2 veya 3 uzunluğunda bir demetse globalsve aksi takdirde ifadeye localsgeçirilmemişse exec, codeyorumlanacaktı sanki tupleın 2. ve 3. elementleri sırasıyla globalsve localsidi. Uyumluluk kesmek Python 1.4 belgelerinde (çevrimiçi olarak en eski sürüm) bile bahsedilmedi ; ve böylece, Kasım 2012'de tekrar belgelenene kadar, taşıma kılavuzlarının ve araçlarının birçok yazarları tarafından bilinmiyordu :

Birinci ifade ayrıca 2 veya 3 uzunluğunda bir demet de olabilir. Bu durumda, isteğe bağlı parçalar atlanmalıdır. Form exec(expr, globals)eşittir exec expr in globals, form exec(expr, globals, locals)eşittir exec expr in globals, locals. Tuple biçimi , bir deyimden ziyade bir işlev execolan Python 3 ile uyumluluk sağlar exec.

Evet, CPython 2.7'de, yirmi yıldır geriye dönük uyumluluk için orada bulunduğunda, ileriye dönük bir uyumluluk seçeneği olarak adlandırılan (neden insanları geriye dönük bir uyumluluk seçeneği olduğu konusunda karıştırmayın) .

Böylece ise execPython 1 ve Python 2'de bir ifadesidir ve bu yerleşik bir işleve Python 3 ve Python 0.9.9 yılında,

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

muhtemelen yayınlanmış her Python sürümünde aynı davranışlara sahipti; ve Jython 2.5.2, PyPy 2.3.1 (Python 2.7.6) ve IronPython 2.6.1'de de çalışır (CPython'un belgelenmemiş davranışını yakından izleyen onlara şuralar).

Uyumluluk kesmek ile Pythons 1.0 - 2.7'de yapamayacağınız şey, dönüş değerini execbir değişkene depolamaktır :

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

(Python 3'te her execzaman olduğu gibi yararlı olmaz None) veya şunlara bir referans iletin exec:

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

Olasılıkla birisinin aslında kullandığı bir kalıp;

Veya liste kavrayışında kullanın:

>>> [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

Bu, liste kavramalarının kötüye kullanılmasıdır ( forbunun yerine bir döngü kullanın!).


[i for i in globals().values() if hasattr(i, '__call__')][0]Bir ifade veya ifade miydi ? Bir ifade olsaydı, neden @dekoratör olarak kullanamıyorum ?
Mario

bir ifadedir. 42aynı zamanda bir ifadedir ve bunu @dekoratör olarak kullanamazsınız .
Antti Haapala

Dekoratör sözdizimi decorator ::= "@" dotted_name ["(" [parameter_list [","]] ")"] NEWLINE; başka bir deyişle, dekoratörler, SADECE bir (belki noktalı) tanımlayıcı ve ardından isteğe bağlı çağrı bağımsız değişkenleri olarak rastgele ifadeler kullanamazsınız.
Antti Haapala

1
Bir ödevin sağ tarafına konabilecek ve hala derlenebilecek hiçbir şey bir ifade değildir. Örneğin, a = b = csağ tarafı gibi mükemmel b = cbir ifadedir - ki bu bir ifade değildir.
Tom

194
  1. execbir ifade değildir: Python 2.x'de bir ifade ve Python 3.x'te bir işlev. Bir dizede bulunan bir deyimi veya deyim kümesini derler ve derhal değerlendirir. Misal:

    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. evalifadeyi değerlendiren ve ifadenin ürettiği değeri döndüren yerleşik bir işlevdir ( deyim değil ). Misal:

    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. compiledaha düşük seviyeli bir versiyonudur execve eval. İfadelerinizi veya ifadelerinizi yürütmez veya değerlendirmez, ancak bunu yapabilen bir kod nesnesi döndürür. Modlar aşağıdaki gibidir:

    1. compile(string, '', 'eval')yapmış olsaydınız yürütülecek olan kod nesnesini döndürür eval(string). O Not olamaz bu modda ifadeleri kullanmak; yalnızca (tek) bir ifade geçerlidir.
    2. compile(string, '', 'exec')yapmış olsaydınız yürütülecek olan kod nesnesini döndürür exec(string). Burada istediğiniz sayıda ifadeyi kullanabilirsiniz.
    3. compile(string, '', 'single')gibidir execmodunda ama ilk açıklamada hariç her şeyi göz ardı eder. Sonuçları içeren bir if/ elseifadesinin tek bir ifade olarak kabul edildiğini unutmayın.

40
Python 3'te, exec()şimdi aslında bir işlev.
Tim Pietzcker

2
(İşaret ettiğiniz gibi), exechedeflediğiniz sürümde bir ifade olduğundan, bu ebeveynleri dahil etmek aldatıcıdır ve eğer in globals, localsbuggy de kullanmaya çalışırsanız .
Mike Graham

2
@MikeGraham exec parantezleri destekler ve Python 2'de çağırma gibi işlev görür .
Antti Haapala

2
@AnttiHaapala, 'parantezleri destekliyor' ataması yapabildiğiniz için yapabilirsiniz x = (y), bu doğru olabilir. Başka bir ifade-döndü-fonksiyonu print; print(1, 2, 3)python 2 ve 3'teki sonuçları karşılaştırın .
habnabit

1
@habnabit böyle değil. Lütfen cevabımın alt kısmını buradan okuyun ve şaşırın.
Antti Haapala

50

exec ifade içindir ve hiçbir şey döndürmez. eval ifadedir ve ifadenin değerini döndürür.

ifade "bir şey" anlamına gelirken ifade "bir şey yap" anlamına gelir.


9
İkinci paragraf o kadar basittir ki neredeyse yalan haline gelir, bir ifade bir işlev çağrısı içeriyorsa çok şey yapabilir.
Antti Haapala
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.