【问题标题】:How to judge (or how to write) a python function with no side effects?如何判断(或如何编写)一个没有副作用的python函数?
【发布时间】:2013-11-30 09:09:12
【问题描述】:

答案等于side effects 的定义。

到目前为止,我还没有找到准确的答案。 python 文档说: Functional style discourages functions with side effects that modify internal state or make other changes that aren’t visible in the function’s return value.

什么是modify internal statemake other changes that aren’t visible...

将变量绑定到对象(只是绑定,而不是修改)是否意味着没有副作用?例如a=1a=[1,2,3]a,b=1,2

这里有 4 个函数。它们都没有副作用吗?为什么?

注意,假设参数n 必须是int 对象。

def purefunc1(n):
    def getn(n):
        return [1,2,3,4,5][:n-1],[1,2,3,4,5][:n]
    def addn(fir,sec,thd):
        return fir+sec+thd
    return addn(getn(n)[0],['HEY'],getn(n)[1])

def purefunc2(n):
    def getn(n):
        #bind
        arr=[1,2,3,4,5]
        return arr[:n-1],arr[:n]
    def addn(fir=[],sec=[],thd=[]):
        return fir+sec+thd
    #bind
    arg1,arg3=getn(n)
    return addn(arg1,['HEY'],arg3)

def purefunc3(n):
    arr=[1,2,3,4,5]
    def getn(n):
        #closure 'arr'
        return arr[:n-1],arr[:n]
    def addn(fir=[],sec=[],thd=[]):
        return fir+sec+thd
    #bind
    arg1,arg3=getn(n)
    return addn(arg1,['HEY'],arg3)

def purefunc4(n):
    def arr():
        return [1,2,3,4,5]
    def getn(n):
        #closure
        return arr()[:n-1],arr()[:n]
    def addn(fir=[],sec=[],thd=[]):
        return fir+sec+thd
    #bind
    arg1,arg3=getn(n)
    return addn(arg1,['HEY'],arg3)

print (purefunc1(3))
print (purefunc2(3))
print (purefunc3(3))
print (purefunc4(3))

我的猜测:purefunc1 没有副作用。但我不知道下面的纯函数*。

输出是:

[1, 2, 'HEY', 1, 2, 3]
[1, 2, 'HEY', 1, 2, 3]
[1, 2, 'HEY', 1, 2, 3]
[1, 2, 'HEY', 1, 2, 3]    

如果你问为什么存在这么奇怪的函数,答案是它只是为了方便。真正的功能是复杂的。不过有兴趣的可以click here看看ieval这个函数有没有副作用。

提前谢谢大家。

【问题讨论】:

  • 来自关于副作用的 wiki 页面:“在存在副作用的情况下,程序的行为取决于历史;也就是说,评估的顺序很重要。理解和调试具有副作用的函数需要了解上下文及其可能的历史。或来自程序员:programmers.stackexchange.com/questions/40297/…"

标签: python functional-programming


【解决方案1】:

Python 函数没有副作用当且仅当它不修改全局变量,只调用无副作用函数,并且doesn't raise exceptions which are caught。 引用维基百科:

在计算机科学中,函数或表达式被称为有边 效果如果,除了返回一个值,它还修改了一些 状态或与调用函数有可观察的交互 外面的世界。

基本上可以调用无副作用的函数,调用后没有什么不同,即调用后一切都一样。

【讨论】:

  • 我不相信链接的答案适用:Python 中的异常通常不会像其他答案描述的那样被视为bottom -- _|_
【解决方案2】:

Python's documentation about functional programming 中有一小段。我将在下面引用(强调我的):

以函数式风格编写的 Python 程序通常不会走极端避免所有 I/O 或所有赋值;相反,它们将提供一个功能性的界面,但将在内部使用非功能性功能。例如,函数的实现将仍然使用对局部变量的赋值,但不会修改全局变量或产生其他副作用。

换句话说,从外部的角度思考。该函数是否修改了一些全局变量、类变量?它是否操纵任何状态?如果不是,那么它的。根据上述文档,函数可以在内部分配给局部变量,但重要的是函数不应修改任何面向外部的状态。


什么是修改内部状态并进行其他不可见的更改...?

看下面的例子。每次调用 non_pure_add1_func(..) 时,我们都会修改 global_list

global_list = []

def non_pure_add1_func(x):
    global_list.append(x)
    return x+1

虽然上面的函数可能是幂等的,但不是纯函数。

【讨论】:

    【解决方案3】:

    你自己回答了。你想要副作用的例子吗?

    def side1():
      print 'Hello!'
    

    这会通过写入来修改 sys.stdout。 sys.stdout 不是函数内部的。

    a = 1
    
    def side2():
      global a
      a += 2
    

    这会修改a,这是一个全局的,而不是函数的局部变量。

    在一个简单的函数(没有类,没有外部)中存储状态的一个优雅的例子是这样的:

    def side3(state=[]):
      # call it without arguments to make the point: side3()
      state.append(len(state))
      return state  # not returning anything still changes state...
    

    数组(因为它在函数定义时只创建一次)对于函数的每次调用都是相同的,它是它的一部分。它会针对每次调用进行修改,根据 state 更改返回值。 :-)

    你的函数不会做任何这些。这就是为什么它们没有副作用。

    【讨论】:

      【解决方案4】:

      在函数式编程的上下文中,函数是确定性的东西,给定输入,输出一些东西。这就是纯函数的作用。他们只计算事物。他们不“做”事情。如果你的函数代码理论上可以通过一个表(甚至是无限表)来切换,那么你就有了一个没有副作用的函数。

      任何东西除了返回一个值之外,它是一个副作用。术语“修改内部状态”和“进行其他不可见的更改”就是指这一点。修改内部状态或进行更改...可能意味着:

      1. 打印一些东西
      2. 修改超出范围的对象
      3. 保存一些东西

      您可以将副作用理解为“修改外部世界”。在您的示例中,他们都没有这样做。但是,如果你为 n 分配了一些东西,比如附加一些东西,或者在函数返回后会在你的环境中持续存在的任何东西,那么这将是一个副作用。

      您的所有功能都可以通过比较输入和输出进行测试。如果你有副作用(比如改变 n),你的测试会变得更加复杂,你需要分析你的函数的上下文。

      【讨论】:

        猜你喜欢
        • 2021-02-09
        • 2015-05-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多