【问题标题】:Don't understand the inner function in python不明白python中的内部函数
【发布时间】:2018-09-24 14:30:03
【问题描述】:

我有这个问题:

cons(a, b) 构造一个对,car(pair)cdr(pair) 返回该对的第一个和最后一个元素。例如,car(cons(3, 4)) 返回3cdr(cons(3, 4)) 返回4

鉴于这种缺点的实现:

def cons(a, b):
    def pair(f):
        return f(a, b)
    return pair

实现carcdr

我不明白这个功能。

它有一个内部函数,并在返回中调用另一个函数。据我了解,内部功能是这些功能应该依赖于它们上面的功能。在这种情况下,cons(..).

但该函数没有使用ab。为什么函数f 在那里?任务是实现carcdr,并给出函数f

那么有人可以解释一下这个功能吗?我该如何开始这项任务?

【问题讨论】:

  • IMO,cons 应该返回一个元组,而不是一个函数
  • @cricket_007: 但cons() 是他们任务的一部分。所以cdr()car() 必须传入一个函数,该函数接受传递的第一个或最后一个参数。
  • @Martijn 对,只是很难考虑除了cons(a, b)(func)之外你会怎么称呼它
  • @cricket_007 正是你会这样称呼它;它是 lambda 演算的实现,而函数是您拥有的 only 类型的值。 没有元组数据结构,只有被定义为遵守car(cons(a, b)) == acons(car(f), cdr(f)) == f 等法律的函数。

标签: python function parameters


【解决方案1】:

首先:Python 函数对象是第一类对象def 语句会生成一个新的函数对象,您可以使用函数名称检索该对象:

>>> def foo():
...     return 'foo was called'
...
>>> foo
<function foo at 0x11b3d8ae8>

这意味着您也可以将该对象分配给其他名称,并且可以将它们作为参数传递给函数调用。然后,您可以稍后通过将 (...) 添加到引用来调用函数对象:

>>> bar = foo
>>> bar()
'foo was called'

函数名分配给当前命名空间。在模块中,这是全局变量,但在诸如cons 之类的函数中,名称被添加为局部变量。然后cons函数中的return pair将函数对象pair返回给调用者。

fab等函数参数也是变量;如果你传入一个函数对象作为参数,那么parameter_name(...) 将调用paramater_name 并传入... 部分中的任何参数。 f(a, b) 调用f 并传入ab

下一个要了解的项目是closures;闭包是附加到函数对象的额外命名空间,用于来自周围范围的变量。

在以下示例中,spambar 函数的闭包中的名称:

>>> 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() 可以通过其闭包访问参数ab

>>> 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,并调用该参数,传入ab。让我们看看当我们传入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_81a = 42c = 81 存储在闭包中,并将使用它们调用给定对象 f这两个论点。

接下来是cdr()car(),它们将返回一对元素的第一个或最后一个元素。如果cons(a, b) 产生pair(f) 返回f(a, b),则cdr()car() 必须各自创建一个函数以传递给pair(),该函数将提取ab

因此,在每个函数中创建一个嵌套函数,并让 cdr()car() 使用该函数调用 pair()。嵌套函数负责选择ab,并返回该值。然后将调用结果返回给外界:

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),返回acar() 可以将其返回给调用者:

>>> car(cons(42, 81))
42

同样适用于pair(return_last),只是现在返回b

>>> cdr(cons(42, 81))
81

您可能对这些操作的背景感兴趣; carcdrcons 来自 LISP,其中cons a b构造一个带有两个指针的单元格(解释名称)和car(表示在 IBM 704 指令集 LISP 中创建的寄存器编号的地址部分)和cdr(意思是 704 语言中寄存器编号的减量部分的内容)取第一个和这种细胞的其余部分。见this Wikipedia article on the names

【讨论】:

  • 感谢您的详细解释。我认为获得cons(3,4)(lambda a, b: a) 更容易,但我很难真正编写适当的嵌套函数,允许内部函数访问 car 和 cdr 中的 (3,4) 等原始参数
【解决方案2】:

这称为闭包。两个基本要素

  • pair 函数知道ab 的值。在这方面它们就像局部变量一样
  • cons 函数返回一个函数,而不是一个值。您必须再次调用结果

所以,当您调用 cons(a, b) 时,您会得到一个对 ab 执行某些操作的函数,只是它还不知道是什么。你必须为此传递另一个函数。例如

 my_f = cons(1, 2)
 result = my_f(lambda x, y: x + y)
 print(result) # 3

这与你的任务有什么关系还不是很清楚。我只假设对于car,您只需要元素a(头部),对于cdr,您需要元素b(列表的其余部分)。因此,这些函数将只返回一个参数而忽略另一个参数。例如

car_f = lambda x, y: x
cdr_f = lambda x, y: y

【讨论】:

  • 这是 lambda 演算中的实现元组; cons 返回元组的函数编码,您可以通过在适当的访问器上调用元组来检索元组的第一个或第二个元素。它与通常的以数据为中心的实现相反。你不打电话给car(some_tuple);你打电话给some_tuple(car)
  • @chepner: 嗯,好吧,这样更有意义。
【解决方案3】:

cons 是一个函数,它接受两个参数 ab 并返回一个函数 pair

函数pair 将函数f 作为参数,它使用两个参数。

def cons(a, b):
    def pair(f):
        return f(a, b)
    return pair

f = lambda n, m: n**2 + m**3
car = lambda n, m: n
cdr = lambda n, m: m
print(cons(2, 3)(f))
print(cons(2, 3)(car))
print(cons(2, 3)(cdr))

f 返回31 = 2**2 + 3**3

注意cons 有两次括号(...) - 一次用于它自己的调用,另一次用于返回的函数调用。

请注意this answer 以便能够致电car(cons(2, 3))。你可能也对Why would a program use a closure?感兴趣

【讨论】:

  • 但是,在赋值的上下文中,你永远不会在像f 这样的函数上真正调用cons(2,3);其目的是将carcdr 作为参数从元组中检索适当的值。
  • (不过,您仍然希望能够调用 car(cons(2,3))carcdr 需要额外的间接级别才能实现。)
  • @chepner 我已经在 Martijn Pieters 的回答中添加了评论
【解决方案4】:

我手动将cons函数转换为Javascript版本,然后实现了测验:

cons(a, b) 构造一个对,car(pair) 和 cdr(pair) 返回该对的第一个和最后一个元素。例如,car(cons(3, 4)) 返回 3,cdr(cons(3, 4)) 返回 4。鉴于 cons 的这种实现:

def cons(a, b):
    def pair(f):
        return f(a, b)
    return pair

实现 car 和 cdr。

解决办法

function cons(a, b) {
  function pair(f) {
    return f(a, b);
  }
  return pair;
}

function car(pair) {
 return pair((a, b) => a);
};

function cdr(pair) {
 return pair((a, b) => b);
};

console.log(car(cons(3,4)));
console.log(cdr(cons(3,4)));

【讨论】:

    【解决方案5】:

    使用 lambda 表达式

    def cons(a, b):
        def pair(f):
            return f(a, b)
    
        return pair
    
    def car(pair):
        return pair(lambda a, b: a)
    
    def cdr(pair):
        return pair(lambda a, b: b)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-20
      • 2013-12-03
      • 1970-01-01
      • 1970-01-01
      • 2023-03-15
      相关资源
      最近更新 更多