仅用作个人学习记录
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()。
函数相关术语
- 定义域:一个函数的可能参数值的集合,调用函数时的有效参数
- 值域:函数可以返回的值集,受值域影响
- 陪域(Codomain)又称上域、到达域。 函数的上域是一个比选择值域范围更大的集合。
例如,我们可以说平方函数的定义域是实数,范围是非负数。我们可以选择将其上域描述为实数或仅描述为非负实数。
函数文档注释
理想情况下,函数的文档注释提供了足够的信息,以便程序员可以正确使用该函数并理解它的作用,而无需阅读其代码主体。
文档注释应该明确哪些输入在什么条件下是有效的,是可以调用函数的。还应该明确对于正确的输入,函数的结果输出或效果是什么。这些共同构成了函数的行为或语义。
两条函数设计原则
-
函数应该做一件明确定义的事情(复杂的文档注释可能暗示你的函数做的太多)。
-
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,并未求函数值。