14

函数是类似于数学函数的函数,其中没有与“现实世界”的交互,也没有副作用。从更实际的角度来看,这意味着纯函数不能

  • 打印或以其他方式显示消息
  • 随意
  • 取决于系统时间
  • 更改全局变量
  • 和别的

所有这些限制使得对纯函数的推理比对非纯函数的推理更容易。大多数函数应该是纯函数,这样程序可以减少错误。

在像 Haskell 这样具有庞大类型系统的语言中,读者可以从一开始就知道函数是纯函数还是非纯函数,从而使后续阅读变得更容易。

在 Python 中,这个信息可以通过@pure放在函数顶部的装饰器来模拟。我也希望那个装饰器能够真正做一些验证工作。我的问题在于这样一个装饰器的实现。

现在,我只是在函数的源代码中查找流行语,例如globalor randomor print,如果找到其中一个,我会抱怨。

import inspect

def pure(function):
    source = inspect.getsource(function)
    for non_pure_indicator in ('random', 'time', 'input', 'print', 'global'):
        if non_pure_indicator in source:
            raise ValueError("The function {} is not pure as it uses `{}`".format(
                function.__name__, non_pure_indicator))
    return function

然而,这感觉就像一个奇怪的黑客,可能会也可能不会取决于你的运气,你能帮我写一个更好的装饰器吗?

4

2 回答 2

12

我有点明白你来自哪里,但我认为这行不通。我们举一个简单的例子:

def add(a,b):
    return a + b

所以这对你来说可能看起来很“纯”。但是在 Python 中,+here 是一个任意函数,它可以做任何事情,这取决于调用它时有效的绑定。所以这a + b可能会产生任意的副作用。

但它甚至比这更糟糕。即使这只是做标准整数,也会+有更多“不纯”的东西在发生。

+正在创建一个新对象。现在,如果您确定只有调用者引用了该新对象,那么您可以将其视为纯函数。但是您不能确定,在该对象的创建过程中,没有泄露对它的引用。

例如:

class RegisteredNumber(int):

    numbers = []

    def __new__(cls,*args,**kwargs):
        self = int.__new__(cls,*args,**kwargs)
        self.numbers.append(self)
        return self

    def __add__(self,other):
        return RegisteredNumber(super().__add__(other))

c = RegisteredNumber(1) + 2

print(RegisteredNumber.numbers)

这将表明,所谓的纯 add 函数实际上已经改变了RegisteredNumber类的状态。这不是一个愚蠢的设计示例:在我的生产代码库中,我们有一些类来跟踪每个创建的实例,例如,允许通过密钥访问。

纯度的概念在 Python 中没有多大意义。

于 2015-07-27T22:02:48.837 回答
1

(不是答案,但评论太长了)

那么如果一个函数可以为同一组参数返回不同的值,它就不是纯粹的了吗?

请记住,Python 中的函数是对象,因此您要检查对象的纯度...

举个例子:

def foo(x):
    ret, foo.x = x*x+foo.x, foo.x+1
    return ret
foo.x=0

foo(3)反复调用给出:

>>> foo(3)
9

>>> foo(3)
10

>>> foo(3)
11

...

此外,读取全局变量不需要使用global语句或函数内的global()内置函数。全局变量可能会在其他地方发生变化,从而影响函数的纯度。

以上所有情况在运行时可能难以检测到。

于 2015-07-22T17:13:28.170 回答