Chapter 5
这里得写点东西。
5.1 函数是一等对象
一等对象,就是满足以下条件的程序实体:
- 在运行时创建
- 能赋值给变量或数据结构中的元素
- 能作为参数传给函数
- 能作为函数的返回结果
如:整数、字符串和字典都是一等对象,Python中函数也是 一等对象 。
也就是说,在 Python 中,函数可以赋值给变量(可以在赋值符,即 =,右侧),也可以作为参数传给其他函数。
例 5-1:通过别的名称使用函数,再把函数作为参数传递
# 以下代码在 Python 控制台(或 IDLE shell)中运行
def factorial(n):
"""
returns n!
"""
return 1 if n < 2 else n * factorial(n - 1)
factorial(5)
# 120
factorial.__doc__
# returns n!
type(factorial)
# <class 'functioin'>
fact = factorial
fact
# <function factorial at 0x...>
fact(5)
# 120
map(factorial, range(11))
# <map object at 0x...>
list(map(fact, range(11)))
# [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
函数式风格编程,其特点之一是使用==高阶函数==。
5.2 高阶函数
一个函数的参数为另一个函数,或者这个函数的返回值为另一个函数,则称这个函数为==高阶函数(higher-order function)==。map 函数就是高阶函数,另外, sorted 内置函数也是,给 key 参数传递一个函数,就会作用到各个元素上进行排序,如 例 5-2-1。
例 5-2-1:根据单词长度给一个列表排序
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
print(sorted(fruits, key=len))
# ['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']
对于 sorted 函数的 key 参数,任何单参数函数都能作为 key 参数的值。如 例 5-2-2。
例 5-2-2:根据反向拼写,给一个单词列表排序。为了创建押韵词典,可以把各个单词反过来拼写,然后排序。
def reverse(word):
reutrn word[::-1]
reverse("testing")
# 'gnitset'
sorted(fruits, key=reverse)
# ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
常用的高阶函数。函数编程范式中,常用的高阶函数有 map、filter、reduce 和 apply。Python 3 中 apply 移除, map、filter、reduce 还能用,但有更好的替代品。
列表推导和生成器表达式替代 map 和 filter。列表推导和生成器表达式具有 map 和 filter 两个函数的功能,且更易于阅读。
例 5-2-3:计算阶乘列表。 map、filter 与列表推导比较。
list(map(fact, range(6)))
# [1, 1, 2, 6, 24, 120]
[fact(n) for n in range(6)]
# [1, 1, 2, 6, 24, 120]
list(map(factorial, filter(lambda n: n % 2, range(6))))
# [1, 6, 120]
[factorial(n) for n in range(6) if n % 2]
# [1, 6, 120]
sum 函数替代 reduce 函数。Python 3 中 reduce 不在是内置函数,放在了 functools 模块中,这个函数常用于求和。Python 3 中最好使用内置的 sum 函数。
例 5-2-4:使用 reduce 和 sum 计算 0~99 之和。
from functools import reduce
from operator import add
reduce(add, range(100))
# 4950
sum(range(100))
# 4950
sum 和 reduce 的通用思想是把某个操作连续应用到序列的元素上,累计之前的结果,把一系列值归约成一个值。这种函数称为 归约函数 ,all 和 any 也是内置的归约函数。
5.3 匿名函数
lambda 关键字在 Python 表达式内创建 匿名函数 。虽然很方便,但 lambda 函数的定义体只能使用 纯表达式 ,也就是说, lambda 函数的定义体中 不能赋值,也不能使用 while 和 try 等 Python 语句。
例 5-3-1:使用 lambda 表达式反转拼写,然后依此给单词列表排序。修改 例 5-2-2
sorted(fruits, key=lambda word: word[::-1])
# ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
5.4 可调用对象
7 种可调用对象:
- 用户定义的函数。
def或lambda表达式创建 - 内置函数。如
len或time.strftime - 内置方法。如
dict.get - 方法。在类的定义体中定义的函数
- 类。
- 类的实例。
- 生成器函数。使用
yield关键字的函数或方法。调用生成器函数返回的是生成器对象。
可以使用内置 callable() 函数,判断对象是否能调用。
[callable(obj) for obj in (abs, str, 13)]
# [True, True, False]
5.5 用户定义的可调用类型
Python 中,不仅函数是真正的对象,任何对象都可以表现得像函数,可以被调用,只需要 实现实例方法 __call__ 。
例 5-5-1:调用 BingoCage 实例,从打乱的列表中取出一个元素。
import random
class BingoCage:
def __init__(self, items):
self._items = list(items)
random.shuffle(self._items)
def pick(self): # 1
try:
return self._items.pop()
except IndexError:
raise LookupError("Pick from Empty BingoCage")
def __call__(self): # 2
return self.pick() # 3
因为 BingoCage 类中实现了 __call__ 方法(# 2),并且这个方法直接调用了 pick 方法(# 3 -> # 2),所以 BingoCage 的实例调用 pick 方法有两种,一种是通用调用方法,实例名.pick(),另一种是直接用实例名调用,实例名()。代码如下:
bingo = BingoCage(range(3))
bingo.pick() # 实例名.pick() 调用
bingo() # 实例名() 直接调用
上例中,bingo() 实现直接调用 bingo.pick() 的底层逻辑:bingo()直接调用的是 __call__() 方法,而 __call__() 又调用了类方法 pick(),最终实现了 bingo() 调用类方法 pick()。