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)
不会导致对象发生更改,这是正确的。)
我的问题是:compile对象可以用来检测Python源代码中的更改吗?你知道吗
编辑:
为了更清楚地解释这一点,我正在开发一个允许用户执行Python代码的web应用程序。你知道吗
每次用户执行代码时,它都会在服务器上执行。即使添加空格/括号等也会导致新的执行。我试图通过存储编译后的代码来优化这一步,如果新请求中的代码相同,则不执行它。你知道吗
如何知道代码已更改?(这种更改是需要再次执行代码还是需要简单的空格添加。)
我确实认为有其他方法可以通过使用散列值或类似的东西来实现我想要实现的目标,但我想这样做,因为它看起来更像Python,而且比重新发明轮子更好。你知道吗
不要这样做,作为一种优化,它不会带来很大的速度提升。你知道吗
compile
是一个built-in function,它是在C
中实现的,它速度很快,并且不是您应该寻求优化的地方。当用户试图执行代码时,应该允许在没有任何缓存的情况下编译和执行代码。你知道吗考虑下面的代码,编写代码试图发现用户输入的文本是否有任何差异,很可能会导致比实际编译函数并执行它更多的执行时间。除此之外,您还必须在某个地方存储和检索编译代码。第一个增加了不必要的空间需求,第二个增加了开销,因为您必须进行查找(当然,这取决于您选择如何存储它)。你知道吗
除了我刚才所说的,您可以尝试比较编译Python代码的结果,但是这种方法是有限的,而且很模糊。我假设您输入的代码片段应该比较相等,因为它们之间的唯一区别在于参数名(其名称对代码执行没有影响)。你知道吗
使用字节码:
一般来说,我将要介绍的方法只返回“equal”(
True
),如果所讨论的代码片段仅在空格和/或参数名称上不同,在所有其他情况下,它返回False
(您可以尝试使其更智能,但这可能需要考虑许多边缘情况)。你知道吗通常应该注意的是
compile
返回一个code
对象。code
对象通常包含执行一段代码所需的所有信息Python
。您可以使用dis
模块查看代码对象中包含的指令:这就是您的代码片段所做的。它加载另一个代码对象(表示函数
fact
),进行函数调用并返回。你知道吗调用
dis(d)
时会生成相同的精确输出,唯一的区别在于为fact
加载code
对象:如你所见,它们是不同的:
但是,它们的区别不在于它们的功能,而在于它们是不同的实例,表示相同的功能单元。你知道吗
所以这里有一个问题:如果它们代表相同的功能,我们是否关心它们是不同的实例?同样,如果两个变量代表相同的值,我们是否关心它们是否有不同的名称?我假设我们没有,这就是为什么我认为如果比较代码对象的原始字节代码,就可以对它们进行有意义的基本比较。你知道吗
比较原始字节码:
原始字节码和我之前描述的差不多,没有名称,没有标识只是Python解释器的一系列命令(纯功能)。我们来看看:
好吧,太难看了,让我们仔细看看:
看起来好多了。这与前面的
dis(c)
命令完全相同,唯一的区别是名称不存在,因为它们在这一切中并不真正起作用。你知道吗那么,如果我们比较
d.co_code
和c.co_code
,我们会得到什么呢?当然,因为执行的命令是相同的,所以相等。但是这里有一个陷阱,为了100%确定d.co_code
等于c.co_code
,我们还需要比较c
和d
中加载的函数的代码对象(表示函数fact
的代码对象),如果我们不比较这些,我们将得到误报。你知道吗那么函数
fact
的code
对象在每种情况下位于何处呢?它们位于一个叫做co_consts
在code
对象c
和d
中,co_consts
是一个list
包含特定code
对象的所有常量。如果你在里面取一个峰值,你就能看到每个峰值的定义:那么,我们该怎么办?我们比较它们的原始字节码,看看它们是否代表相同的功能,就像我们以前做的那样。你知道吗
正如您所理解的,这是一个递归过程,我们首先比较输入的原始字节码,然后扫描
co_consts
以查看是否存在另一个code
对象,然后重复,直到找不到code
对象,如果code
对象位于co_consts
中的不同位置,我们将返回False
。你知道吗在代码中,应该是这样的:
其中,来自
types
的CodeType
用于检查code
实例。你知道吗我认为这是您仅使用从
compile
生成的code
对象所能做到的最好的方法。你知道吗这么多问题。。。你知道吗
是的,有点。但是,由于代码对象包含其局部变量的名称,因此将
x
重命名为y
将导致比较失败,即使没有函数更改。你知道吗值得一提的是,“简单的空间添加”可能需要在Python中重新编译和执行,比在许多其他语言中更需要这样做。你知道吗
我不知道这个选项有什么Pythonic的地方-也许它让你的代码更简单?这将是一个很好的选择它的理由。你知道吗
否则,字符串比较可能更快(对变量重命名的敏感度相同),完整的AST比较更复杂,但可能更聪明。你知道吗
最后:
但是当用户明确要求您执行时,您可能应该执行用户的代码。你知道吗
如果每次用户键入字符时都这样做,显然毫无意义,但请考虑使用随机数的代码:用户在点击execute时会合理地期望看到输出更改,即使没有代码更改。你知道吗
有许多方法比你正在尝试的要简单得多:
使用带有
-w
(忽略空白)标志的diff
。如果产生了差异,您将知道这很可能是代码更改。使用git或其他源代码存储库。然后在决定执行之前,找出文件之间是否有更改。然而,在这方面,您只是在使用git的不同功能,所以不妨使用第一个选项。
相关问题 更多 >
编程相关推荐