Tôi đã xem xét đánh giá động về mã Python, và tìm thấy các hàm eval()
và compile()
hàm và exec
câu lệnh.
Ai đó có thể vui lòng giải thích sự khác biệt giữa eval
và exec
, và làm thế nào các chế độ khác nhau compile()
phù hợp?
Tôi đã xem xét đánh giá động về mã Python, và tìm thấy các hàm eval()
và compile()
hàm và exec
câu lệnh.
Ai đó có thể vui lòng giải thích sự khác biệt giữa eval
và exec
, và làm thế nào các chế độ khác nhau compile()
phù hợp?
Câu trả lời:
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ó.
eval
và exec
có hai điểm khác biệt sau:
eval
chỉ chấp nhận một biểu thức duy nhất , exec
có thể mất một khối mã có báo cáo Python: vòng, try: except:
, class
và chức năng / phương pháp def
initions 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)
eval
trả về giá trị của biểu thức đã cho, trong khi exec
bỏ 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, exec
là 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 exec
cho các tác dụng phụ của nó bên trong hàm.
Trong Python 3, exec
là 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 compile
trong '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 eval
hàm nếu một chuỗi được truyền vào), compile
sẽ 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 exec
hoặc eval
, chúng sẽ hành xử giống hệt nhau , ngoại trừ thực tế là exec
bỏ qua giá trị trả về, vẫn None
luô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 compile
nó 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 eval
hàm nếu một chuỗi được truyền vào), compile
sẽ 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
exec
và eval
Các exec
chứ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 eval
chức năng hoạt động tương tự đối với một biểu thức duy nhất , và trả về giá trị của biểu thức:
>>> a = 2
>>> my_calculation = '42 * a'
>>> result = eval(my_calculation)
>>> result
84
exec
và eval
cả hai chấp nhận các chương trình / biểu thức để được chạy hoặc là một str
, unicode
hoặ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
/ bytes
chứa mã nguồn được truyền tới exec
, nó hoạt động tương đương với:
exec(compile(source, '<string>', 'exec'))
và eval
hà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 Expr
nú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 exec
nế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à eval
trả về giá trị được trả về my_func
và exec
loạ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ỉ exec
chấ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 exec
và eval
chấp nhận 2 đối số vị trí bổ sung - globals
và locals
- đó 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()
và locals()
trong phạm vi mà gọi là exec
hay eval
, nhưng bất kỳ từ điển có thể được sử dụng cho globals
và bất kỳ mapping
cho locals
(bao gồm dict
tấ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à exec
mã 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ì exec
và eval
thê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 exec
câ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 exec
hoặc eval
bằ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ã compile
chức năng chấp nhận và loại bytecode nó tạo ra. Các lựa chọn là 'eval'
, 'exec'
và '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 repr
giá trị mà biểu hiện cho đầu ra tiêu chuẩn (!) .
Một if
- elif
- else
dây chuyền, một vòng lặp với else
, và try
với nó except
, else
và finally
khố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 eval
mã kết quả.
Do đó, sự khác biệt lớn nhất exec
và eval
thực sự đến từ compile
chứ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, compile
hỗ 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 code
cá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ù eval
chỉ cho phép bạn đánh giá một chuỗi chứa một biểu thức, bạn có thể eval
toàn bộ một câu lệnh hoặc thậm chí toàn bộ một mô-đun đã được compile
d vào mã byte; đó là, với Python 2, print
là một câu lệnh và không thể được eval
dẫ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
compile
nó với 'exec'
chế độ thành một code
đối tượng và bạn có thể làm eval
điều đó ; các eval
chứ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 eval
và exec
mã nguồn trong CPython 3, điều này rất rõ ràng; cả hai đều gọi PyEval_EvalCode
với cùng một đối số, sự khác biệt duy nhất là exec
trả về rõ ràngNone
.
exec
giữa Python 2 và Python 3Một trong những khác biệt chính trong Python 2 là đó exec
là một câu lệnh và eval
là 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 exec
Python 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
exec
exec(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 đó code
là một tuple có độ dài 2 hoặc 3 globals
và locals
không được chuyển vào exec
tuyên bố theo cách khác, thì code
nó sẽ được giải thích như thể phần tử thứ 2 và thứ 3 của tuple là globals
và locals
tươ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ớiexec expr in globals
, trong khi hình thứcexec(expr, globals, locals)
tương đươngexec expr in globals, locals
. Dạng tupleexec
cung cấp khả năng tương thích với Python 3, trong đóexec
là 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 đó exec
là 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 exec
mộ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ư exec
luô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 for
vòng lặp thay thế!).
42
cũng là một biểu thức và bạn không thể sử dụng nó @
như một trang trí.
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.
a = b = c
là 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.
exec
khô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.
eval
là 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.
compile
là một phiên bản cấp thấp hơn của exec
và eval
. 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:
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ệ.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.compile(string, '', 'single')
giống như exec
chế độ, 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
/ else
với kết quả của nó được coi là một câu lệnh.exec()
thực tế bây giờ là một hàm.
exec
là 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.
exec
hỗ trợ dấu ngoặc đơn và chức năng như lời gọi trong Python 2 .
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.
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ì đó".
[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í?