python函数中的奇怪返回值

时间:2018-08-22 15:23:28

标签: python python-3.x

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

def car(f):
    def left(a, b):
        return a
    return f(left)

def cdr(f):
    def right(a, b):
        return b
    return f(right)

在git上找到了这个python代码。

只想知道cons定义中的f(a,b)是什么,它是如何工作的? (我猜不是一个功能)

2 个答案:

答案 0 :(得分:3)

cons是一个带有两个参数的函数,并返回一个带有另一个将使用这两个参数的函数的函数。

例如,考虑以下功能:

def add(a, b):
    return a + b

这只是一个将两个输入相加的函数,例如add(2, 5) == 7

由于此函数有两个参数,因此我们可以使用cons来调用此函数:

func_caller = cons(2, 5)  # cons receives two arguments and returns a function, which we call func_caller

result = func_caller(add) # func_caller receives a function, that will process these two arguments

print(result)             # result is the actual result of doing add(2, 5), i.e. 7 

此技术对于包装函数和在调用适当函数的之前之后中执行内容很有用。

例如,我们可以修改cons函数,以在调用add之前和之后实际打印值:

def add(a, b):
    print('Adding {} and {}'.format(a, b))
    return a + b


def cons(a, b):
    print('Received arguments {} and {}'.format(a, b))
    def pair(f):
        print('Calling {} with {} and {}'.format(f, a, b))
        result = f(a, b)
        print('Got {}'.format(result))
        return result
    return pair

通过此更新,我们得到以下输出:

 func_caller = cons(2, 5)
 # prints "Received arguments 2 and 5" from inside cons

 result = func_caller(add)
 # prints "Calling add with 2 and 5" from inside pair
 # prints "Adding 2 and 5" from inside add
 # prints "Got 7" from inside pair

答案 1 :(得分:3)

除非您知道conscarcdr的含义,否则这对您毫无意义。

在Lisp中,列表以链接列表的非常简单的形式存储。列表可以是nil(例如None)以表示一个空列表,也可以是一对值和另一个列表。 cons函数获取一个值和一个列表,然后仅需配对即可返回另一个列表:

def cons(head, rest):
    return (head, rest)

还有carcdr函数(它们代表“地址|数据寄存器的内容”,因为它们是用于在特定1950年代的计算机上实现它们的汇编语言指令,但这不是不太有用)返回一对中的第一个或第二个值:

def car(lst):
    return lst[0]
def cdr(lst):
    return lst[1]

因此,您可以列出一个列表:

lst = cons(1, cons(2, cons(3, None)))

...,您可以从中获取第二个值:

print(car(cdr(lst))

…,您甚至可以编写函数来获取第n个值:

def nth(lst, n):
    if n == 0:
        return car(lst)
    return nth(cdr(lst), n-1)

…或打印出整个列表:

def printlist(lst):
    if lst:
        print(car(lst), end=' ')
        printlist(cdr(lst))

如果您了解它们的工作原理,那么下一步就是尝试使用发现的那些奇怪的定义。

他们仍然做同样的事情。所以,问题是:如何?更大的问题是:有什么意义?

好吧,使用这些奇怪的功能没有实用的意义。真正的要点是向您展示计算机科学中的所有内容都可以只用函数编写,而不能像元组(甚至整数)那样使用内置数据结构编写。

键是高阶函数:将函数作为值和/或返回其他函数的函数。您实际上一直在使用这些内容:mapsortkey,修饰符,partial…它们只是在它们非常简单时才令人困惑:

def car(f):
    def left(a, b):
        return a
    return f(left)

这需要一个函数,并在返回其两个参数中第一个参数的函数上调用它。

cdr类似。

在看到cons之前,很难看清如何使用这两种方法:

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

这需要两件事,并返回一个函数,该函数需要另一个函数并将其应用于这两件事。


那么,我们从cons(3, None)得到什么呢?我们得到一个带有函数的函数,并将其应用于参数3None

def pair3(f):
    return f(3, None)

如果我们打电话给cons(2, cons(3, None))

def pair23(f):
    return f(2, pair3)

如果在该函数上调用car会发生什么?跟踪它:

def left(a, b):
    return a
return pair23(left)

pair23(left)会这样做:

return left(2, pair3)

left很简单:

return 2

因此,我们得到了(2, cons(3, None))的第一个元素。


如果您致电cdr,怎么办?

def right(a, b):
    return a
return pair23(right)

pair23(right)会这样做:

return right(2, pair3)

…和right非常简单,因此只返回pair3

您可以得出结论,如果我们调用car(cdr(pair23)),我们将把其中的3排除掉。

现在您可以编写lst = cons(1, cons(2, cons(3, None))),编写上面的递归nthprintlist函数,并跟踪它们在lst上的工作方式。


我在上面提到过,您甚至可以摆脱整数。你是怎样做的?了解有关Church numerals的信息。您定义zerosuccessor函数。然后,可以将one定义为successor(zero),将two定义为successor(one)。您甚至可以递归定义{{​​1}},以使addadd(x, zero),而xadd(x, successor(y)),然后继续定义successor(add(x, y)),以此类推。< / p>

您还需要一个特殊的函数,可以用作mul的值。

无论如何,使用上面的所有其他定义完成此操作后,您可以执行nil,而lst = cons(zero(cons(one, cons(two, cons(three, nil))))会给您nth(lst, two)的回馈。 (当然,写one会比较棘手……)


显然,这将比仅使用元组和整数等慢很多。但从理论上讲,这很有趣。

考虑一下:我们可以写一个Python的小方言,它只有三种语句-printlistdef和表达式语句-仅有三种表达式-文字,标识符和函数调用-它可以完成普通Python所做的一切。 (实际上,仅通过具有Python已有的函数定义表达式就可以完全摆脱语句。)那种小语言很难使用,但是编写一个程序来推理程序会容易得多用那种小语言。而且我们甚至都知道如何使用元组,循环等将代码转换为这种微小的子集语言的代码,这意味着我们可以编写一个程序来编写真正的Python代码。

实际上,有了更多技巧(咖喱函数和/或静态函数类型以及惰性求值),编译器/解释器可以即时进行这种推理并为我们优化代码。很容易以编程方式告诉您return将返回3,而不必实际评估大多数这些函数调用,因此我们可以跳过对它们的评估,而将car(cdr(cons(2, cons(3, None))替换为整个表达式。

当然,如果有任何功能会产生副作用,这种情况就会崩溃。显然,您不能仅用3代替None并获得相同的结果。因此,您需要一些巧妙的技巧,其中由一些魔术对象处理IO,该魔术对象评估函数以确定应该读取和写入的内容,然后整个程序的其余部分,即用户编写的部分,将变得纯净并可以优化随你怎么便。再加上几个抽象,我们甚至可以使IO变得不必要地神奇。

然后,您可以构建一个标准库,该库可以将定义和调用函数方面的所有工作全部归还给您,因此它实际上是可用的,但实际上它只是减少了纯函数调用,因此简单到足以让计算机进行优化。然后,您基本上已经编写了Haskell。