检查两个Python函数是否相等

32

我想知道如何检查两个函数是否相同。例如,(lambda x: x) == (lambda y: y) 可以评估为True。据我所知,Python 会检查函数在内存中的位置是否相同,但不会检查它们是否具有相同的操作。我知道这似乎是不切实际的。

另一个解决方案是运行某些方法来查看函数包含什么或者它如何工作。因此可以使用 (lambda x: x).what() 这样的方法,返回方法的工作方式,可能是字典或其他形式。

我希望得到答案,但我怀疑这是不可能的。


尽管对于Python问题的答案似乎假定直接获取字节码是不可能的,因此您必须对其进行反汇编,然后尝试从字节码以外的源中剥离出所有附加的内容,这在实际上是完全可以(而且更简单)直接获取字节码... - abarnert
3
如果有人来这里想了解如何比较两个指向同一个函数的引用:f==g 对我有效。 - lucidbrot
2个回答

53

您可以测试的唯一事项是代码对象的相等性:

>>> x = lambda x: x
>>> y = lambda y: y
>>> x.__code__.co_code
'|\x00\x00S'
>>> x.__code__.co_code == y.__code__.co_code
True

这两个函数的字节码是相同的。你可能需要验证代码对象的更多方面(如常量和闭包),但是相等的字节码应该等于相同的执行路径。

当然,有办法创建返回相同输入值的函数,但字节码不同; 总有更多的方法去做。


您还可以创建对象,其中您的测试为true,但结果不同。(这有点牵强,但无论如何...):https://gist.github.com/sharth/7536465 - Bill Lynch
2
@sharth:而且答案已经涵盖了这一点:“你可能需要验证……”他举了闭包作为一个例子。自定义函数属性是另一个例子。给函数一个不同的__globals__也是一个例子。我认为我们不需要详尽的列表。 - abarnert
5
根据这个定义,“lambda x:x + 1”和“lambda x:x + 2”也是相等的。 - Andreas Mueller
4
@AndreasMueller说的与abarnert评论相同,它属于代码对象的co_consts元组可能会有变化。 - Martijn Pieters
依赖于dunder方法可能是个坏主意 - 如果函数的作者决定将其删除会怎么样呢?就像numpy函数在1.26版本中刚刚发生的那样。 - undefined
显示剩余2条评论

15

如果你想知道两个函数是否对所有输入始终执行相同的操作,你将不得不在所有输入上同时运行它们(这将需要无限时间),并拦截所有可能的副作用(这实际上是不可能的)。

当然,你可以想出一些启发式方法,向它们抛出一组不同的值,如果函数不同,则非常可能生成不同的输出,适用于你的应用领域。但显然,这没有通用解决方案,否则所有单元测试都将自动生成,从而为我们节省大量工作,对吧?


相反,您可能只是想知道两个函数是否具有完全相同的实现。对此,Martijn Pieters的答案是明显的起点,甚至可能是终点(取决于您是否关心闭包、全局变量等)。


但是,您所要求的与这两者都不同;您显然想要手动查看代码以了解“它是如何工作的”:

另一种解决方案是我可以运行在一个函数上的某种方法,以查看它包含什么或它是如何工作的。因此,类似于(lambda x:x)。what()这样的东西会返回该方法的工作方式,可能是字典或其他东西。

该函数已经存在:dis.dis。当你在一个函数上运行它时,它会告诉你该函数如何工作。不是以字典的形式(什么字典?),而是以Python解释器的一系列字节码行的形式呈现(这是一个相对简单的堆栈机器,上面添加了一些更高级别的东西,主要在dis文档中描述)。

或者,更简单地说,你可以用inspect.getsource获取源代码。

以下是使用您的示例的两个函数的示例:

>>> f1 = lambda x: x
>>> f2 = lambda y: y
>>> def f3(z):
...     return z
>>> dis.dis(f1)
  1           0 LOAD_FAST                0 (x)
              3 RETURN_VALUE
>>> dis.dis(f2)
  1           0 LOAD_FAST                0 (y)
              3 RETURN_VALUE
>>> dis.dis(f3)
  1           0 LOAD_FAST                0 (z)
              3 RETURN_VALUE
>>> inspect.getsource(f1)
'f1 = lambda x: x\n'
>>> inspect.getsource(f2)
'f2 = lambda y: y\n'
>>> inspect.getsource(f3)
'def f3(z):\n    return z\n'

在第一种情况下,您需要了解足够多的有关 dis 的知识才能意识到 (x) 等不是字节码的一部分,而是函数本地名称列表的一部分。(这在 inspect 文档和 dis 文档中都有详细说明。)在第二种情况下,您需要了解 Python 的足够多的知识才能意识到 deflambda 定义了完全相同的函数。所以,无论哪种方式,都没有办法自动化这个过程(或者说,除了 Martijn 的答案之外,真的没有什么可以自动化的东西)。


1
在某些情况下,可以使用自动定理证明器(例如Z3)来证明函数的等价性。 - Anderson Green

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接