首先:Python 函数对象是第一类对象。 def 语句会生成一个新的函数对象,您可以使用函数名称检索该对象:
>>> def foo():
... return 'foo was called'
...
>>> foo
<function foo at 0x11b3d8ae8>
这意味着您也可以将该对象分配给其他名称,并且可以将它们作为参数传递给函数调用。然后,您可以稍后通过将 (...) 添加到引用来调用函数对象:
>>> bar = foo
>>> bar()
'foo was called'
函数名分配给当前命名空间。在模块中,这是全局变量,但在诸如cons 之类的函数中,名称被添加为局部变量。然后cons函数中的return pair将函数对象pair返回给调用者。
而f、a、b等函数参数也是变量;如果你传入一个函数对象作为参数,那么parameter_name(...) 将调用paramater_name 并传入... 部分中的任何参数。 f(a, b) 调用f 并传入a 和b。
下一个要了解的项目是closures;闭包是附加到函数对象的额外命名空间,用于来自周围范围的变量。
在以下示例中,spam 是 bar 函数的闭包中的名称:
>>> def foo():
... spam = 'Vikings'
... def bar():
... return spam
... return bar
...
>>> foo()
<function foo.<locals>.bar at 0x11b44bf28>
>>> foo()()
'Vikings'
调用foo()会返回一个新的函数对象; foo() 内部的 bar() 函数。调用返回的函数对象会产生'Vikings',即foo 函数中spam 变量的值。 bar() 是如何访问它的?通过闭包:
>>> foo().__closure__
(<cell at 0x11b3c05b8: str object at 0x11b469180>,)
>>> foo().__closure__[0].cell_contents
'Vikings'
所以嵌套函数可以通过闭包访问周围作用域的名称。 (旁注:不是 value 存储在闭包中,而是 variable。变量可以随时间变化,就像稍后访问该名称的其他变量将反映新值;如果稍后更改了 spam,再次调用 bar() 将返回新值。
现在到您的函数:cons() 创建一个内部函数pair(),并且pair() 可以通过其闭包访问参数a 和b:
>>> def cons(a, b):
... def pair(f):
... return f(a, b)
... return pair
...
>>> cons(42, 81)
<function cons.<locals>.pair at 0x11b46f048>
>>> pair_42_81 = cons(42, 81)
>>> pair_42_81.__closure__
(<cell at 0x11b3c02b8: int object at 0x10f59a750>, <cell at 0x11b3c05b8: int object at 0x10f59ac30>)
>>> pair_42_81.__closure__[0].cell_contents
42
>>> pair_42_81.__closure__[1].cell_contents
81
pair() 函数接受一个参数f,并调用该参数,传入a 和b。让我们看看当我们传入print 时会发生什么。
print 也是一个函数,它是一个可以调用的对象,它将参数写入控制台,中间有空格:
>>> print
<built-in function print>
>>> print('arg1', 'arg2')
arg1 arg2
如果你将它传递给cons() 返回的pair() 函数,你可以看到f(a, b) 做了什么:
>>> pair_42_81(print)
42 81
print,传递给pair(),赋值给f,而f(a, b)与print(a, b)完全一样。
我们可以看到 print() 被调用是因为值被写入控制台。但您也可以创建一个返回新值的函数。假设您有一个将两个数字相加并返回该值的函数:
>>> def add(first, second):
... return first + second
...
>>> add(42, 81)
123
>>> pair_42_81(add)
123
我们可以直接调用函数,返回123,也可以让pair_42_81()为我们做,同样的结果返回给我们。简单!
所有这些都行得通,因为函数是对象,可以像其他变量一样传递,并且因为 pair_42_81 将 a = 42 和 c = 81 存储在闭包中,并将使用它们调用给定对象 f这两个论点。
接下来是cdr() 和car(),它们将返回一对元素的第一个或最后一个元素。如果cons(a, b) 产生pair(f) 返回f(a, b),则cdr() 和car() 必须各自创建一个函数以传递给pair(),该函数将提取a 或b。
因此,在每个函数中创建一个嵌套函数,并让 cdr() 和 car() 使用该函数调用 pair()。嵌套函数负责选择a 或b,并返回该值。然后将调用结果返回给外界:
def car(pair):
def return_first(a, b):
return a
return pair(return_first)
def cdr(pair):
def return_last(a, b):
return b
return pair(return_last)
pair(return_first) 调用return_first(a, b),返回a,car() 可以将其返回给调用者:
>>> car(cons(42, 81))
42
同样适用于pair(return_last),只是现在返回b:
>>> cdr(cons(42, 81))
81
您可能对这些操作的背景感兴趣; car、cdr 和 cons 来自 LISP,其中cons a b构造一个带有两个指针的单元格(解释名称)和car(表示在 IBM 704 指令集 LISP 中创建的寄存器编号的地址部分)和cdr(意思是 704 语言中寄存器编号的减量部分的内容)取第一个和这种细胞的其余部分。见this Wikipedia article on the names。