Python的内置函数compile()
code = """def fact(x):
if x <= 1:
return 1
print (1)
return x*fact(x-1)
fact(10)"""
c = compile(code, "<string>", "exec")
code = """def fact(y):
if y <= 1:
return 1
print (1)
return y*fact(y-1)
fact(10)"""
d = compile(code, "<string>", "exec")
这里c == d是假的。这是预期的行为? (在源代码中添加空格或使print 1
代替print(1)
不会导致更改的对象,这是正确的。)
我的问题是:可以使用编译对象来检测Python源代码中的更改吗?
编辑:
为了更清楚地解释它,我正在开发一个允许用户执行他们的Python代码的Web应用程序。
每次用户执行代码时,都会在服务器上执行。即使添加空格/括号等也会导致新的执行。我试图通过存储已编译的代码来优化此步骤,如果新请求中的代码相同,则不执行它。
我如何知道代码已更改?(是否需要再次执行代码或添加简单空间的更改。)
我认为还有其他方法可以通过使用散列值或类似的东西来实现我想要实现的目标,但我想这样做,因为它看起来更像Pythonic并且比重新发明轮子更好。
答案 0 :(得分:3)
不要这样做,作为优化,它不会带来极快的加速。
compile
是built-in function ,它是在C
中实现的,速度很快,而且不是您应该优化的地方。当用户尝试执行代码时,您应该允许在没有任何缓存的情况下编译和执行代码。
考虑以下内容,编写试图发现用户输入的文本是否有任何差异的代码,最有可能导致执行时间比实际编译函数并执行它更多。除此之外,您还需要您必须在某处存储和检索编译代码。第一个增加了不必要的空间要求,第二个增加了开销,因为您需要进行查找(当然,取决于您选择存储它的方式)。
除了我刚才所说的,你可以尝试比较编译Python代码的结果,但这种方法是有限的和混淆的。我将假设您输入的代码片段应该比较等于,因为它们之间的唯一区别在于参数名称(其名称对代码执行没有影响)。
一般来说,我要提出的方法只会返回&#34;等于&#34; (True
)如果有问题的代码段仅在空格和/或参数名称上有所不同,则在所有其他情况下,它会返回False
(您可能会尝试使其更加智能,但可能会要求你考虑许多边缘情况。)
您通常应该注意的是compile
会返回code
个对象。 code
个对象通常包含Python
所需的所有信息,以便执行一段代码。您可以使用dis
模块查看代码对象中包含的指令:
from dis import dis
dis(c)
1 0 LOAD_CONST 0 (<code object fact at 0x7fa7bc30e630, file "<string>", line 1>)
3 MAKE_FUNCTION 0
6 STORE_NAME 0 (fact)
6 9 LOAD_NAME 0 (fact)
12 LOAD_CONST 1 (10)
15 CALL_FUNCTION 1
18 POP_TOP
19 LOAD_CONST 2 (None)
22 RETURN_VALUE
这是您的代码段所做的事情。它加载另一个代码对象(表示函数fact
),进行函数调用并返回。
调用dis(d)
时会生成相同的确切输出 ,唯一的区别在于加载code
对象fact
1}}:
dis(d)
1 0 LOAD_CONST 0 (<code object fact at 0x7fa7bc30e5b0, file "<string>", line 1>)
正如您所看到的,它们是不同的:
code object fact at 0x7fa7bc30e630 != code object fact at 0x7fa7bc30e5b0
但它们的区别不在于它们的功能,而是 它们是表示相同功能单元的不同实例 。
所以这就是问题所在:如果它们代表相同的功能,我们是否关心它们是不同的实例?同样,如果两个变量表示相同的值,我们是否关心它们是否具有不同的名称?我假设我们不这样做,这就是为什么我认为如果比较它们的原始字节代码,你可以对代码对象进行有意义的基本比较。
原始字节代码几乎就是我之前描述的,没有名称,没有标识只是Python解释器的一系列命令(纯功能)。我们来看看它:
c.co_code
'd\x00\x00\x84\x00\x00Z\x00\x00e\x00\x00d\x01\x00\x83\x01\x00\x01d\x02\x00S'
好的,这很难看,让我们看看更好看:
dis(c.co_code)
0 LOAD_CONST 0 (0)
3 MAKE_FUNCTION 0
6 STORE_NAME 0 (0)
9 LOAD_NAME 0 (0)
12 LOAD_CONST 1 (1)
15 CALL_FUNCTION 1
18 POP_TOP
19 LOAD_CONST 2 (2)
22 RETURN_VALUE
看起来更好。这与之前的dis(c)
命令完全相同,唯一的区别是名称不存在,因为它们并没有真正发挥作用。
那么,如果我们将d.co_code
与c.co_code
进行比较,我们会得到什么?当然,执行命令后的相等性是相同的。 但是,这里有一个陷阱,为了100%确定d.co_code
等于c.co_code
我们还需要比较代码对象在c
和d
内加载的函数(代表函数fact
的代码对象)如果我们不比较这些函数,我们就会得到误报。
那么函数code
的{{1}}对象在哪里呢?它们分别位于fact
对象co_consts
和code
内的c
字段中,d
是包含所有常量的co_consts
特定的list
对象。如果你在那里取得一个高峰,你将能够看到每个的定义:
code
那么,我们该怎么办?我们比较它们的原始字节代码,看它们是否代表了我们之前所做的相同的功能。
您可以理解这是一个递归过程,我们首先比较输入的原始字节代码,然后扫描# located at position 0 in this case.
# the byte code for function 'fact'
dis(c.co_consts[0])
2 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (1)
6 COMPARE_OP 1 (<=)
9 POP_JUMP_IF_FALSE 16
3 12 LOAD_CONST 1 (1)
15 RETURN_VALUE
4 >> 16 LOAD_CONST 1 (1)
19 PRINT_ITEM
20 PRINT_NEWLINE
5 21 LOAD_FAST 0 (x)
24 LOAD_GLOBAL 0 (fact)
27 LOAD_FAST 0 (x)
30 LOAD_CONST 1 (1)
33 BINARY_SUBTRACT
34 CALL_FUNCTION 1
37 BINARY_MULTIPLY
38 RETURN_VALUE
以查看是否存在另一个co_consts
对象并重复直到如果code
对象位于code
中的不同位置,我们就会找不到code
个对象,我们将返回co_consts
。
在代码中,它应该如下所示:
False
来自from types import CodeType
def equal_code(c1, c2):
if c1.co_code != c2.co_code:
return False
for i, j in zip(c1.co_consts, c2.co_consts):
if isinstance(i, CodeType):
if isinstance(j, CodeType):
return equal_code(i, j)
else: # consts in different position, return false
return False
return True
的{{1}}用于检查CodeType
个实例。
我认为只使用types
生成的code
个对象,这是最好的。
答案 1 :(得分:2)
有许多方法可以做到这一点比你正在尝试的方法简单得多:
使用diff
和-w
(忽略空格)标记。如果产生差异,您将很可能知道它是代码更改。
使用git或其他源代码存储库。然后在决定执行之前查明文件之间是否有更改。但是,在这方面你只是使用git的差异功能,所以不妨使用第一个选项。
答案 2 :(得分:1)
这么多问题......
可以使用编译对象来检测Python源代码中的更改吗?
是的,有点儿。但是,由于代码对象包含其局部变量的名称,因此将x
重命名为y
将使您的比较失败,即使没有功能更改。
我如何知道代码已更改? (这种变化需要再次执行代码还是简单的空间添加。)
值得一提的是,“简单的空间添加”可能需要在Python中重新编译和重新执行,而不是在许多其他语言中。
我认为他们是通过使用散列值或类似的东西实现我想要实现的目标的另一种方式,但我想这样做,因为它似乎更像Pythonic并且比重新发明轮子更好。
我不确定这个特殊选项的Pythonic是什么 - 也许它会让你的代码变得更简单?这是选择它的一个很好的理由。
否则,字符串比较可能更快(对变量重命名具有相同的敏感性),并且完整的AST比较更复杂但可能更智能。
最后:
每次用户执行他/她的代码时,都会在服务器上执行。甚至添加空格/括号等也会导致新的执行。我试图通过存储已编译的代码来优化此步骤,如果新请求中的代码相同,则不执行它。
但是当你明确要求时,你可能会 执行用户的代码。
如果你每次键入一个字符时都这样做,那显然是没有意义的,但是考虑使用随机数的代码:即使没有代码更改,用户也会合理地期望看到输出在执行时发生变化。