跳转到主要内容

安全评估AST节点,无副作用

项目描述

pure_eval

Build Status Coverage Status Supports Python versions 3.7+

这是一个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 查看散列值)

上传时间 Python 3

由以下支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面