[CS61A课堂记录]Lecture #4:Higher-Order Functions

仅用作个人学习记录
Reference:
https://inst.eecs.berkeley.edu/~cs61a/sp21/
https://www.bookstack.cn/read/sicp-py-zh/

上节课没讲完的例子,寻找一个数的因数:

#  Prime numbers  素数
def is_prime(n):
    """Return True iff N is prime.
    >>> is_prime(1)
    False
    >>> is_prime(2)
    True
    >>> is_prime(8)
    False
    >>> is_prime(21)
    False
    >>> is_prime(23)
    True
    """
    return n > 1 and smallest_factor(n) == n

def smallest_factor(n):
    """Returns the smallest value k>1 that evenly divides N."""
    # The following can be speeded up a great deal!
    k = 2
    while k <= n:
        if n % k == 0:
            return k
        k += 1

def print_factors(n):
    """Print the prime factors of N. # 打印一个数的质因数
    >>> print_factors(180)
    2
    2
    3
    3
    5
    """
    while n > 1:
        d = smallest_factor(n)
        print(d)
        n = n // d     # or n //= d

上述使用了上节介绍的 doctest,使用命令:

python -m doctest <FileName>

Python 中使用 doctest,在运行这些测试前,会先扫描整个文件。这就允许了 doctest 出现在 smallest_factor() 前,还能调用smallest_factor()。

函数相关术语

  1. 定义域:一个函数的可能参数值的集合,调用函数时的有效参数
  2. 值域:函数可以返回的值集,受值域影响
  3. 陪域(Codomain)又称上域、到达域。 函数的上域是一个比选择值域范围更大的集合。

例如,我们可以说平方函数的定义域是实数,范围是非负数。我们可以选择将其上域描述为实数或仅描述为非负实数。

函数文档注释

理想情况下,函数的文档注释提供了足够的信息,以便程序员可以正确使用该函数并理解它的作用,而无需阅读其代码主体。

文档注释应该明确哪些输入在什么条件下是有效的,是可以调用函数的。还应该明确对于正确的输入,函数的结果输出或效果是什么。这些共同构成了函数的行为或语义。

两条函数设计原则

  1. 函数应该做一件明确定义的事情(复杂的文档注释可能暗示你的函数做的太多)。

  2. DRY (Don’t Repeat Yourself)

    看起来非常相似的多个代码段迫切需要重构。 用对单个通用函数的简单调用替换重复段,该通用函数仅声明一次它们的共享结构,并使用用于专门针对各种情况的参数。

作为参数的函数

考虑下面两个函数,它们都计算总和。

sum_naturals 专用于计算截至n的自然数的和:

>>> def sum_naturals(n):
        total, k = 0, 1
        while k <= n:
            total, k = total + k, k + 1
        return total
>>> sum_naturals(100)
5050

sum_cubes,计算截至n的自然数的立方和:

>>> def sum_cubes(n):
        total, k = 0, 1
        while k <= n:
            total, k = total + pow(k, 3), k + 1
        return total
>>> sum_cubes(100)
25502500

这两个函数在背后都具有相同模式。它们大部分相同,只是名字、用于计算被加项的k的函数,以及提供k的下一个值的函数不同。我们可以通过向相同的模板中填充槽位来生成每个函数:

def <name>(n):
    total, k = 0, 1
    while k <= n:
        total, k = total + <term>(k), <next>(k)
    return total

这个通用模板的出现是一个强有力的证据,证明有一个实用抽象正在等着我们表现出来。作为程序的设计者,我们希望我们的语言足够强大,便于我们编写函数来自我表达求和的概念,而不仅仅是计算特定和的函数。我们可以在 Python 中使用上面展示的通用模板,并且把槽位变成形参来轻易完成它。

def summation(n, term, next):
	total, k = 0, 1
	while k <= n:
		total, k = total + term(k), next(k)
	return total

重新表示 sum_naturals():

>>> def identity(k):
        return k
>>> def successor(k):
        return k + 1
>>> def sum_naturals(n):
        return summation(n, identity, successor)
>>> sum_naturals(10)
55

或者,可以创建匿名函数作为参数:

sum_naturals(10, lambda x: x, lambda x: x + 1)

回顾 Lambda 表达式

在 python 中,lambda 只是一个表达式,并不能执行复杂的语句。

lambda PARAMS: EXPRESSION
def Name(PARAMS):
    return EXPRESSION

上述两式等价。注意 lambda 中没有return,主体必须只是一个简单表达式。

Lambda 表达式非常简短和直接,但复合的 Lambda 表达式非常难以辨认,尽管它们很简洁。下面的定义是是正确的,但是不能很快地理解:

compose1 = lambda f,g: lambda x: f(g(x))
def compose1(f,g):
    def func1(x):
        return f(g(x))
    return func1

通常,Python 的代码风格倾向于显式的def语句而不是 Lambda 表达式,但是允许它们在简单函数作为参数或返回值的情况下使用。

定义函数:嵌套定义

词法作用域。局部定义的函数也可以访问它们定义所在作用域的名称绑定。在上述 compose1()中,func1引用了名称f、g,它们是外层函数compose1的形参。这种在嵌套函数中共享名称的规则叫做词法作用域。严格来说,内部函数能够访问定义所在环境(而不是调用所在位置)的名称。

每个用户定义的函数都有一个关联环境:它的定义所在的环境。

当一个用户定义的函数调用时,它的局部帧扩展于函数所关联的环境。

在这里插入图片描述
调用com(256)新建的局部帧的父节点指向 f1 帧,而不是直接指向全局环境。

返回值为函数的函数

函数是 first-class values,这意味着我们可以将它们赋值给变量,将它们传递给函数,并从函数返回它们。

>>> def add_func(f, g):
... """Return function that returns F(x)+G(x) for argument x."""
... 	def adder(x): #
... 		return f(x) + g(x) # or return lambda x: f(x) + g(x)
... 	return adder #
>>> from math import sin, cos, pi
>>> h = add_func(sin, cos)
>>> sin(pi/4) + cos(pi/4)
1.414213562373095
>>> h(pi / 4)
1.414213562373095

让我们做一个通用的函数组合函数:

>>> def combine_funcs(op):
... """combine funcs(OP)(f, g)(x) = OP(f(x), g(x))."""
... 	def combined(f, g):
... 		return lambda x: op(f(x), g(x))
... 	return combined
>>> from operator import add
>>> add_func = combine_funcs(add)
>>> from math import sin, cos, pi
>>> h = add func(sin, cos)
>>> h(pi / 4)
1.414213562373095

在这里插入图片描述

条件函数?

设计一个函数 if_func :

if func(1/x, x > 0, 0)

要求实现条件表达式:

1/x if x > 0 else 0

可以实现吗?

不能!因为函数总会计算全部参数的值,而条件表达式只会计算 TrueExpression、FalseExpression 中的一个。

但是我们可以定义:

def if_func(then_expr, condition, else_expr):
	return then_expr() if condition else else_expr()

然后调用:

if_func(lambda: 1/x, x > 0, lambda: 0)

这些无参数 lambda 的术语是 thunks 形实转换函数。
在这里插入图片描述
在执行if_func(lambda: 1/x, x > 0, lambda: 0)时,只是计算了x>0的值,引入了两个 thunks,并未求函数值。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值