【问题标题】:is there any way to prevent side effects in python?有什么方法可以防止python中的副作用?
【发布时间】:2014-01-01 09:03:52
【问题描述】:

有什么方法可以防止 python 的副作用?比如下面这个函数有副作用,有没有什么关键字或者其他方法让python吐槽?

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

【问题讨论】:

  • 我认为你做不到。有点相关的帖子:stackoverflow.com/a/17292194/1967396
  • 你怎么可能检测到副作用?副作用和效果之间的区别几乎完全是上下文相关的。您甚至会如何开始设计一个可以自动确定此类事情的系统?
  • @Silas:副作用在 CS 中具有非常精确的含义:除了返回值之外的任何东西,请参阅en.wikipedia.org/wiki/Side_effect_%28computer_science%29
  • @Junuxx 根据这个定义,append() 本身就有副作用。
  • @SilasRay 确实有,这就是为什么 OP 使用 a.append 作为副作用的例子。你的意思是什么?

标签: python functional-programming side-effects


【解决方案1】:

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

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

【讨论】:

  • 虽然不能改变元组中的任何可变对象,但这并不能说明......
  • @JonClements 是的,最好指定元组应该包含不可变类型的对象。
【解决方案2】:

关于强制执行的唯一方法是将函数规范覆盖为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复制的代码)

【讨论】:

    【解决方案3】:

    由于任何 Python 代码都可以执行 IO,因此任何 Python 代码都可以发射洲际弹道导弹(我认为发射洲际弹道导弹对于大多数用途来说是一个相当灾难性的副作用)。

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

    【讨论】:

    • 什..什...什么?如果它可以发射洲际弹道导弹,那么它一定是天网试图摆脱人类敌人......不过,带有 Python 代码库的 终结者 似乎不那么可怕 - 所以这是一个加号:)
    • 这是对“不”最现实的阐述。 import antigravity.
    【解决方案4】:

    Python 并不是为了强制防止副作用而设置的。正如其他一些人所提到的,您可以尝试 deepcopy 数据或使用不可变类型,但这些仍然存在难以捕捉的极端情况,而且付出的努力多于其价值。

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

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

    【讨论】:

    • 谢谢,现在我知道这是不可能的,我可以停止尝试让它工作,只是忍受它。
    • @yigalirani python 让您可以访问所有代码、猴子补丁违规功能,一切就绪!
    【解决方案5】:

    您必须先复制该列表。像这样的:

    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】:

      一般情况下很难做到,但对于一些实际情况,你可以这样做:

      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 以便它可以t被修改。在那里,“副作用”可能会在编译时引发错误。 (当然有办法得到一些反正)。

      上面强制任何修改的值都在返回值/元组中。如果您在 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
      

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

      【讨论】:

        【解决方案7】:

        很抱歉参加聚会真的迟到了。您可以使用 effect 库来隔离 Python 代码中的副作用。正如其他人在 Python 中所说,您必须明确编写函数式代码,但这个库确实鼓励这样做。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-09-10
          • 2018-07-08
          • 2017-06-02
          • 2012-04-28
          • 2011-12-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多