有没有办法防止python中的副作用?

时间:2013-12-13 14:42:04

标签: python functional-programming side-effects

有没有办法防止python中的副作用?例如,以下函数有副作用,是否有任何关键字或任何其他方式让python抱怨它?

def func_with_side_affect(a):
    a.append('foo')

7 个答案:

答案 0 :(得分:10)

Python实际上并未设置为强制防止副作用。正如其他人提到的那样,你可以尝试deepcopy数据或使用不可变类型,但是这些仍然存在难以捕获的极端情况,并且它只需要比它的价值更多的努力。

在Python中使用函数式通常涉及程序员简单地设计其功能以实现功能。换句话说,只要编写一个函数,就会以不改变参数的方式编写它。

如果您正在调用其他人的功能,那么必须确保您传入的数据无法变异,或必须保持安全,不受影响的数据副本,你远离那个不受信任的功能。

答案 1 :(得分:3)

对不起,聚会很晚。您可以使用effect库来隔离python代码中的副作用。正如其他人在Python中所说的那样,你必须明确地编写功能样式代码,但这个库真的鼓励它。

答案 2 :(得分:2)

不,但是以您为例,您可以使用不可变类型,并将元组作为a参数传递。副作用不能影响不可变类型,例如你不能附加到元组,你只能通过扩展给定来创建其他元组。

UPD:但是,您的函数仍然可以更改由不可变对象引用的对象(如在注释中指出的那样),写入文件并执行其他IO。

答案 3 :(得分:1)

关于强制执行的唯一方法是在将函数规范传递给原始函数之前将其覆盖到deepcopy任何参数。您可以使用function decorator

这样,函数无法实际更改最初传递的参数。然而,由于深度复制操作在内存(和垃圾收集)使用以及CPU消耗方面相当昂贵,因此具有显着减速的“副作用”。

我建议您正确测试代码,以确保不会发生意外更改或使用使用完全按值复制语义的语言(或只包含不可变变量)。

作为另一种解决方法,您可以通过将传递的对象添加到您的类中来使您的传递对象基本上不可变:

 """An immutable class with a single attribute 'value'."""
 def __setattr__(self, *args):
     raise TypeError("can't modify immutable instance")
 __delattr__ = __setattr__
 def __init__(self, value):
     # we can no longer use self.value = value to store the instance data
     # so we must explicitly call the superclass
     super(Immutable, self).__setattr__('value', value)

(从Wikipedia article about Immutable object复制的代码)

答案 4 :(得分:1)

由于任何Python代码都可以执行IO,因此任何Python代码都可以发射洲际弹道导弹(我考虑在大多数情况下启动ICBM是一种相当灾难性的副作用。)

避免副作用的唯一方法是首先不使用Python代码,而是使用数据 - 即最终创建一个不允许副作用的特定于域的语言,以及执行该语言程序的Python解释器。 / p>

答案 5 :(得分:1)

您必须先复制一份清单。像这样:

def func_without_side_affect(a):
    b = a[:]
    b.append('foo')
    return b

这个较短的版本也适合你:

def func_without_side_affect(a):
    return a[:] + ['foo']

如果您有嵌套列表或类似的其他内容,您可能希望查看copy.deepcopy来制作副本而不是[:]切片操作符。

答案 6 :(得分:0)

对于一般案例来说,这是非常困难的,但对于某些实际案例,你可以这样做:

def call_function_checking_for_modification(f, *args, **kwargs):
    myargs = [deepcopy(x) for x in args]
    mykwargs = dict((x, deepcopy(kwargs[x])) for x in kwargs)

    retval = f(*args, **kwargs)

    for arg, myarg in izip(args, myargs):
        if arg != myarg: 
            raise ValueError, 'Argument was modified during function call!'
    for kwkey in kwargs:
        if kwargs[kwkey] != mykwargs[kwkey]:
            raise ValueError, 'Argument was modified during function call!'

    return retval

但是,显然,这有一些问题。对于琐碎的事情(即,所有的输入是简单类型),那么该反正不是非常有用 - 那些将可能是不可变的,并且在任何情况下它们很容易(当然,相对),以检测比复杂类型

对于复杂类型,deepcopy代价很高,并且无法保证==运算符实际上能正常工作。 (并且简单的副本不够好......想象一个列表,其中一个元素改变值...一个简单的副本将只存储一个引用,所以原始值也改变了)。

一般情况下,这并不是那么有用,因为如果您已经担心调用此函数的副作用,您可以更智能地防范它们(通过存储您自己的副本,如果需要,审核目标函数,等),如果这是你的功能,你担心引起副作用,你会审核它以确保。

上面的东西可以包装在装饰器中;由全局变量(if _debug == True:,类似的东西)控制的昂贵部分,它可能在很多人编辑相同代码的项目中很有用,但是,我猜...

编辑:这仅适用于需要更“严格”形式的“副作用”的环境。在许多编程语言中,您可以更加明确地提供副作用 - 例如,在C ++中,除非显式指针或引用,否则一切都是按值进行的,即使这样,您也可以将传入引用声明为const以便它无法修改。在那里,'副作用'可以在编译时抛出错误。 (当然有办法得到一些反正)。

以上强制要求任何修改后的值都在返回值/元组中。如果您在python 3中(我还没有)我认为您可以在函数声明中指定装饰来指定函数参数的属性,包括是否允许修改它们,并在上面的函数中包含它以允许一些明确的论据是可变的。

请注意,我认为您可能也会这样做:

class ImmutableObject(object):
    def __init__(self, inobj):
        self._inited = False
        self._inobj = inobj
        self._inited = True

    def __repr__(self):
        return self._inobj.__repr__()

    def __str__(self):
        return self._inobj.__str__()

    def __getitem__(self, key):
        return ImmutableObject(self._inobj.__getitem__(key))

    def __iter__(self):
        return self.__iter__()

    def __setitem__(self, key, value):
        raise AttributeError, 'Object is read-only'

    def __getattr__(self, key):
        x = getattr(self._inobj, key)
        if callable(x):
              return x
        else:
              return ImmutableObject(x)

    def __setattr__(self, attr, value):
        if attr not in  ['_inobj', '_inited'] and self._inited == True:
            raise AttributeError, 'Object is read-only'
        object.__setattr__(self, attr, value)

(可能不是一个完整的实现,没有测试太多,但一个开始)。像这样工作:

a = [1,2,3]
b = [a,3,4,5]
print c
[[1, 2, 3], 3, 4, 5]
c[0][1:] = [7,8]
AttributeError: Object is read-only

如果您不信任下游功能,它可以让您保护特定对象不被修改,同时仍然相对轻量级。仍然需要显式包装对象。您可以构建一个装饰器来为所有参数半自动执行此操作。确保跳过可调用的那些。