- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
5.10 支持函数式编程的包
虽然 Guido 明确表明,Python 的目标不是变成函数式编程语言,但是得益于 operator 和 functools 等包的支持,函数式编程风格也可以信手拈来。接下来的两节分别介绍这两个包。
5.10.1 operator模块
在函数式编程中,经常需要把算术运算符当作函数使用。例如,不使用递归计算阶乘。求和可以使用 sum 函数,但是求积则没有这样的函数。我们可以使用 reduce 函数(5.2.1 节是这么做的),但是需要一个函数计算序列中两个元素之积。示例 5-21 展示如何使用 lambda 表达式解决这个问题。
示例 5-21 使用 reduce 函数和一个匿名函数计算阶乘
from functools import reduce def fact(n): return reduce(lambda a, b: a*b, range(1, n+1))
operator 模块为多个算术运算符提供了对应的函数,从而避免编写 lambda a, b: a*b 这种平凡的匿名函数。使用算术运算符函数,可以把示例 5-21 改写成示例 5-22 那样。
示例 5-22 使用 reduce 和 operator.mul 函数计算阶乘
from functools import reduce from operator import mul def fact(n): return reduce(mul, range(1, n+1))
operator 模块中还有一类函数,能替代从序列中取出元素或读取对象属性的 lambda 表达式:因此,itemgetter 和 attrgetter 其实会自行构建函数。
示例 5-23 展示了 itemgetter 的常见用途:根据元组的某个字段给元组列表排序。在这个示例中,按照国家代码(第 2 个字段)的顺序打印各个城市的信息。其实,itemgetter(1) 的作用与 lambda fields: fields[1] 一样:创建一个接受集合的函数,返回索引位 1 上的元素。
示例 5-23 演示使用 itemgetter 排序一个元组列表(数据来自示例 2-8)
>>> metro_data = [ ... ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)), ... ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)), ... ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)), ... ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)), ... ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)), ... ] >>> >>> from operator import itemgetter >>> for city in sorted(metro_data, key=itemgetter(1)): ... print(city) ... ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)) ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)) ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)) ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)) ('New York-Newark', 'US', 20.104, (40.808611, -74.020386))
如果把多个参数传给 itemgetter,它构建的函数会返回提取的值构成的元组:
>>> cc_name = itemgetter(1, 0) >>> for city in metro_data: ... print(cc_name(city)) ... ('JP', 'Tokyo') ('IN', 'Delhi NCR') ('MX', 'Mexico City') ('US', 'New York-Newark') ('BR', 'Sao Paulo') >>>
itemgetter 使用 [] 运算符,因此它不仅支持序列,还支持映射和任何实现 __getitem__ 方法的类。
attrgetter 与 itemgetter 作用类似,它创建的函数根据名称提取对象的属性。如果把多个属性名传给 attrgetter,它也会返回提取的值构成的元组。此外,如果参数名中包含 .(点号),attrgetter 会深入嵌套对象,获取指定的属性。这些行为如示例 5-24 所示。这个控制台会话不短,因为我们要构建一个嵌套结构,这样才能展示 attrgetter 如何处理包含点号的属性名。
示例 5-24 定义一个 namedtuple,名为 metro_data(与示例 5-23 中的列表相同),演示使用 attrgetter 处理它
>>> from collections import namedtuple >>> LatLong = namedtuple('LatLong', 'lat long') # ➊ >>> Metropolis = namedtuple('Metropolis', 'name cc pop coord') # ➋ >>> metro_areas = [Metropolis(name, cc, pop, LatLong(lat, long)) # ➌ ... for name, cc, pop, (lat, long) in metro_data] >>> metro_areas[0] Metropolis(name='Tokyo', cc='JP', pop=36.933, coord=LatLong(lat=35.689722, long=139.691667)) >>> metro_areas[0].coord.lat # ➍ 35.689722 >>> from operator import attrgetter >>> name_lat = attrgetter('name', 'coord.lat') # ➎ >>> >>> for city in sorted(metro_areas, key=attrgetter('coord.lat')): # ➏ ... print(name_lat(city)) # ➐ ... ('Sao Paulo', -23.547778) ('Mexico City', 19.433333) ('Delhi NCR', 28.613889) ('Tokyo', 35.689722) ('New York-Newark', 40.808611)
❶ 使用 namedtuple 定义 LatLong。
❷ 再定义 Metropolis。
❸ 使用 Metropolis 实例构建 metro_areas 列表;注意,我们使用嵌套的元组拆包提取 (lat, long),然后使用它们构建 LatLong,作为 Metropolis 的 coord 属性。
❹ 深入 metro_areas[0],获取它的纬度。
❺ 定义一个 attrgetter,获取 name 属性和嵌套的 coord.lat 属性。
❻ 再次使用 attrgetter,按照纬度排序城市列表。
❼ 使用标号❺中定义的 attrgetter,只显示城市名和纬度。
下面是 operator 模块中定义的部分函数(省略了以 _ 开头的名称,因为它们基本上是实现细节):4
4Python 3.5 中增加了 imatmul 和 matmul。——编者注
>>> [name for name in dir(operator) if not name.startswith('_')] ['abs', 'add', 'and_', 'attrgetter', 'concat', 'contains', 'countOf', 'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt', 'iadd', 'iand', 'iconcat', 'ifloordiv', 'ilshift', 'imod', 'imul', 'index', 'indexOf', 'inv', 'invert', 'ior', 'ipow', 'irshift', 'is_', 'is_not', 'isub', 'itemgetter', 'itruediv', 'ixor', 'le', 'length_hint', 'lshift', 'lt', 'methodcaller', 'mod', 'mul', 'ne', 'neg', 'not_', 'or_', 'pos', 'pow', 'rshift', 'setitem', 'sub', 'truediv', 'truth', 'xor']
这 52 个名称中大部分的作用不言而喻。以 i 开头、后面是另一个运算符的那些名称(如 iadd、iand 等),对应的是增量赋值运算符(如 +=、&= 等)。如果第一个参数是可变的,那么这些运算符函数会就地修改它;否则,作用与不带 i 的函数一样,直接返回运算结果。
在 operator 模块余下的函数中,我们最后介绍一下 methodcaller。它的作用与 attrgetter 和 itemgetter 类似,它会自行创建函数。methodcaller 创建的函数会在对象上调用参数指定的方法,如示例 5-25 所示。
示例 5-25 methodcaller 使用示例:第二个测试展示绑定额外参数的方式
>>> from operator import methodcaller >>> s = 'The time has come' >>> upcase = methodcaller('upper') >>> upcase(s) 'THE TIME HAS COME' >>> hiphenate = methodcaller('replace', ' ', '-') >>> hiphenate(s) 'The-time-has-come'
示例 5-25 中的第一个测试只是为了展示 methodcaller 的用法,如果想把 str.upper 作为函数使用,只需在 str 类上调用,并传入一个字符串参数,如下所示:
>>> str.upper(s) 'THE TIME HAS COME'
示例 5-25 中的第二个测试表明,methodcaller 还可以冻结某些参数,也就是部分应用(partial application),这与 functools.partial 函数的作用类似。详情参见下一节。
5.10.2 使用functools.partial冻结参数
functools 模块提供了一系列高阶函数,其中最为人熟知的或许是 reduce,我们在 5.2.1 节已经介绍过。余下的函数中,最有用的是 partial 及其变体,partialmethod。
functools.partial 这个高阶函数用于部分应用一个函数。部分应用是指,基于一个函数创建一个新的可调用对象,把原函数的某些参数固定。使用这个函数可以把接受一个或多个参数的函数改编成需要回调的 API,这样参数更少。示例 5-26 做了简单的演示。
示例 5-26 使用 partial 把一个两参数函数改编成需要单参数的可调用对象
>>> from operator import mul >>> from functools import partial >>> triple = partial(mul, 3) ➊ >>> triple(7) ➋ 21 >>> list(map(triple, range(1, 10))) ➌ [3, 6, 9, 12, 15, 18, 21, 24, 27]
➊ 使用 mul 创建 triple 函数,把第一个定位参数定为 3。
➋ 测试 triple 函数。
➌ 在 map 中使用 triple;在这个示例中不能使用 mul。
使用 4.6 节介绍的 unicode.normalize 函数再举个例子,这个示例更有实际意义。如果处理多国语言编写的文本,在比较或排序之前可能会想使用 unicode.normalize('NFC', s) 处理所有字符串 s。如果经常这么做,可以定义一个 nfc 函数,如示例 5-27 所示。
示例 5-27 使用 partial 构建一个便利的 Unicode 规范化函数
>>> import unicodedata, functools >>> nfc = functools.partial(unicodedata.normalize, 'NFC') >>> s1 = 'café' >>> s2 = 'cafe\u0301' >>> s1, s2 ('café', 'café') >>> s1 == s2 False >>> nfc(s1) == nfc(s2) True
partial 的第一个参数是一个可调用对象,后面跟着任意个要绑定的定位参数和关键字参数。
示例 5-28 在示例 5-10 中定义的 tag 函数上使用 partial,冻结一个定位参数和一个关键字参数。
示例 5-28 把 partial 应用到示例 5-10 中定义的 tag 函数上
>>> from tagger import tag >>> tag <function tag at 0x10206d1e0> ➊ >>> from functools import partial >>> picture = partial(tag, 'img', cls='pic-frame') ➋ >>> picture(src='wumpus.jpeg') '<img class="pic-frame" src="wumpus.jpeg" />' ➌ >>> picture functools.partial(<function tag at 0x10206d1e0>, 'img', cls='pic-frame') ➍ >>> picture.func ➎ <function tag at 0x10206d1e0> >>> picture.args ('img',) >>> picture.keywords {'cls': 'pic-frame'}
❶ 从示例 5-10 中导入 tag 函数,查看它的 ID。
❷ 使用 tag 创建 picture 函数,把第一个定位参数固定为 'img',把 cls 关键字参数固定为 'pic-frame'。
❸ picture 的行为符合预期。
❹ partial() 返回一个 functools.partial 对象。5
5functools.py 的源码表明,functools.partial 类是使用 C 语言实现的,而且默认使用这个实现。如果这个实现不可用,从 Python 3.4 起,functools 模块为 partial 提供了纯 Python 实现。
❺ functools.partial 对象提供了访问原函数和固定参数的属性。
functools.partialmethod 函数(Python 3.4 新增)的作用与 partial 一样,不过是用于处理方法的。
functools 模块中的 lru_cache 函数令人印象深刻,它会做备忘(memoization),这是一种自动优化措施,它会存储耗时的函数调用结果,避免重新计算。第 7 章将会介绍这个函数,还将讨论装饰器,以及旨在用作装饰器的其他高阶函数:singledispatch 和 wraps。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论