安全评估AST节点,无副作用
项目描述
pure_eval
这是一个Python包,允许您安全地评估某些AST节点,而不会触发可能产生不受欢迎副作用的任意代码。
它可以从PyPI安装
pip install pure_eval
为了展示用法,假设我们有一个如下定义的对象
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
print("Calculating area...")
return self.width * self.height
rect = Rectangle(3, 5)
给定 rect
对象,我们想要评估此源代码中可以评估的所有表达式
source = "(rect.width, rect.height, rect.area)"
这个库与AST一起工作,因此让我们解析源代码并查看内部
import ast
tree = ast.parse(source)
the_tuple = tree.body[0].value
for node in the_tuple.elts:
print(ast.dump(node))
输出
Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load())
Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load())
Attribute(value=Name(id='rect', ctx=Load()), attr='area', ctx=Load())
现在实际使用这个库。首先构建一个评估器
from pure_eval import Evaluator
evaluator = Evaluator({"rect": rect})
Evaluator
的参数应是从变量名到其值的映射。或者如果您可以访问定义 rect
的堆栈帧,您可以使用以下方式
evaluator = Evaluator.from_frame(frame)
现在要评估一些节点,使用 evaluator[node]
print("rect.width:", evaluator[the_tuple.elts[0]])
print("rect:", evaluator[the_tuple.elts[0].value])
输出
rect.width: 3
rect: <__main__.Rectangle object at 0x105b0dd30>
好吧,但您也可以用 eval
做同样的事情。有用之处在于它会拒绝评估属性 rect.area
,因为这会触发未知代码。如果我们尝试,它将引发一个 CannotEval
异常。
from pure_eval import CannotEval
try:
print("rect.area:", evaluator[the_tuple.elts[2]]) # fails
except CannotEval as e:
print(e) # prints CannotEval
要找到树中可以评估的所有表达式
for node, value in evaluator.find_expressions(tree):
print(ast.dump(node), value)
输出
Attribute(value=Name(id='rect', ctx=Load()), attr='width', ctx=Load()) 3
Attribute(value=Name(id='rect', ctx=Load()), attr='height', ctx=Load()) 5
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
Name(id='rect', ctx=Load()) <__main__.Rectangle object at 0x105568d30>
请注意,这包括了三次 rect
,每次都对应源代码中的一个出现。由于所有这些节点都是等效的,我们可以将它们放在一起
from pure_eval import group_expressions
for nodes, values in group_expressions(evaluator.find_expressions(tree)):
print(len(nodes), "nodes with value:", values)
输出
1 nodes with value: 3
1 nodes with value: 5
3 nodes with value: <__main__.Rectangle object at 0x10d374d30>
如果我们想要列出树中的所有表达式,我们可能想过滤掉一些值明显的表达式。例如,假设我们有一个函数 foo
def foo():
pass
如果我们像平常一样通过名称引用 foo
,那么这并不有趣
from pure_eval import is_expression_interesting
node = ast.parse('foo').body[0].value
print(ast.dump(node))
print(is_expression_interesting(node, foo))
输出
Name(id='foo', ctx=Load())
False
但如果我们用不同的名称引用它,那么这就有趣了
node = ast.parse('bar').body[0].value
print(ast.dump(node))
print(is_expression_interesting(node, foo))
输出
Name(id='bar', ctx=Load())
True
通常 is_expression_interesting
对于以下值返回 False
- 字面量(例如
123
,'abc'
,[1, 2, 3]
,{'a': (), 'b': ([1, 2], [3])}
) - 名称等于值的
__name__
的变量或属性,例如上面提到的foo
或如果是方法的话,self.foo
- 通常名称的内置函数(例如
len
)
为了使事情更简单,你可以使用以下方法找到表达式、将它们分组并过滤掉明显的表达式
evaluator.interesting_expressions_grouped(root)
要获取 AST 节点的源代码,我推荐使用 asttokens。
以下是一个将所有这些内容整合在一起的完整示例
from asttokens import ASTTokens
from pure_eval import Evaluator
source = """
x = 1
d = {x: 2}
y = d[x]
"""
names = {}
exec(source, names)
atok = ASTTokens(source, parse=True)
for nodes, value in Evaluator(names).interesting_expressions_grouped(atok.tree):
print(atok.get_text(nodes[0]), "=", value)
输出
x = 1
d = {1: 2}
y = 2
d[x] = 2
项目详情
下载文件
下载您平台上的文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。
源代码分发
pure_eval-0.2.3.tar.gz (19.8 kB 查看散列值)
构建分发
pure_eval-0.2.3-py3-none-any.whl (11.8 kB 查看散列值)
关闭
纯_eval-0.2.3.tar.gz 的散列值
算法 | 散列摘要 | |
---|---|---|
SHA256 | 5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42 |
|
MD5 | d545186f2c899d9dd273c03d71b7ffb7 |
|
BLAKE2b-256 | cd050a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b |
关闭
纯_eval-0.2.3-py3-none-any.whl 的散列值
算法 | 散列摘要 | |
---|---|---|
SHA256 | 1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0 |
|
MD5 | 133fc4b94daeec26570d6297817d0b94 |
|
BLAKE2b-256 | 8e37efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f |