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

有没有办法防止python中的副作用?,python,functional-programming,side-effects,Python,Functional Programming,Side Effects,有没有办法防止python中的副作用?例如,下面的函数有一个副作用,是否有任何关键字或其他方式让python抱怨它 def func_with_side_affect(a): a.append('foo') 不,但在您的示例中,您可以使用不可变类型,并将tuple作为参数传递。副作用不能影响不可变的类型,例如,您不能附加到元组,您只能通过扩展给定的类型来创建其他元组 UPD:但是,您的函数仍然可以更改不可变对象引用的对象(正如在注释中指出的那样)、写入文件和执行其他IO 关于强制执行的

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

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

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


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

关于强制执行的唯一方法是在将函数规范传递给原始函数之前将其覆盖到任何参数。你可以用一个简单的例子来说明这一点

这样,函数就无法实际更改最初传递的参数。但是,这有一个“副作用”,即速度大大减慢,因为deepcopy操作在内存(和垃圾收集)使用以及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)

(复制的代码)

< P>因为任何Python代码都可以做艾奥,任何Python代码都可以发射洲际弹道导弹(我认为发射ICBM对于大多数目的来说是一个相当灾难性的副作用)。
避免副作用的唯一方法是首先不要使用Python代码,而是使用数据——即,最终创建了一种不允许副作用的领域特定语言,以及一个执行该语言程序的Python解释器。

Python并不是为了强制防止副作用而设置的。正如其他一些人所提到的,您可以尝试
deepcopy
数据或使用不可变类型,但这些情况仍然存在一些棘手的问题,需要付出大量的努力

在Python中使用函数式通常涉及程序员简单地将其函数设计为函数式。换句话说,每当你写一个函数时,你写它的方式不会改变参数


如果你正在调用其他人的函数,那么你必须确保你传递的数据不能被变异,或者你必须自己保存一个安全的、未被触及的数据副本,远离不受信任的函数。

你必须先复制一份列表。大概是这样的:

def func_without_side_affect(a):
    b = a[:]
    b.append('foo')
    return b
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
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
这个较短的版本可能也适用于您:

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

如果您有嵌套列表或其他类似的内容,您可能希望查看以创建副本,而不是使用
[:]
切片运算符。

对于一般情况,这将非常困难,但对于某些实际情况,您可以执行以下操作:

def func_without_side_affect(a):
    b = a[:]
    b.append('foo')
    return b
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
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
但是,很明显,这有一些问题。对于琐碎的事情(即,所有的输入都是简单类型),那么不管怎样,这都不是很有用——这些可能是不可变的,并且在任何情况下,它们都比复杂类型更容易(相对而言)检测

但是,对于复杂类型,
deepcopy
将非常昂贵,而且不能保证
=
操作符实际上能够正常工作。(简单的复制还不够好……想象一下一个列表,其中一个元素改变了值……一个简单的复制只会存储一个引用,所以原始值也会改变)

但是,一般来说,这并不是很有用,因为如果您已经担心调用此函数的副作用,您可以更智能地防范它们(如果需要,可以存储您自己的副本,审核目标函数等),如果是您的函数,您担心会引起副作用,您将对其进行审核以确保

像上面这样的东西可以用一个装饰器包装;由于昂贵的部分由全局变量控制(
如果_debug==True:
,诸如此类),它可能在许多人编辑相同代码的项目中很有用,不过,我想

编辑:这只适用于预期“副作用”形式更“严格”的环境。在许多编程语言中,可以使副作用更加明显——例如,在C++中,除了显式指针或引用之外,所有的东西都是按值排列的,甚至可以将传入引用声明为“代码> const < /COD>”,从而使其不能被修改。在那里,“副作用”会在编译时抛出错误。(当然,无论如何都有办法得到一些)

上面强制所有修改的值都在返回值/元组中。如果您使用的是Python3(我还没有),我认为您可以在函数声明本身中指定装饰,以指定函数参数的属性,包括是否允许修改它们,并在上述函数中包含装饰,以允许某些参数显式可变

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

def func_without_side_affect(a):
    b = a[:]
    b.append('foo')
    return b
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
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
(可能不是一个完整的实现,没有进行过很多测试,但只是一个开始)。工作原理如下:

def func_without_side_affect(a):
    b = a[:]
    b.append('foo')
    return b
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
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

如果您不信任下游函数,它将允许您保护特定对象不受修改,同时仍然是相对轻量级的。但仍然需要显式包装对象。您可能会为所有参数构建一个装饰器来半自动完成这项工作。一定要跳过那些可以打电话的。

对不起,聚会迟到了。您可以使用这个库来隔离python代码中的副作用。正如其他人在Python中所说的,您必须显式地编写函数