Chapter 5

这里得写点东西。

5.1 函数是一等对象

一等对象,就是满足以下条件的程序实体:

  1. 在运行时创建
  2. 能赋值给变量或数据结构中的元素
  3. 能作为参数传给函数
  4. 能作为函数的返回结果
    如:整数、字符串和字典都是一等对象,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']

常用的高阶函数。函数编程范式中,常用的高阶函数有 mapfilterreduceapplyPython 3apply 移除, mapfilterreduce 还能用,但有更好的替代品。

列表推导和生成器表达式替代 map filter。列表推导和生成器表达式具有 mapfilter 两个函数的功能,且更易于阅读。

例 5-2-3:计算阶乘列表。 mapfilter 与列表推导比较。

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 3reduce 不在是内置函数,放在了 functools 模块中,这个函数常用于求和。Python 3 中最好使用内置的 sum 函数。

例 5-2-4:使用 reducesum 计算 0~99 之和。

from functools import reduce
from operator import add
reduce(add, range(100))
# 4950
sum(range(100))
# 4950

sumreduce 的通用思想是把某个操作连续应用到序列的元素上,累计之前的结果,把一系列值归约成一个值。这种函数称为 归约函数allany 也是内置的归约函数。

5.3 匿名函数

lambda 关键字在 Python 表达式内创建 匿名函数 。虽然很方便,但 lambda 函数的定义体只能使用 纯表达式 ,也就是说, lambda 函数的定义体中 不能赋值也不能使用 whiletryPython 语句

例 5-3-1:使用 lambda 表达式反转拼写,然后依此给单词列表排序。修改 例 5-2-2

sorted(fruits, key=lambda word: word[::-1])
# ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

5.4 可调用对象

7 种可调用对象:

  1. 用户定义的函数。deflambda 表达式创建
  2. 内置函数。如 len time.strftime
  3. 内置方法。如 dict.get
  4. 方法。在类的定义体中定义的函数
  5. 类。
  6. 类的实例。
  7. 生成器函数。使用 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()