60
def maker(n):
    def action(x):
        return x ** n
    return action

f = maker(2)
print(f)
print(f(3))
print(f(4))

g = maker(3)
print(g(3))

print(f(3)) # still remembers 2

为什么嵌套函数会记住第一个值2,即使maker()在调用时已经返回并退出action()

4

9 回答 9

40

您基本上是在创建一个闭包

在计算机科学中,闭包是一个一流的函数,具有绑定在词法环境中的自由变量。这样的函数被称为“封闭”了它的自由变量。

相关阅读:闭包:为什么它们如此有用?

闭包只是让函数访问本地状态的一种更方便的方法。

http://docs.python.org/reference/compound_stmts.html

程序员注:函数是一等对象。在函数定义中执行的“def”形式定义了一个可以返回或传递的本地函数。嵌套函数中使用的自由变量可以访问包含 def 的函数的局部变量。有关详细信息,请参阅命名和绑定部分。

于 2010-01-05T12:37:17.340 回答
33

您可以将其视为源自父函数的所有变量都被子函数中的实际值替换。这样,就不需要跟踪父函数的范围来使子函数正确运行。

将其视为“动态创建函数”。

def maker(n):
  def action(x):
    return x ** n
  return action

f = maker(2)
--> def action(x):
-->   return x ** 2

这是 python 中的基本行为,它对多个赋值也是如此。

a = 1
b = 2
a, b = b, a

Python 将其读取为

a, b = 2, 1

它基本上在对它们进行任何操作之前插入这些值。

于 2010-01-05T12:41:12.617 回答
15

您正在定义两个函数。你打电话时

f = maker(2)

您正在定义一个返回两倍数字的函数,所以

f(2) --> 4
f(3) --> 6

然后,您定义另一个不同的功能

g = maker(3)

返回三倍的数字

g(3) ---> 9

但它们是两个不同的函数,引用的函数不是同一个函数,每一个都是独立的。即使在函数'maker'的范围内调用相同,也不是相同的函数,每次调用maker()时都定义了不同的函数。它就像一个局部变量,每次调用该函数时都取相同的名称,但可以包含不同的值。在这种情况下,变量“action”包含一个函数(可以不同)

于 2010-01-05T12:37:54.363 回答
9

这就是所谓的“闭包”。简而言之,对于将函数视为第一类对象的大多数(如果不是全部)编程语言,只要函数仍然存在,函数对象中使用的任何变量都会被包含(即记住)。如果您知道如何使用它,这是一个强大的概念。

在您的示例中,嵌套action函数使用变量n,因此它围绕该变量形成一个闭包,并为以后的函数调用记住它。

于 2010-01-05T12:39:41.733 回答
6

让我们看一下编写内部函数的三个常见原因。

1. 闭包和工厂函数

即使变量超出范围或函数本身已从当前命名空间中删除,封闭范围内的值也会被记住。

def print_msg(msg):
    """This is the outer enclosing function"""

    def printer():
        """This is the nested function"""
        print(msg)

    return printer  # this got changed

现在让我们尝试调用这个函数。

>>> another = print_msg("Hello")
>>> another()
Hello

这很不寻常。该print_msg()函数是用字符串调用的,"Hello"并且返回的函数绑定到 name another。在调用another()时,尽管我们已经完成了print_msg()函数的执行,但仍然记得该消息。这种将一些数据 ( "Hello") 附加到代码的技术在 Python 中称为闭包。

何时使用闭包?

那么闭包有什么用呢?闭包可以避免使用全局值并提供某种形式的数据隐藏。它还可以为该问题提供面向对象的解决方案。当一个类中要实现的方法很少(大多数情况下是一个方法)时,闭包可以提供另一种更优雅的解决方案。参考

2.封装:

封装的一般概念是隐藏和保护内部世界免受外部世界的影响,这里内部函数只能在外部世界内部访问,并且不受函数外部发生的任何事情的影响。

3.保持干燥

也许您有一个巨大的函数,它在许多地方执行相同的代码块。例如,您可能编写一个处理文件的函数,并且您希望接受打开的文件对象或文件名:

def process(file_name):
    def do_stuff(file_process):
        for line in file_process:
            print(line)
    if isinstance(file_name, str):
        with open(file_name, 'r') as f:
            do_stuff(f)
    else:
        do_stuff(file_name)

有关更多信息,您可以参考博客。

于 2015-09-07T11:11:50.927 回答
2

因为在您创建函数时nwas 2,所以您的函数是:

def action(x):
    return x ** 2

当您调用 f(3) 时,x设置为3,因此您的函数将返回3 ** 2

于 2010-01-05T12:42:20.930 回答
1

人们对闭包的回答是正确的,即:动作内部“n”的有效值是它在调用“maker”时的最后一个值。

克服这个问题的一种简单方法是让你的 freevar (n) 成为“action”函数中的一个变量,它在运行时接收“n”的副本:

最简单的方法是在创建时将“n”设置为默认值为“n”的参数。“n”的这个值保持不变,因为函数的默认参数存储在一个元组中,该元组是函数本身的一个属性(在这种情况下为 action.func_defaults):

def maker(n):
    def action(x, k=n):
        return x ** k
    return action

用法:

f = maker(2) # f is action(x, k=2)
f(3)   # returns 3^2 = 9
f(3,3) # returns 3^3 = 27
于 2010-01-05T13:13:08.960 回答
1

一种用途是返回一个维护参数的函数。

def outer_closure(a):
    #  parm = a               <- saving a here isn't needed
    def inner_closure():
        #return parm
        return a              # <- a is remembered 
    return inner_closure

# set parm to 5 and return address of inner_closure function
x5 = outer_closure(5)
x5()
>5

x6 = outer_closure(6)
x6()
>6

# x5 inner closure function instance of parm persists 
x5()
>5
于 2017-02-12T21:28:59.847 回答
0

当您使用 def 关键字创建函数时,您就是在这样做:您正在创建一个新的函数对象并将其分配给一个变量。在您提供的代码中,您将该新函数对象分配给一个名为 action 的局部变量。

当您第二次调用它时,您正在创建第二个函数对象。所以 f 指向第一个函数对象(平方值),g 指向第二个函数对象(立方值)。当 Python 看到“f(3)”时,它的意思是“执行指向变量 f 的函数对象并将值 3 传递给它”。f 和 g 以及不同的函数对象,因此返回不同的值。

于 2010-01-05T16:04:01.857 回答