ฉันได้ดูการประเมินรหัส Python แบบไดนามิกและพบกับeval()และcompile()ฟังก์ชั่นและexecคำสั่ง
ใครช่วยอธิบายความแตกต่างระหว่างevalและกับexecและโหมดที่แตกต่างกันได้compile()อย่างไร
ฉันได้ดูการประเมินรหัส Python แบบไดนามิกและพบกับeval()และcompile()ฟังก์ชั่นและexecคำสั่ง
ใครช่วยอธิบายความแตกต่างระหว่างevalและกับexecและโหมดที่แตกต่างกันได้compile()อย่างไร
คำตอบ:
โดยทั่วไปevalจะใช้ในการEVAL uate แสดงออกหลามเดียวที่สร้างแบบไดนามิกและexecจะใช้ในการบริหาร ute สร้างแบบไดนามิกรหัสหลามเฉพาะสำหรับผลข้างเคียง
evalและexecมีความแตกต่างสองประการนี้:
evalยอมรับเฉพาะการแสดงออกเดียว , execสามารถใช้การป้องกันรหัสที่มีงบหลาม: ลูปtry: except:, classและฟังก์ชั่น / วิธีdefinitions และอื่น ๆ
การแสดงออกใน Python เป็นสิ่งที่คุณสามารถมีค่าในการกำหนดตัวแปร:
a_variable = (anything you can put within these parentheses is an expression)eval ส่งคืนค่าของนิพจน์ที่กำหนดในขณะที่execละเว้นค่าส่งคืนจากรหัสของมันและส่งคืนเสมอNone(ใน Python 2 มันเป็นคำสั่งและไม่สามารถใช้เป็นนิพจน์ได้
ในรุ่น 1.0 - 2.7 execเป็นคำสั่งเนื่องจาก CPython จำเป็นต้องผลิตวัตถุรหัสชนิดอื่นสำหรับฟังก์ชั่นที่ใช้execสำหรับผลข้างเคียงภายในฟังก์ชั่น
ใน Python 3 execเป็นฟังก์ชั่น การใช้งานจะไม่มีผลกับการรวบรวมไบต์ของฟังก์ชั่นที่มีการใช้งาน
ดังนั้นโดยทั่วไป:
>>> 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ใน'exec'โหมดการรวบรวมจำนวนของงบใด ๆ ลงใน bytecode ที่ปริยายเสมอผลตอบแทนNoneในขณะที่ใน'eval'โหมดมันรวบรวมเดียวแสดงออกเข้า bytecode ว่าผลตอบแทนที่คุ้มค่าของการแสดงออกว่า
>>> 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
ใน'eval'โหมด (และด้วยevalฟังก์ชั่นหากมีการส่งผ่านสตริง) การcompileยกข้อยกเว้นหากซอร์สโค้ดมีคำสั่งหรือสิ่งอื่นนอกเหนือจากการแสดงออกเดียว:
>>> 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
อันที่จริงคำสั่ง"EVAL ยอมรับเฉพาะการแสดงออกเดียว"ใช้เฉพาะเมื่อสตริง (ซึ่งมีงูหลามรหัสที่มา ) evalจะถูกส่งผ่านไปยัง จากนั้นจะถูกรวบรวมภายในเพื่อ bytecode โดยใช้compile(source, '<string>', 'eval')นี่คือสิ่งที่แตกต่างมาจากจริงๆ
ถ้าcodeวัตถุ (ซึ่งมีงูหลามbytecode ) จะถูกส่งผ่านไปยังexecหรือeval, พวกเขาประพฤติเหมือนกันยกเว้นความจริงที่ว่าexecไม่สนใจค่าตอบแทนที่ยังคงกลับมาNoneเสมอ ดังนั้นจึงเป็นไปได้ที่จะใช้evalเพื่อดำเนินการบางอย่างที่มีคำสั่งถ้าคุณเพียงแค่compileใส่ไว้ใน bytecode ก่อนแทนที่จะส่งผ่านเป็นสตริง:
>>> eval(compile('if 1: print("Hello")', '<string>', 'exec'))
Hello
>>>
ทำงานได้โดยไม่มีปัญหาแม้ว่ารหัสที่รวบรวมมีคำสั่ง มันก็ยังคงให้ผลตอบแทนเพราะนั่นคือค่าตอบแทนของวัตถุรหัสกลับมาจากNonecompile
ใน'eval'โหมด (และด้วยevalฟังก์ชั่นหากมีการส่งผ่านสตริง) การcompileยกข้อยกเว้นหากซอร์สโค้ดมีคำสั่งหรือสิ่งอื่นนอกเหนือจากการแสดงออกเดียว:
>>> 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 และ evalexecฟังก์ชั่น (ซึ่งเป็นคำสั่งในหลาม 2 ) ถูกนำมาใช้สำหรับการดำเนินการคำสั่งที่สร้างขึ้นแบบไดนามิกหรือโปรแกรม:
>>> program = '''
for i in range(3):
print("Python is cool")
'''
>>> exec(program)
Python is cool
Python is cool
Python is cool
>>>
evalฟังก์ชั่นไม่เหมือนกันสำหรับการแสดงออกเดียว , และผลตอบแทนที่คุ้มค่าของการแสดงออกนี้:
>>> a = 2
>>> my_calculation = '42 * a'
>>> result = eval(my_calculation)
>>> result
84
execและevalทั้งสองยอมรับโปรแกรม / การแสดงออกที่จะทำงานไม่ว่าจะเป็นstr, unicodeหรือbytesวัตถุที่มีรหัสที่มาหรือเป็นcodeวัตถุที่มีงูหลาม bytecode
หากstr/ unicode/ bytesบรรจุซอร์สโค้ดถูกส่งไปที่execมันจะทำงานเทียบเท่ากับ:
exec(compile(source, '<string>', 'exec'))
และevalมีพฤติกรรมเทียบเท่ากับ:
eval(compile(source, '<string>', 'eval'))
เนื่องจากนิพจน์ทั้งหมดสามารถใช้เป็นคำสั่งใน Python (สิ่งเหล่านี้เรียกว่าExprโหนดในไวยากรณ์ของ Python นามธรรมตรงกันข้ามไม่เป็นความจริง) คุณสามารถใช้execหากคุณไม่ต้องการค่าส่งคืน กล่าวคือคุณสามารถใช้อย่างใดอย่างหนึ่งeval('my_func(42)')หรือexec('my_func(42)')ความแตกต่างที่evalส่งกลับค่าที่ส่งคืนโดยmy_funcและexecละทิ้งมัน:
>>> 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 เพียงexecยอมรับรหัสที่มาที่มีงบเช่นdef, for, while, importหรือclassคำสั่งที่ได้รับมอบหมาย (aka a = 42) หรือโปรแกรมทั้งหมด:
>>> 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
ทั้งสองexecและevalยอมรับข้อโต้แย้งตำแหน่งเพิ่มเติม 2 ข้อ - globalsและlocals- ซึ่งเป็นขอบเขตโกลบอลและโลคัลตัวแปรที่โค้ดเห็น ค่าเริ่มต้นเหล่านี้เป็นglobals()และlocals()ภายในขอบเขตที่เรียกว่าexecหรือevalแต่พจนานุกรมใด ๆ สามารถใช้สำหรับglobalsและใด ๆmappingสำหรับlocals(รวมถึงdictแน่นอน) สิ่งเหล่านี้สามารถใช้เพื่อ จำกัด / แก้ไขตัวแปรที่โค้ดมองเห็น แต่มักจะใช้สำหรับการจับตัวแปรที่execโค้ด uted สร้างขึ้น:
>>> g = dict()
>>> l = dict()
>>> exec('global a; a, b = 123, 42', g, l)
>>> g['a']
123
>>> l
{'b': 42}
(ถ้าคุณแสดงค่าของทั้งหมดgมันจะนานกว่าเพราะexecและevalเพิ่มโมดูลในตัวเป็น__builtins__ไปยัง globals โดยอัตโนมัติหากมันหายไป)
ใน Python 2 ไวยากรณ์อย่างเป็นทางการสำหรับexecคำสั่งนั้นเป็นจริงexec code in globals, localsตามที่
>>> exec 'global a; a, b = 123, 42' in g, l
อย่างไรก็ตามไวยากรณ์อื่นexec(code, globals, locals)ได้รับการยอมรับเสมอเช่นกัน (ดูด้านล่าง)
compilecompile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)ในตัวสามารถนำมาใช้เพื่อเพิ่มความเร็วในการเรียกซ้ำของรหัสเดียวกันกับexecหรือevalโดยการรวบรวมแหล่งที่มาเป็นcodeวัตถุก่อน modeควบคุมพารามิเตอร์ชนิดของรหัส fragment compileฟังก์ชั่นยอมรับและชนิดของ bytecode มันผลิต ทางเลือกที่มี'eval', 'exec'และ'single':
'eval'โหมดคาดหวังว่าจะแสดงออกเพียงครั้งเดียวและจะผลิต bytecode ว่าเมื่อทำงานจะคืนค่าของการแสดงออกที่ :
>>> 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'ยอมรับไพ ธ อนชนิดใดก็ได้ที่สร้างจากนิพจน์เดียวไปยังโมดูลทั้งหมดของโค้ดและดำเนินการราวกับว่ามันเป็นประโยคระดับบนสุดของโมดูล วัตถุโค้ดส่งคืน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'เป็นรูปแบบจำนวน จำกัด'exec'ซึ่งรับรหัสที่มาที่มีเพียงครั้งเดียวคำสั่ง (หรืองบหลายคั่นด้วย;) ถ้ามีคำสั่งที่ผ่านมาเป็นคำสั่งการแสดงออกที่ bytecode ส่งผลให้ยังพิมพ์reprของมูลค่าของการแสดงออกที่ไปออกมาตรฐาน (!)
if- elif- elseห่วงโซ่ห่วงด้วยelseและtryด้วยexcept, elseและfinallyบล็อกถือเป็นคำเดียว
แฟรกเมนต์ต้นทางมี 2 ประโยคระดับบนสุดเป็นข้อผิดพลาดสำหรับ'single'ยกเว้นใน Python 2 มีข้อบกพร่องที่บางครั้งอนุญาตให้มีคำสั่งระดับบนสุดจำนวนมากในโค้ด รวบรวมเฉพาะอันแรกเท่านั้น ส่วนที่เหลือจะถูกละเว้น:
ใน Python 2.7.8:
>>> exec(compile('a = 5\na = 6', '<string>', 'single'))
>>> a
5
และใน 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
สิ่งนี้มีประโยชน์มากสำหรับการสร้างเชลล์ Python แบบโต้ตอบ อย่างไรก็ตามค่าของการแสดงออกจะไม่ถูกส่งกลับแม้ว่าคุณจะได้evalรับรหัส
ความแตกต่างที่ยิ่งใหญ่ที่สุดของexecและevalมาจากcompileฟังก์ชั่นและโหมดของมัน
นอกจากการคอมไพล์ซอร์สโค้ดไปยัง bytecode แล้วยังcompileรองรับการคอมไพล์ไวยากรณ์แบบนามธรรม (ต้นไม้แยกวิเคราะห์ของโค้ดไพ ธ อน) ลงในcodeวัตถุ และซอร์สโค้ดลงในต้นไม้ไวยากรณ์ที่เป็นนามธรรม (ast.parseเขียนใน Python และเพิ่งเรียกcompile(source, filename, mode, PyCF_ONLY_AST)); สิ่งเหล่านี้ใช้สำหรับการปรับเปลี่ยนซอร์สโค้ดได้ทันทีและสำหรับการสร้างโค้ดแบบไดนามิกเนื่องจากมักจะง่ายต่อการจัดการโค้ดเป็นแผนผังของโหนดแทนบรรทัดข้อความในกรณีที่ซับซ้อน
แม้ว่าevalจะอนุญาตให้คุณประเมินสตริงที่มีนิพจน์เดียว แต่คุณสามารถevalใช้ข้อความทั้งหมดหรือแม้กระทั่งโมดูลทั้งหมดที่เคยเป็นมาได้compileเข้าสู่ bytecode; นั่นคือด้วย Python 2 printเป็นคำสั่งและไม่สามารถevalนำโดยตรง:
>>> 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ด้วย'exec'โหมดเป็นcodeวัตถุและคุณก็สามารถeval มัน ; ฟังก์ชั่นจะกลับมาevalNone
>>> 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
หากมีการตรวจสอบevalและexecซอร์สโค้ดใน CPython 3 สิ่งนี้จะชัดเจนมาก พวกเขาทั้งคู่โทรPyEval_EvalCodeมีการขัดแย้งกันความแตกต่างเพียงอย่างเดียวว่าอย่างชัดเจนผลตอบแทนexecNone
execระหว่าง Python 2 และ Python 3หนึ่งในความแตกต่างที่สำคัญใน Python 2ก็execคือคำสั่งและevalเป็นฟังก์ชันในตัว (ทั้งสองเป็นฟังก์ชันในตัวใน Python 3) มันเป็นความจริงที่รู้จักกันดีว่าไวยากรณ์อย่างเป็นทางการของexecในหลาม exec code [in globals[, locals]]2
ซึ่งแตกต่างจากคนส่วนใหญ่ของงูหลาม 2 ไป 3 porting คู่มือ ดูเหมือน จะแนะนำที่execคำสั่งใน CPython 2 ยังสามารถใช้กับไวยากรณ์ที่มีลักษณะ ตรงเช่นexecภาวนาฟังก์ชั่นในหลาม 3. เหตุผลก็คือว่างูหลาม 0.9.9 มีexec(code, globals, locals)จั ในฟังก์ชั่น! และฟังก์ชั่นที่ถูกแทนที่ด้วยexecคำสั่งที่ไหนสักแห่งก่อนที่จะปล่อยงูหลาม 1.0
เพราะมันเป็นที่น่าพอใจจะไม่ทำลายความเข้ากันได้กับงูหลาม 0.9.9, กุยรถตู้ซัมเพิ่มสับการทำงานร่วมกันในปี 1993 : ถ้าcodeเป็น tuple ของความยาว 2 หรือ 3 และglobalsและlocalsไม่ได้ผ่านเข้าสู่execคำสั่งมิฉะนั้นcodeจะถูกตีความว่า ราวกับว่าองค์ประกอบที่ 2 และ 3 ของ tuple เป็นglobalsและlocalsตามลำดับ การแฮ็คเข้ากันไม่ได้กล่าวถึงแม้ในเอกสาร Python 1.4 (เวอร์ชั่นที่เก่าที่สุดที่มีในระบบออนไลน์) ; และดังนั้นจึงไม่เป็นที่รู้จักของนักเขียนหลายคนเกี่ยวกับคำแนะนำการย้ายและเครื่องมือจนกว่าจะได้รับการบันทึกอีกครั้งในเดือนพฤศจิกายน 2012 :
นิพจน์แรกอาจเป็น tuple ที่มีความยาว 2 หรือ 3 ในกรณีนี้ชิ้นส่วนเสริมจะต้องถูกตัดออก แบบฟอร์มที่
exec(expr, globals)เทียบเท่ากับexec expr in globalsในขณะที่รูปแบบเทียบเท่ากับexec(expr, globals, locals)exec expr in globals, localsรูปแบบ tuple ของexecให้ความเข้ากันได้กับ Python 3 โดยที่execเป็นฟังก์ชันแทนที่จะเป็นคำสั่ง
ใช่ใน CPython 2.7 มันถูกอ้างถึงอย่างคล่องแคล่วว่าเป็นตัวเลือกความเข้ากันได้ไปข้างหน้า (ทำไมผู้คนสับสนว่ามีตัวเลือกความเข้ากันได้แบบย้อนกลับ) เมื่อจริง ๆ แล้วมันอยู่ที่นั่น ย้อนกลับเข้ากันได้สำหรับสองทศวรรษ
ดังนั้นในขณะที่ execเป็นคำสั่งใน Python 1 และ Python 2 และฟังก์ชั่นในตัวใน Python 3 และ Python 0.9.9
>>> exec("print(a)", globals(), {'a': 42})
42
มีพฤติกรรมที่เหมือนกันใน Python ทุกเวอร์ชันที่ออกวางตลาดอย่างกว้างขวาง และทำงานใน Jython 2.5.2, PyPy 2.3.1 (Python 2.7.6) และ IronPython 2.6.1 ด้วย (ขอชื่นชมพวกเขาหลังจากพฤติกรรมที่ไม่ได้บันทึกไว้ของ CPython อย่างใกล้ชิด)
สิ่งที่คุณไม่สามารถทำได้ใน Pythons 1.0 - 2.7 กับการแฮ็คเข้ากันได้คือการเก็บค่าส่งคืนของexecไว้ในตัวแปร:
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 เช่นเดียวกับexecส่งคืนเสมอNone) หรือผ่านการอ้างอิงถึงexec:
>>> call_later(exec, 'print(42)', delay=1000)
File "<stdin>", line 1
call_later(exec, 'print(42)', delay=1000)
^
SyntaxError: invalid syntax
ซึ่งเป็นรูปแบบที่บางคนอาจใช้งานจริง
หรือใช้ในรายการความเข้าใจ:
>>> [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
ซึ่งเป็นการละเมิดความเข้าใจในรายการ (ใช้forวนซ้ำแทน!)
42เป็นนิพจน์และคุณไม่สามารถใช้มัน@เป็นมัณฑนากรได้
decorator ::= "@" dotted_name ["(" [parameter_list [","]] ")"] NEWLINE ; เช่นคุณไม่สามารถใช้นิพจน์โดยพลการเป็นตัวตกแต่งได้เพียงตัวระบุ (อาจเป็นจุด) ตามด้วยอาร์กิวเมนต์ตัวเลือกการโทร
a = b = cเป็นข้อความที่ถูกต้องสมบูรณ์เช่นเดียวกับด้านขวาb = c- ซึ่งไม่ใช่การแสดงออก
execไม่ใช่นิพจน์: คำสั่งใน Python 2.x และฟังก์ชันใน Python 3.x มันรวบรวมและประเมินผลทันทีคำสั่งหรือชุดของคำสั่งที่มีอยู่ในสตริง ตัวอย่าง:
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เป็นฟังก์ชันในตัว ( ไม่ใช่ข้อความสั่ง) ซึ่งประเมินค่านิพจน์และส่งคืนค่าที่นิพจน์สร้าง ตัวอย่าง:
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เป็นรุ่นระดับที่ต่ำกว่าและexec evalมันไม่ได้ดำเนินการหรือประเมินผลคำสั่งหรือการแสดงออกของคุณ แต่ส่งกลับวัตถุรหัสที่สามารถทำได้ โหมดมีดังนี้:
compile(string, '', 'eval')eval(string)ผลตอบแทนที่ได้วัตถุรหัสที่จะได้รับการดำเนินการได้ที่คุณทำ โปรดทราบว่าคุณไม่สามารถใช้คำสั่งในโหมดนี้ นิพจน์ (เดี่ยว) เท่านั้นที่ถูกต้องcompile(string, '', 'exec')exec(string)ผลตอบแทนที่ได้วัตถุรหัสที่จะได้รับการดำเนินการได้ที่คุณทำ คุณสามารถใช้งบจำนวนเท่าใดก็ได้ที่นี่compile(string, '', 'single')เหมือนexecโหมด แต่มันจะไม่สนใจทุกอย่างยกเว้นสำหรับคำสั่งแรก โปรดทราบว่าif/elseคำสั่งพร้อมผลลัพธ์จะถือว่าเป็นคำสั่งเดียวexec()ตอนนี้เป็นฟังก์ชั่น
execเป็นคำสั่งในเวอร์ชันที่คุณกำหนดเป้าหมายมันเป็นการหลอกลวงที่จะรวม parens เหล่านั้นและหากคุณพยายามใช้in globals, localsมัน
exec สนับสนุนวงเล็บและฟังก์ชั่นเช่นการภาวนาในหลาม 2
x = (y)นั่นอาจเป็นจริง อีกฟังก์ชั่นเปิดใช้งานคือprint; เปรียบเทียบผลลัพธ์ของprint(1, 2, 3)ใน python 2 และ 3
exec เป็นคำสั่งและจะไม่ส่งคืนสิ่งใด eval ใช้สำหรับการแสดงออกและคืนค่าการแสดงออก
การแสดงออกหมายถึง "บางสิ่ง" ในขณะที่คำสั่งหมายถึง "ทำอะไรบางอย่าง"
[i for i in globals().values() if hasattr(i, '__call__')][0]คำสั่งหรือการแสดงออก? ถ้าเป็นการแสดงออกฉันจะใช้กับ@มัณฑนากรไม่ได้ทำไม