Sự khác biệt giữa eval, exec và compile là gì?


428

Tôi đã xem xét đánh giá động về mã Python, và tìm thấy các hàm eval()compile()hàm và execcâu lệnh.

Ai đó có thể vui lòng giải thích sự khác biệt giữa evalexec, và làm thế nào các chế độ khác nhau compile()phù hợp?

Câu trả lời:


517

Câu trả lời ngắn hoặc TL; DR

Về cơ bản, evalđược sử dụng để eval uate một tạo động biểu Python duy nhất, và execđược sử dụng để exec ute tạo động mã Python chỉ cho tác dụng phụ của nó.

evalexeccó hai điểm khác biệt sau:

  1. evalchỉ chấp nhận một biểu thức duy nhất , execcó thể mất một khối mã có báo cáo Python: vòng, try: except:, classvà chức năng / phương pháp definitions và vân vân.

    Một biểu thức trong Python là bất cứ thứ gì bạn có thể có với giá trị trong một phép gán biến:

    a_variable = (anything you can put within these parentheses is an expression)
  2. eval trả về giá trị của biểu thức đã cho, trong khi execbỏ qua giá trị trả về từ mã của nó và luôn trả về None(trong Python 2, nó là một câu lệnh và không thể được sử dụng như một biểu thức, vì vậy nó thực sự không trả về bất cứ thứ gì).

Trong các phiên bản 1.0 - 2.7, execlà một tuyên bố, bởi vì CPython cần tạo ra một loại đối tượng mã khác cho các hàm được sử dụng execcho các tác dụng phụ của nó bên trong hàm.

Trong Python 3, execlà một hàm; việc sử dụng nó không ảnh hưởng đến mã byte được biên dịch của hàm nơi nó được sử dụng.


Về cơ bản:

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

Các compiletrong 'exec'chế độ biên dịch bất kỳ số lượng báo cáo thành bytecode mà ngầm luôn lợi nhuận None, trong khi ở 'eval'chế độ nó biên dịch một đơn biểu hiện thành bytecode rằng lợi nhuận giá trị của biểu thức đó.

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

Trong 'eval'chế độ (và do đó với evalhàm nếu một chuỗi được truyền vào), compilesẽ xuất hiện một ngoại lệ nếu mã nguồn chứa các câu lệnh hoặc bất cứ thứ gì khác ngoài một biểu thức:

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

Trên thực tế, câu lệnh "eval chỉ chấp nhận một biểu thức" chỉ áp dụng khi một chuỗi (chứa mã nguồn Python ) được truyền vào eval. Sau đó, nó được biên dịch nội bộ sang mã byte bằng cách sử dụng compile(source, '<string>', 'eval')Đây là nơi khác biệt thực sự đến từ.

Nếu một codeđối tượng (có chứa mã byte Python ) được truyền tới exechoặc eval, chúng sẽ hành xử giống hệt nhau , ngoại trừ thực tế là execbỏ qua giá trị trả về, vẫn Noneluôn trả về . Vì vậy, có thể sử dụng evalđể thực thi một cái gì đó có các câu lệnh, nếu bạn chỉ đưa compilenó vào mã byte trước đó thay vì chuyển nó dưới dạng một chuỗi:

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

hoạt động mà không có vấn đề, mặc dù mã được biên dịch có chứa các câu lệnh. Nó vẫn trả về None, vì đó là giá trị trả về của đối tượng mã được trả về compile.

Trong 'eval'chế độ (và do đó với evalhàm nếu một chuỗi được truyền vào), compilesẽ xuất hiện một ngoại lệ nếu mã nguồn chứa các câu lệnh hoặc bất cứ thứ gì khác ngoài một biểu thức:

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

Câu trả lời dài hơn, còn gọi là chi tiết đẫm máu

execeval

Các execchức năng (đó là một tuyên bố bằng Python 2 ) được sử dụng để thực hiện một tuyên bố động tạo hoặc chương trình:

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

Các evalchức năng hoạt động tương tự đối với một biểu thức duy nhất , trả về giá trị của biểu thức:

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

execevalcả hai chấp nhận các chương trình / biểu thức để được chạy hoặc là một str, unicodehoặc bytesđối tượng có chứa mã nguồn, hoặc như là một codeđối tượng , trong đó có Python bytecode.

Nếu a str/ unicode/ byteschứa mã nguồn được truyền tới exec, nó hoạt động tương đương với:

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

evalhành vi tương tự như:

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

Vì tất cả các biểu thức có thể được sử dụng làm câu lệnh trong Python (chúng được gọi là các Exprnút trong ngữ pháp trừu tượng của Python ; điều ngược lại là không đúng), bạn luôn có thể sử dụng execnếu bạn không cần giá trị trả về. Điều đó có nghĩa là, bạn có thể sử dụng một trong hai eval('my_func(42)')hoặc exec('my_func(42)'), sự khác biệt là evaltrả về giá trị được trả về my_funcexecloại bỏ nó:

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

Trong số 2, chỉ execchấp nhận mã nguồn có chứa báo cáo, như def, for, while, import, hay class, báo cáo kết quả chuyển nhượng (aka a = 42), hoặc toàn bộ các chương trình:

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

Cả hai execevalchấp nhận 2 đối số vị trí bổ sung - globalslocals- đó là phạm vi biến toàn cục và biến cục bộ mà mã nhìn thấy. Những mặc định đến globals()locals()trong phạm vi mà gọi là exechay eval, nhưng bất kỳ từ điển có thể được sử dụng cho globalsvà bất kỳ mappingcho locals(bao gồm dicttất nhiên). Chúng không chỉ được sử dụng để hạn chế / sửa đổi các biến mà mã nhìn thấy, mà còn thường được sử dụng để nắm bắt các biến mà execmã uted tạo ra:

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

(Nếu bạn hiển thị giá trị của toàn bộ g, nó sẽ dài hơn nhiều, bởi vì execevalthêm mô-đun tích __builtins__hợp vào tự động nếu nó bị thiếu).

Trong Python 2, cú pháp chính thức cho execcâu lệnh thực sự exec code in globals, locals, như trong

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

Tuy nhiên, cú pháp thay thế exec(code, globals, locals)luôn luôn được chấp nhận (xem bên dưới).

compile

Các compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)built-in có thể được sử dụng để tăng tốc độ lời gọi lặp đi lặp lại của cùng mã với exechoặc evalbằng cách biên dịch các nguồn vào một codeđối tượng trước đó. Các modeđiều khiển thông số các loại đoạn mã compilechức năng chấp nhận và loại bytecode nó tạo ra. Các lựa chọn là 'eval', 'exec''single':

  • 'eval'chế độ mong đợi một biểu thức duy nhất và sẽ tạo ra mã byte mà khi chạy sẽ trả về giá trị của biểu thức đó :

    >>> 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'chấp nhận bất kỳ loại cấu trúc python nào từ các biểu thức đơn lẻ đến toàn bộ các mô-đun mã và thực thi chúng như thể chúng là các câu lệnh cấp cao nhất của mô-đun. Đối tượng mã trả về 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'là một hình thức hạn chế về 'exec'mà chấp nhận một mã nguồn có chứa một đơn tuyên bố (hoặc nhiều báo cáo phân cách bằng ;) nếu báo cáo kết quả cuối cùng là một tuyên bố ngôn luận, các bytecode kết quả cũng in các reprgiá trị mà biểu hiện cho đầu ra tiêu chuẩn (!) .

    Một if- elif- elsedây chuyền, một vòng lặp với else, và tryvới nó except, elsefinallykhối được coi là một tuyên bố duy nhất.

    Một đoạn nguồn chứa 2 câu lệnh cấp cao nhất là một lỗi cho 'single', ngoại trừ trong Python 2 có một lỗi đôi khi cho phép nhiều câu lệnh toplevel trong mã; chỉ cái đầu tiên được biên soạn; phần còn lại bị bỏ qua:

    Trong Python 2.7.8:

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

    Và trong 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

    Điều này rất hữu ích để tạo các vỏ Python tương tác. Tuy nhiên, giá trị của biểu thức không được trả về , ngay cả khi bạn evalmã kết quả.

Do đó, sự khác biệt lớn nhất execevalthực sự đến từ compilechức năng và các chế độ của nó.


Ngoài việc biên dịch mã nguồn thành mã byte, compilehỗ trợ biên dịch các cây cú pháp trừu tượng (các phân tích cú pháp của mã Python) thành codecác đối tượng; và mã nguồn thành các cây cú pháp trừu tượng ( ast.parseđược viết bằng Python và chỉ gọi compile(source, filename, mode, PyCF_ONLY_AST)); chúng được sử dụng ví dụ để sửa đổi mã nguồn một cách nhanh chóng và cũng để tạo mã động, vì thường dễ xử lý mã như một cây nút thay vì các dòng văn bản trong các trường hợp phức tạp.


Mặc dù evalchỉ cho phép bạn đánh giá một chuỗi chứa một biểu thức, bạn có thể evaltoàn bộ một câu lệnh hoặc thậm chí toàn bộ một mô-đun đã được compiled vào mã byte; đó là, với Python 2, printlà một câu lệnh và không thể được evaldẫn trực tiếp:

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

compilenó với 'exec'chế độ thành một codeđối tượng và bạn có thể làm eval điều đó ; các evalchức năng sẽ trở lại 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

Nếu một người nhìn vào evalexecmã nguồn trong CPython 3, điều này rất rõ ràng; cả hai đều gọi PyEval_EvalCodevới cùng một đối số, sự khác biệt duy nhất là exectrả về rõ ràngNone .

Cú pháp khác nhau execgiữa Python 2 và Python 3

Một trong những khác biệt chính trong Python 2 là đó execlà một câu lệnh và evallà một hàm dựng sẵn (cả hai đều là các hàm dựng sẵn trong Python 3). Một thực tế nổi tiếng là cú pháp chính thức của execPython 2 là exec code [in globals[, locals]].

Không giống như phần lớn các hướng dẫn chuyển từ 2 đến 3 của Python dường như đề xuất , câu lệnh trong CPython 2 cũng có thể được sử dụng với cú pháp trông giống hệt như lời gọi hàm trong Python 3. Lý do là Python 0.9.9 đã được tích hợp sẵn trong chức năng! Và chức năng tích hợp đó đã được thay thế bằng câu lệnh ở đâu đó trước khi phát hành Python 1.0 . exec execexec(code, globals, locals)exec

Vì mong muốn không phá vỡ khả năng tương thích ngược với Python 0.9.9, Guido van Rossum đã thêm một hack tương thích vào năm 1993 : nếu đó codelà một tuple có độ dài 2 hoặc 3 globalslocalskhông được chuyển vào exectuyên bố theo cách khác, thì codenó sẽ được giải thích như thể phần tử thứ 2 và thứ 3 của tuple là globalslocalstương ứng. Việc hack tương thích không được đề cập ngay cả trong tài liệu Python 1.4 (phiên bản sớm nhất có sẵn trực tuyến) ; và do đó không được biết đến bởi nhiều tác giả của các hướng dẫn và công cụ porting, cho đến khi nó được ghi lại vào tháng 11 năm 2012 :

Biểu thức đầu tiên cũng có thể là một tuple có độ dài 2 hoặc 3. Trong trường hợp này, các phần tùy chọn phải được bỏ qua. Hình thức exec(expr, globals)tương đương với exec expr in globals, trong khi hình thức exec(expr, globals, locals)tương đương exec expr in globals, locals. Dạng tuple execcung cấp khả năng tương thích với Python 3, trong đó execlà một hàm chứ không phải là một câu lệnh.

Có, trong CPython 2.7, nó được gọi một cách cẩn thận là một tùy chọn tương thích về phía trước (tại sao mọi người lại nhầm lẫn rằng có một tùy chọn tương thích ngược), khi nó thực sự đã ở đó để tương thích ngược trong hai thập kỷ .

Do đó, trong khi đó execlà một câu lệnh trong Python 1 và Python 2 và một hàm dựng sẵn trong Python 3 và Python 0.9.9,

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

đã có hành vi giống hệt nhau trong mọi phiên bản Python được phát hành rộng rãi từ trước đến nay; và hoạt động trong Jython 2.5.2, PyPy 2.3.1 (Python 2.7.6) và IronPython 2.6.1 (kudos đối với họ theo sát hành vi không có giấy tờ của CPython).

Điều bạn không thể làm trong Pythons 1.0 - 2.7 với hack tương thích của nó, là lưu trữ giá trị trả về của execmột biến:

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

(điều này sẽ không hữu ích trong Python 3, như execluôn luôn trả về None) hoặc chuyển tham chiếu đến exec:

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

Mà một mô hình mà ai đó thực sự có thể đã sử dụng, mặc dù không thể;

Hoặc sử dụng nó trong một danh sách hiểu:

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

đó là lạm dụng việc hiểu danh sách (sử dụng forvòng lặp thay thế!).


[i for i in globals().values() if hasattr(i, '__call__')][0]một tuyên bố hoặc biểu hiện? Nếu đó là một biểu thức, tại sao tôi không thể sử dụng nó với @tư cách là người trang trí?
Mario

nó là một biểu thức 42cũng là một biểu thức và bạn không thể sử dụng nó @như một trang trí.
Antti Haapala

Cú pháp trang trí là decorator ::= "@" dotted_name ["(" [parameter_list [","]] ")"] NEWLINE; tức là bạn không thể sử dụng các biểu thức tùy ý làm trang trí, CHỈ một mã định danh (có thể được chấm), theo sau là các đối số cuộc gọi tùy chọn.
Antti Haapala

1
Không phải bất cứ điều gì có thể được đặt ở phía bên phải của một bài tập và vẫn biên dịch là một biểu thức. Ví dụ, a = b = clà một tuyên bố hoàn toàn hợp lệ, cũng như phía bên tay phải của nó b = c- không phải là một biểu thức.
Tom

194
  1. execkhông phải là biểu thức: một câu lệnh trong Python 2.x và một hàm trong Python 3.x. Nó biên dịch và ngay lập tức đánh giá một câu lệnh hoặc tập hợp câu lệnh chứa trong một chuỗi. Thí dụ:

    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. evallà một hàm dựng sẵn ( không phải là một câu lệnh), để đánh giá một biểu thức và trả về giá trị mà biểu thức tạo ra. Thí dụ:

    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. compilelà một phiên bản cấp thấp hơn của execeval. Nó không thực thi hoặc đánh giá các câu lệnh hoặc biểu thức của bạn, nhưng trả về một đối tượng mã có thể làm điều đó. Các chế độ như sau:

    1. compile(string, '', 'eval')trả về đối tượng mã sẽ được thực thi nếu bạn đã thực hiện eval(string). Lưu ý rằng bạn không thể sử dụng các câu lệnh trong chế độ này; chỉ một biểu thức (đơn) là hợp lệ.
    2. compile(string, '', 'exec')trả về đối tượng mã sẽ được thực thi nếu bạn đã thực hiện exec(string). Bạn có thể sử dụng bất kỳ số lượng báo cáo ở đây.
    3. compile(string, '', 'single')giống như execchế độ, nhưng nó sẽ bỏ qua mọi thứ trừ câu lệnh đầu tiên. Lưu ý rằng một câu lệnh if/ elsevới kết quả của nó được coi là một câu lệnh.

40
Trong Python 3, exec()thực tế bây giờ là một hàm.
Tim Pietzcker

2
Vì (như bạn chỉ ra), execlà một tuyên bố trong phiên bản bạn đang nhắm mục tiêu, nên việc bao gồm các parens đó là lừa đảo và nếu bạn cố gắng sử dụng in globals, locals, cũng có lỗi.
Mike Graham

2
@MikeGraham exec hỗ trợ dấu ngoặc đơn và chức năng như lời gọi trong Python 2 .
Antti Haapala

2
@AnttiHaapala trong chừng mực như bài tập 'hỗ trợ dấu ngoặc đơn' vì bạn có thể làm x = (y), điều đó có thể đúng. Một chức năng chuyển đổi câu lệnh khác là print; so sánh kết quả của print(1, 2, 3)python 2 và 3.
habnabit

1
@habnabit không như thế. Xin vui lòng đọc phần dưới của câu trả lời của tôi ở đây và ngạc nhiên.
Antti Haapala

50

exec là cho tuyên bố và không trả lại bất cứ điều gì. eval là cho biểu thức và trả về giá trị của biểu thức.

biểu thức có nghĩa là "một cái gì đó" trong khi tuyên bố có nghĩa là "làm một cái gì đó".


9
Đoạn thứ hai là một sự đơn giản hóa đến mức nó gần như trở thành một lời nói dối, một biểu thức rất có thể làm một cái gì đó nếu nó bao gồm một lời gọi hàm.
Antti Haapala
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.