流畅的python--第七章
把函数视为对象
在python中,函数是一等对象。编程语言研究人员把“一等对象”定义为满足以下条件的程序实体:
- 在运行时创建;
- 能赋值给变量或数据结构中的元素;
- 能作为参数传给函数;
- 能作为函数的返回结果。
示例7-1 创建并测试一个函数,读取函数的__doc__
属性,再检查函数的类型
🚩
__doc__
属性用于生成对象的帮助文本.
示例7-2 通过其他名称使用factorial
函数,再把factorial
函数作为参数传递
🚩 示例7-2展示了函数对象的“一等”本性。可以把
factorial
函数赋值给变量fact
,然后通过变量名调用。还可以把factorial
函数作为参数传给map
函数。map(function, iterable)
调用会返回一个可迭代对象,所含的项是把第一个参数(一个函数)应用到第二个参数(一个可迭代对象,这里是range(11))
中各个元素上得到的结果。
高阶函数
接受函数为参数或者把函数作为结果返回的函数是高阶函数(higher-oder function)。示例7-2中的map
函数就是一例。此外,内置函数sorted
也是:通过可选的key
参数提供一个函数,应用到每一项上进行排序。
示例7-3 根据单词长度排序一个列表
🚩 任何单函数都可以作为key参数的值。例如,为了创建押韵字典,可以把各个单词反过来拼写,然后排序。
示例7-4 根据反向拼写排序一个单词列表
🚩 在函数式编程范式中,最为人熟知的高阶函数有map、filter、reduce和apply(python3已经废弃),可以编写
fn(*args, **kwargs)
.
map
、filter
和reduce
的现代替代品。函数式语言通常会提供map
、filter
和reduce
这3个高阶函数,在python3
中,map
和filter还是内置函数,但是由于引入了列表推导式和生成器表达式,因此二者就变得没那么重要了。列表推导式或生成器表达式兼具map
和filter
这两个函数的功能,而且代码可读性更高。
示例7-5 计算阶乘列表:map
和filter
与列表推导式对比
示例7-6 使用reduce
和sum
计算0——99
的整数之和
🚩 sum和reduce的整体运作方式是一样的,即把某个操作连续应用到序列中的项上,累计前一个结果,把一系列值规约成一个值
内置的规约函数还有all
和any
。
all(iterable)
iterable
中没有表示假值的元素时返回True
。all([])
返回True
any(iterable)
只要iterable
中有元素是真值就返回True
。any([])
返回False
匿名函数
lambda
关键字使用python
表达式创建匿名函数。然而,受python
简单的句法限制,lambda
函数的主体只能是纯粹的表达式,不能有while
、try
等其他语句。赋值语句=
也不能出现在lambda
函数的主体中,但是可以有海象运算符:=
,但是这种语句太过复杂,可读性差,因此建议重构。
示例7-7使用lambda表达式反转拼写,然后依次给单词列表排序。
9种可调用对象
除了函数,调用运算符(()
)还可以应用到其他对象上,如果想判断对象能否调用,可以使用内置的callable()
函数。
- 用户定义的函数
使用def
语句或lambda
表达式创建的函数 - 内置函数
使用C
语言(Cpython)
实现的函数,例如len
或time.strftime
- 内置方法
使用C
语言实现的方法,例如dict.get
。 - 方法
在类主体中定义的函数 - 类
调用类时运行类的__new__
方法创建一个实例,然后运行
__init__
方法,初始化实例,最后再把实例返回给调用方。Python
中
没有new
运算符,调用类就相当于调用函数 - 类的实例
如果类定义了__call__
方法,那么它的实例可以作为函数调用。 - 生成器函数
主体中有yield
关键字的函数或方法。调用生成器函数返回一个
生成器对象 - 原生协程函数
使用async def
定义的函数或方法。调用原生协程函数返回一个
协程对象。 - 异步生成器函数
使用async def
定义,而且主体中有yield
关键字的函数或方
法。调用异步生成器函数返回一个异步生成器,供async for
使用。
用户定义的可调用类型
不仅 Python
函数是真正的对象,而且任何 Python
对象都可以表现得像
函数。为此,只需实现实例方法 __call__
。
示例7-8 bingocall.py
:调用BingoCage
实例,从打乱顺序的列表中取出一个元素
🚩
bingo
实例可以作为函数调用,而且内置函数callable()
判定它是可调用对象。
从位置参数到仅限关键字参数
Python
函数最好的功能之一是提供了极为灵活的参数处理机制。与之密
切相关的是,调用函数时可以使用 *
和 **
拆包可迭代对象,映射各个
参数。
示例7-9 tag
函数用于生成HTML
标签。可以使用名为class_
的仅限关键字参数传入“class”
属性,这是一种变通方法,因为“class”
是python
中的关键字。
在示例7-9中,class_参数只能通过关键字参数指定,它一定不会捕获无名位置参数。定义函数时,如果想指定仅限关键字参数,就要把它们放到前面有的参数后面。如果不想支持数量不定的位置参数,但是想支持仅限关键字参数,则可以在签名中放一个。
注意,仅限关键字参数不一定要有默认值,可以像上例中的 b 那样,强
制要求传入实参。
仅限位置参数
从python3.8
开始,用户定义的函数签名可以指定仅限位置参数。内置函数都是如此。例如
divmod(a, b)
只能使用位置参数调用,不能写成divmod(a=10, b=4)
。如果想定义只接受位置参数的函数,则可以在参数列表中使用/
。
def divmod(a, b, /):
return (a//b, a%b)
/
左边均是仅限位置参数。在'/'后面,可以指定其他参数,处理方式一同往常。
支持函数式编程的包
Python
的目标不是变成函数式编程语言,但是得
益于一等函数、模式匹配,以及 operator
和 functools
等包的支
持,其对函数式编程风格也可以“信手拈来”。
-
operator
模块
在函数式编程中,经常需要把算术运算符当作函数使用。例如,不使用
递归计算阶乘。求和可以使用sum
函数,求积则没有这样的函数。可以
使用reduce
函数(参见“map
、filter
和reduce
的现代替代品”一
节),但是需要一个函数来计算序列中两项之积。
示例 7-13 使用itemgetter排序一个元组列表
示例7-14使用attrgetter处理前文定义的具名元组
-
使用
functools.partial
冻结参数
functools
模块提供了一系列高阶函数,比如 7.3 节map
、filter
和
reduce
的现代替代品”中用过的reduce
。另外一个值得关注的函数是
partial
,它可以根据提供的可调用对象产生一个新可调用对象,为原
可调用对象的某些参数绑定预定的值。使用这个函数可以把接受一个或
多个参数的函数改造成需要更少参数的回调的API
。
示例7-16 使用partial
把一个双参数函数改造成只需要一个参
数的可调用对象
示例7-17 使用partial
构建一个便利的Unicode
规范化函数