Image

print_sums(1)(3)(5)应该理解为先运行print_sums(1),然后运行print_sums(1)的(3),然后运行print_sums(1)(3)的(5)。

运行步骤f1:n=1,print出来一个1,然后定义了一个新的函数next_sum(n+k),最后return这个function next_sum(k),于是我们立马就得到一个新的框架f2,在这里面call了next_sum(k),并且k是3。

f2的parent是f1,因为f1call的是print_sums(n),f2call的是next_sum(k) ,在代码中,print_sums(n) 是主函数,而 next_sum(k) 是在 print_sums(n) 函数内部定义的。当你调用 next_sum(3) 时,它在 print_sums 函数内部作为局部函数被执行,所以会创建一个新的框架 f2,这个框架是为 next_sum(k) 函数分配的。

call到next_sum了于是来到第三行,(n+k)其中的n没有在f2里定义,但是在其parent的f1里面定义了,所以下一行会是print_sums(4),于是运行第四行,是个return,调用的是print_sums函数,注意,parent是根据你调用的这个函数来走的,所以f3的parent是global(根据print_sums来判定的)。

调用print_sums,于是来到第一行,运行print_sums(4),第二行print(4)输出4,第三行定义next_sum,注意此时next_sum是在f3作为parent的环境里面定义的,里面参数要用 f3的了,感觉这一步就是递归的精髓。于是就是在f4框架运行 next_sum(5),5是第七行那个5,来到第三行,在第四行创建框架f5运行print_sum(4+5),来到第一行,最后print(9)。最后的返回值其实是fun next_sum(k) [parent = f5],但没用上而已。

PPT上的解释:

Image


下一个话题,柯里化函数(currying)如果要深入研究就自行搜这个词

Image
这段代码展示了如何使用“柯里化”(currying)将一个接受两个参数的函数转化为一个接受一个参数并返回另一个接受一个参数的函数的形式。

解释:

  1. 柯里化函数(curry2

    • curry2(f) 是一个函数,它接受一个函数 f,并返回一个新的函数。
    • 这个返回的函数接受一个参数 x,并且返回另一个函数,这个函数接受参数 y。然后,它使用原始的函数 f(x, y) 计算结果。
  2. Lambda 表达式

    • lambda x: lambda y: f(x, y) 是一个嵌套的 lambda 表达式,它的作用是将 f 的两个参数分开处理。
    • lambda x 表示接受第一个参数 x,然后返回一个新的函数 lambda y,该函数接受第二个参数 y。然后,计算 f(x, y)
  3. 代码示例

    • from operator import add 导入 Python 的 add 函数,它是一个简单的加法函数。
    • curry2(add)add 函数进行柯里化处理,使其变成接受一个参数的函数。
    • curry2(add)(30) 返回一个新的函数,这个新函数接受参数 y,并计算 add(30, y)
    • curry2(add)(30)(12) 最终计算的是 add(30, 12),即 30 + 12

输出解释:

通过柯里化,你可以将多参数函数转化为一系列只接受一个参数的函数。这种方式使得你可以部分应用函数(即提前给定部分参数)。

第二行的代码是:

print(curry2(add)(30))

这个打印的结果是一个 函数对象,而不是一个具体的值。原因如下:

因此,第二行的输出是类似于:

<function <lambda> at 0x...>

它表示一个 lambda 函数的内存地址。

为了看得清楚,他把lambda x和lambda y写到两行去,下面是只运行第一个print的时候的情况

Image

下面是运行第二个print的情况

Image


下一个话题,函数的哲学和规范

Image

这张图展示了函数的哲学和规范,重点讲解了 语法规范(Syntactic specification)语义规范(Semantic specification) 的概念。具体解释如下:

1. 语法规范(Syntactic specification)

2. 语义规范(Semantic specification)

3. 合同(Contract)

总结:


简单线性递归

Image

这张图展示了简单线性递归的概念,并通过一个示例函数 sum_squares(N) 来演示。以下是详细解释:

1. 递归函数 sum_squares(N)

2. 递归展开示例

3. 递归的本质

4. 环境框架

总结:


尾递归 tail recurtion

Image

这张图展示了**尾递归(Tail Recursion)**的概念,并通过一个平方和的例子进行了比较。以下是对图中内容的解释:

  1. 尾递归(Tail Recursion):
    尾递归是递归的一种特殊形式,其中递归调用是函数的最后一步,即递归调用是函数的返回值,或者是递归调用之后没有更多的操作。
    在尾递归中,递归调用不需要保持函数调用栈上的额外信息,因为递归调用的返回值就是最终结果,这使得尾递归比普通递归更高效。

  2. 两种实现方式:
    左边的版本:使用 while 循环实现平方和的计算。
    右边的版本:使用尾递归实现平方和的计算,带有 part_sum 函数。

这段代码展示了两种不同的实现方式来计算整数的平方和。我们可以逐行理解这段代码:

第一部分:使用 while 循环实现的平方和

def sum_squares(N):
    """The sum of K**2 for 1 <= K <= N."""
    accum = 0
    k = 1
    while k <= N:
        accum = accum + k**2
        k = k + 1
        print("accum =", accum, "/ k =", k)
    return accum
print(sum_squares(3))

1. sum_squares(N) 函数:

2. 代码分析:

3. sum_squares(3) 执行:

第二部分:递归实现的平方和

def sum_squares(N):
    def part_sum(accum, k):
        if k <= N:
            return part_sum(accum + k**2, k + 1)
        else:
            return accum
    return part_sum(0, 1)
print(sum_squares(3))

1. sum_squares(N) 函数:

2. 代码分析:

3. sum_squares(3) 执行:

最终返回值为 14,即 1^2 + 2^2 + 3^2 = 14

总结:

  1. 尾递归的特点:
    右边的递归实现是尾递归,因为递归调用是函数的最后一步,返回的就是递归调用的结果。
    尾递归的好处是它可以被编译器或解释器优化为迭代过程,从而减少栈空间的使用,避免栈溢出。
  2. 总结:
    尾递归与普通递归的主要区别在于递归调用是否是最后执行的操作。在尾递归中,递归调用不需要保留函数调用栈中的额外信息,因此可以进行优化,减少内存消耗。
    该技巧也可以应用到 while 循环中,将其转化为递归形式。

下一个话题:防止无限递归的良基

Image

在这张图中,提到了 "well founded"(良基)的概念,它是防止无限递归的一种方法。

解释:

例子:

未来的例子:

总结来说,良基是递归中保证结束的一个重要条件,通过确保每一步递归都能逐渐缩小,避免了无限递归的风险。