文章大纲
- eval 函数简介 -- 简单字符代码的执行
- 调用样例
- exec 函数简介 -- 复杂多行代码的执行【无返回值】
- compile 函数简介 -- 调用eval 或者exec
- 调用样例
- exec 封装 执行上下文
- 命令行输入 并执行
- 参考文献
eval 函数简介 – 简单字符代码的执行
eval(expression[, globals[, locals]])
实参是一个字符串,以及可选的 globals 和 locals。globals 实参必须是一个字典。locals 可以是任何映射对象。
表达式解析参数 expression 并作为 Python 表达式进行求值(从技术上说是一个条件列表),采用 globals 和 locals 字典作为全局和局部命名空间。
如果存在 globals 字典,并且不包含 _builtins_ 键的值,则在解析 expression 之前会插入以该字符串为键以对内置模块 builtins 的字典的引用为值的项。 这样就可以在将 globals 传给 eval() 之前通过向其传入你自己的 _builtins_ 字典来控制可供被执行代码可以使用哪些内置模块。
如果 locals 字典被省略则它默认为 globals 字典。 如果两个字典都被省略,则将使用调用 eval() 的环境中的 globals 和 locals 来执行该表达式。 注意,eval() 无法访问闭包环境中的 嵌套作用域 (非局部变量)。
返回值就是表达式的求值结果。 语法错误将作为异常被报告。
调用样例
x = 1
eval('x+1')
# 输出为:
2
该函数还可用于执行任意代码对象(比如由 compile() 创建的对象)。 这时传入的是代码对象,而非一个字符串了。如果代码对象已用参数为 mode 的 ‘exec’ 进行了编译,那么 eval() 的返回值将为 None。
提示: exec() 函数支持语句的动态执行。 globals() 和 locals() 函数分别返回当前的全局和本地字典,可供传给 eval() 或 exec() 使用。
如果给出的源数据是个字符串,那么其前后的空格和制表符将被剔除。
另外可以参阅 ast.literal_eval(),该函数可以安全执行仅包含文字的表达式字符串。
引发一个 审计事件 exec 附带参数 code_object。
exec 函数简介 – 复杂多行代码的执行【无返回值】
exec(object[, globals[, locals]])
This function supports dynamic execution of Python code. object must be either a string or a code object.
If it is a string, the string is parsed as a suite of Python statements which is then executed (unless a syntax error occurs). 1 If it is a code object, it is simply executed. In all cases, the code that’s executed is expected to be valid as file input (see the section 文件输入 in the Reference Manual).
Be aware that the nonlocal, yield, and return statements may not be used outside of function definitions even within the context of code passed to the exec() function. The return value is None.
– 这一段主要说的就是没有返回值
无论在什么情况下,如果省略了可选部分,代码将运行于当前作用域中。如果只提供了 globals,则必须为字典对象(而不能是字典的子类),同时用于存放全局变量和局部变量。如果提供了 globals 和 locals,则将分别用于全局变量和局部变量。locals 可以是任意字典映射对象。请记住,在模块级别,globals 和 locals 是同一个字典。如果 exec 获得两个独立的对象作为 globals 和 locals,代码执行起来就像嵌入到某个类定义中一样。
如果 globals 字典不包含 _builtins_ 键值,则将为该键插入对内建 builtins 模块字典的引用。因此,在将执行的代码传递给 exec() 之前,可以通过将自己的 _builtins_ 字典插入到 globals 中来控制可以使用哪些内置代码。
引发一个 审计事件 exec 附带参数 code_object。
注解 内置 globals() 和 locals() 函数各自返回当前的全局和本地字典,因此可以将它们传递给 exec() 的第二个和第三个实参。
注解 默认情况下,locals 的行为如下面 locals() 函数描述的一样:不要试图改变默认的 locals 字典。如果您想在 exec() 函数返回时知道代码对 locals 的变动,请明确地传递 locals 字典。
compile 函数简介 – 调用eval 或者exec
compile(source, filename, mode, flags=0, dont_inherit=False, optimize=- 1)
参数
source – 字符串或者AST(Abstract Syntax Trees)对象。。
filename – 代码文件名称,如果不是从文件读取代码则传递一些可辨认的值。
mode – 指定编译代码的种类。可以指定为 exec, eval, single。
flags – 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。。
flags和dont_inherit是用来控制编译源码时的标志
将 source 编译成代码或 AST 对象。代码对象可以被 exec() 或 eval() 执行。source 可以是常规的字符串、字节字符串,或者 AST 对象。参见 ast 模块的文档了解如何使用 AST 对象。
filename 实参需要是代码读取的文件名;如果代码不需要从文件中读取,可以传入一些可辨识的值(经常会使用 ‘<string>’)。
mode 实参指定了编译代码必须用的模式。如果 source 是语句序列,可以是 ‘exec’;如果是单一表达式,可以是 ‘eval’;如果是单个交互式语句,可以是 ‘single’。(在最后一种情况下,如果表达式执行结果不是 None 将会被打印出来。)
可选参数 flags 和 dont_inherit 控制应当激活哪个 编译器选项 以及应当允许哪个 future 特性。 如果两者都未提供 (或都为零) 则代码会应用与调用 compile() 的代码相同的旗标来编译。 如果给出了 flags 参数而未给出 dont_inherit (或者为零) 则会在无论如何都将被使用的旗标之外还会额外使用 flags 参数所指定的编译器选项和 future 语句。 如果 dont_inherit 为非零整数,则只使用 flags 参数 – 外围代码中的旗标 (future 特性和编译器选项) 会被忽略。
编译器选项和 future 语句是由比特位来指明的。 比特位可以通过一起按位 OR 来指明多个选项。 指明特定 future 特性所需的比特位可以在 future 模块的 Feature 实例的 compiler_flag 属性中找到。 编译器旗标 可以在 ast 模块中查找带有 PyCF 前缀的名称。
optimize 实参指定编译器的优化级别;
默认值 -1 选择与解释器的 -O 选项相同的优化级别。
显式级别为 0 (没有优化;debug 为真)、1 (断言被删除, debug 为假)或 2 (文档字符串也被删除)。
如果编译的源码不合法,此函数会触发 SyntaxError 异常;如果源码包含 null 字节,则会触发 ValueError 异常。
如果您想分析 Python 代码的 AST 表示,请参阅 ast.parse()。
引发一个 审计事件 compile 附带参数 source, filename。
注解 在 ‘single’ 或 ‘eval’ 模式编译多行代码字符串时,输入必须以至少一个换行符结尾。 这使 code 模块更容易检测语句的完整性。
警告 在将足够大或者足够复杂的字符串编译成 AST 对象时,Python 解释器有可能因为 Python AST 编译器的栈深度限制而崩溃。
在 3.2 版更改: Windows 和 Mac 的换行符均可使用。而且在 ‘exec’ 模式下的输入不必再以换行符结尾了。另增加了 optimize 参数。
在 3.5 版更改: 之前 source 中包含 null 字节的话会触发 TypeError 异常。
3.8 新版功能: ast.PyCF_ALLOW_TOP_LEVEL_AWAIT 现在可在旗标中传入以启用对最高层级 await, async for 和 async with 的支持。
调用样例
C:\Users\season>python
Python 3.8.8 (default, Apr 13 2021, 15:08:03) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> str = "for i in range(0,10): print(i)"
>>> c = compile(str,'','exec') # 编译为字节代码对象
>>> c
<code object <module> at 0x000001C0B4643500, file "", line 1>
>>> exec(c)
0
1
2
3
4
5
6
7
8
9
>>> str = "3 * 4 + 5"
>>> a = compile(str,'','eval')
>>> eval(a)
17
exec 封装 执行上下文
有了上面的例子,我们使用exec 简单封装一个执行上下文
样例代码如下
执行路径, main.py -->> main() – >> exec(code_str)-- >> 初始化 myUserCustom – >> myUserCustom 的run () 方法
可见,由于exec 执行的代码是包含上下文的,所以 如果是执行过程中传入的内容,具有很大的动态特性。另外要注意使用exec函数的安全性,如传入 rm -rf 的可能性。
# -*- coding: utf-8 -*-
import sys
code_str = \
'''
path=r'123'
myUserCustom(path).run()
'''
code_add = \
'''
print(path)
'''
class myUserCustom():
def __init__(self, code_str):
print("初始化")
self.code_str = code_str
def list_all_member(self):
for name, value in vars(self).items():
print('%s=%s' % (name, value))
def exec_code(self,str_code):
exec(str_code)
def hello(self,test_str):
#print(test_str)
self.code_str = test_str+code_add
def run(self):
self.hello(self.code_str)
self.list_all_member()
print("执行字符串代码")
self.exec_code(self.code_str)
if __name__ == '__main__':
exec(code_str)
在idea 直接执行后的输出:
初始化
code_str=123
print(path)
执行字符串代码
123
命令行输入 并执行
这次我们修改 code_str , 并加入获取命令行参数的sys 模块
# -*- coding: utf-8 -*-
import sys
code_str = \
'''
path=r'123'
mytest=myUserCustom(path)
mytest.run()
'''
code_add = \
'''
print(path)
'''
class myUserCustom():
def __init__(self, code_str):
print("初始化")
self.code_str = code_str
def list_all_member(self):
for name, value in vars(self).items():
print('%s=%s' % (name, value))
def exec_code(self,str_code):
exec(str_code)
def hello(self,test_str):
#print(test_str)
self.code_str = test_str+code_add
def run(self):
self.hello(self.code_str)
self.list_all_member()
print("执行字符串代码")
self.exec_code(self.code_str)
if __name__ == '__main__':
exec(code_str)
temp_code = sys.argv[1]
print(temp_code)
mytest.exec_code(temp_code)
在命令行执行后输出: 注意以下两点:
- 和直接在idea执行的区别,看到了吗,在当前的python 脚本中,上下文保存了字符串中的对象:mytest
- 特别注意输入命令行参数的时候,要采用python 的语法规则。但是python对与缩进等的要求比较严格,命令行传参,比较长的函数,就会出错,怎么办呢,可以使用base64 编码后,在脚本里面进行解码的方式进行处理。
PS D:\code\python\LocalsparkAndPandas> python .\localTestExec.py "print('hello')"
初始化
code_str=123
print(path)
执行字符串代码
123
print('hello')
hello