Python HOWTOS学习,函数式编程(二)
目录
前一部分讲了什么是函数式编程(Functional Programming),函数式编程特点和Python语言特性中的迭代器Iterator
,列表生成式list comprehensive以及生成器Generator
,可以参见此处。
这一部分主要为一些与迭代器配合使用的Python内置函数,还有一些Python的标准库,以及lambda表达式。
Built-in functions
让我们更详细地看看经常与迭代器配合的内置函数:
map(f, iterA, iterB, ...)
filter(predicate, iter)
enumerate(iter, start=0)
sorted(iter, key=None, reverse=False)
any(iter)
all(iter)
zip(iterA, iterB, ...)
map
map(f, iterA, iterB, ...)
在序列f(iterA[0], iterB[0], ...)
,f(iterA[1], iterB[1], ...)
,f(iterA[2], iterB[2], ...)
,…上返回一个迭代器。
1 | def upper(s): |
1 | 'sentence', 'fragment'])) list(map(upper, [ |
当然可以通过列表生成式达到同样的效果。
filter
filter(predicate, iter)
返回满足一定条件的所有序列元素的迭代器,类似于通过列表生成式复制。一个predicate
是一个函数,返回某个条件的真值。
1 | def is_even(x): |
1 | 10))) list(filter(is_even, range( |
这也可以写成列表生成式:
1 | for x in range(10) if is_even(x)) list(x |
enumerate
enumerate(iter, start=0)
返回一个可迭代的2-tuples
,容纳了count(从start
开始)和iter
中的每个元素。
1 | for item in enumerate(['subject', 'verb', 'object']): |
在循环列表并记录符合某些条件的索引时,通常使用enumerate()
:
1 | f = open('data.txt', 'r') |
sorted
sorted(iterable, key=None, reverse=False)
将迭代器的所有元素收集到list
中,对list
进行排序并返回排序的结果。key
和reverse
被传递给构造list
的sort()
方法。
1 | import random |
有关排序的更详细讨论,请参阅Sorting HOW TO.
any 和 all
any(iter)
和all(iter)
都会茶轴iterable
中的真值。如果iterable
中的存在任何一个元素为真,则any()
返回True
,如果所有元素均为真,则all()
返回True
:
1 | 0,1,0]) any([ |
zip
zip(iterA, iterB, ...)
从每个iterable
中提取一个元素,并将它们返回到一个tuple
中:
1 | for i in zip(['a', 'b', 'c'], (1, 2, 3)): |
tuple
只有在被请求时才被构造和返回,即为惰性的,与迭代器,生成器类似。
如果iterable
的长度不同,结果流的长度将与最短的iterable
长度相同。
1 | for i in zip(['a', 'b', 'c'], (1, 2)): |
itertools模块
itertools模块包含许多常用的迭代器以及用于组合多个迭代器的函数。
该模块的函数分为几个大类:
- 用现有迭代器创建新迭代器的函数
- 将迭代器的元素视为函数参数的函数
- 选择迭代器的输出的函数
- 分组迭代器的输出的函数
创建一个新的迭代器
itertools.count(start, step)
返回一个具有均匀间隔的无限数据流的迭代器。start
为起始,默认为1,step
为步长,默认为1。通常用做参数传递给map()
来生成连续的数据点。另外,可以使用zip()
添加序列号。
1 | itertools.count() => |
大致相当于:
1 | def count(start=0, step=1): |
itertools.cycle(iter)
保存提供的iterable
的内容,并返回一个新的迭代器,新的迭代器将无限重复这些元素。
1 | itertools.cycle([1,2,3,4,5]) => |
类似于:
1 | def cycle(iterable): |
itertools.repeat(elem, [n])
返回一个重复提供的elem
n
次的迭代器,或者如果没有提供 n
,则无限循环返回elem
。
1 | itertools.repeat('abc') => |
大致相当于:
1 | def repeat(object, times=None): |
itertools.chain(iterA, iterB ,...)
将任意数量的iterable
作为输入,并返回一个迭代器,该迭代器先第一个iterable
的所有元素,然后返回第二个iterable
的所有元素,依此类推。
1 | itertools.chain(['a', 'b', 'c'], (1, 2, 3)) => |
大致相当于:
1 | def chain(*iterables): |
itertools.islice(iter, [start], stop, [step])
返回一个迭代器片段的数据流。
如果start
为None,则迭代从0
开始。如果step
为None
,则step
默认为1
。使用单个stop
参数,它将返回第一个stop
元素。如果提供起始索引,则会获得stop
到start
元素,如果为step
提供值,则会相应跳过元素。与Python的字符串和列表切片不同,start
,stop
或step
不能负值。
1 | itertools.islice(range(10), 8) => |
类似于:
1 | def islice(iterable, *args): |
itertools.tee(iter, [n])
复制一个迭代器,并返回n
个独立的迭代器,它们都将返回源迭代器的内容。如果没有为n
提供值,则默认值为2
。tee
可能占据大量的内存(取决于需要存储多少临时数据)。一般来说,如果一个迭代器在另一个迭代器启动之前使用大部分或全部数据,则使用更快的list()
而不是tee()
。
1 | itertools.tee( itertools.count() ) => |
函数应用于元素
operator
库包含一系列对应于python操作符的函数。例如operator.add(a, b)
(a + b) , operator.ne(a, b)
(a != b) 等。operator.attrgetter('id')
返回一个获取id
属性的函数,f = operator.attrgetter('name')
,调用f(b)
返回b.name
; f = operator.attrgetter('name', 'date')
,调用f(b)
返回(b.name, b.date)
。
itertools.starmap(func,iter)
假定这个iter
将返回一个元组流,并使用这些元组作为参数调用func
:
1 | itertools.starmap(os.path.join, |
选择元素
还有另一组函数,根据predicate
选择迭代器元素的子集。
itertools.filterfalse(predicate, iter)
与filter()
相反,返回所有的那些predicate
返回false
的元素:
1 | itertools.filterfalse(is_even, itertools.count()) => |
itertools.takewhile(predicate, iter)
只要predicate
返回true
,就返回元素。一旦predicate
返回false
,迭代器就会结束。
1 | def less_than_10(x): |
itertools.dropwhile(predicate,iter)
在predicate
返回true
时丢弃元素,然后返回迭代结果的其余部分。
1 | itertools.dropwhile(less_than_10, itertools.count()) => |
itertools.compress(data,selectors)
接受两个迭代器,只返回data
中与selectors
相应元素为true
的数据元素,当其中一个迭代器元素耗尽时停止:
1 | itertools.compress([1,2,3,4,5], [True, True, False, False, True]) => |
组合排列
itertools.combinations(iterable,r)
返回一个迭代器,给出iterable
中包含的元素的所有可能的以r
大小的tuple组合。
1 | itertools.combinations([1, 2, 3, 4, 5], 2) => |
每个tuple
中的元素保持与iterable
的顺序相同。 例如,在上面的示例中,数字1总是在2, 3, 4或5之前。 类似的函数itertools.permutations(iterable,r = None)
删除了顺序这个约束,返回了长度为r的所有可能的排列:
1 | itertools.permutations([1, 2, 3, 4, 5], 2) => |
如果没有为r
提供值,则使用iterable
的长度。
这些函数按位置生成所有可能的组合,并且不要求iterable
的内容是唯一的:
1 | itertools.permutations('aba', 3) => |
itertools.combinations_with_replacement(iterable,r)
函数松弛了另一个的约束:元素可以在单个tuple
中重复。 从概念上讲,为每个tuple的第一个位置选择一个元素,然后在选择第二个元素之前替换该元素。
1 | itertools.combinations_with_replacement([1, 2, 3, 4, 5], 2) => |
元素分组
这里讨论的itertools.groupby(iter,key_func = None)
是最复杂的。 key_func(elem)
是一个可以计算iterable
返回的每个元素键值的函数。如果不提供key_func
,则键值只是每个元素本身。
groupby()
从iterable
中收集具有相同键值的所有连续元素,并返回一个包含键值的2-tuple
的流和具有该键值的元素的迭代器。
1 | city_list = [('Decatur', 'AL'), ('Huntsville', 'AL'), ('Selma', 'AL'), |
groupby()
假定iterable
的内容已经根据键进行排序。 注意,返回的迭代器也使用iterable
,因此在请求iterator-2及其相应的键之前,需要消耗iterator-1的结果。
functools模块
functools
模块包含一些高阶函数。高阶函数将一个或多个函数作为输入并返回一个新函数。这个模块中最有用的工具是functools.partial()
函数。
对于以函数式编写的程序,有时需要构造填充了一些参数的现有函数的变体。
考虑一个函数f(a,b,c)
, 你可能希望创建一个等于f(1,b,c)
的新函数g(b,c)
。这称为"partial function application"。
partial()
的构造函数接受参数(function,arg1,arg2,...,kwarg1 = value1,kwarg2 = value2)
。 生成的对象是可调用的,因此您可以调用它来调用已经填充过参数的函数。
例如:
1 | import functools |
functools.reduce(func,iter,[initial_value])
累加对所有iterable元素执行的操作,因此不能应用于无限的可迭代对象。
func
必须是一个带有两个元素并返回单个值的函数。functools.reduce()
获取迭代器返回的前两个元素A和B,并计算func(A,B)
。然后它请求第三个元素C计算func(func(A,B),C)
,将此结果与返回的第四个元素组合,并继续直到迭代结束。如果iterable
没有返回值,则引发TypeError
异常。如果提供了初始值,则将其用作起点,即func(initial_value,A)
是第一次计算。
1 | import operator, functools |
如果将operator.add()
与functools.reduce()
一起使用,则会累加iterable
的所有元素。 更常见的则是用内置函数sum()
来计算:
1 | import functools, operator |
虽然functools.reduce()
还有很多用途,但是,编写for循环可以更清楚:
1 | import functools |
另一个相关函数是itertools.accumulate(iterable,func = operator.add)
。它执行相同的计算,但不是仅返回最终结果,accumulate()
返回一个迭代器,它也产生每一步的结果:
1 | itertools.accumulate([1,2,3,4,5]) => |
operator模块
之前提到过operator
模块。它包含一系列与Python运算符相对应的函数。这些函数通常在函数式代码中很有用,因为它们可以避免编写执行单个操作的简单函数。
该模块中的一些函数有:
- 数学运算符:
add()
,sub()
,mul()
,floordiv()
,abs()
, … - 逻辑运算符:
not_()
,truth()
. - 位操作符:
and_()
,or_()
,invert()
. - 比较:
eq()
,ne()
,lt()
,le()
,gt()
, 和ge()
. - Object identity:
is_()
,is_not()
.
完整列表,请参阅Python文档。
Lambda表达式
在编写函数式程序时,经常需要很少的函数作为predicate
或以某种方式组合元素。
如果有内置的Python或适合的模块函数,则不需要定义新函数:
1 | stripped_lines = [line.strip() for line in lines] |
如果不存在,则需要编写它。一种方法是使用lambda语句。lambda接受许多参数和一个组合这些参数的表达式,并创建一个返回表达式值的匿名函数:
1 | adder = lambda x, y: x+y |
等价于:
1 | def adder(x, y): |
哪种方案更可取? 这是一个风格问题,因人而异。
例如:
1 | import functools |
需要时间来解开表达式来弄清楚发生了什么。使用简短的嵌套def语句可以使事情变得更好:
1 | import functools |
使用for循环实现,则更加清晰:
1 | total = 0 |
也可以使用sum()
与生成器表达式:
1 | total = sum(b for a,b in items) |
实际上functools.reduce()
的许多用法,使用for循环时都更清晰。
Fredrik Lundh曾经为重构lambda表达式提出了以下一套规则:
- 写一个lambda函数。
- 写评论,解释lambda的作用。
- 研究评论一段时间,并想一个捕捉评论本质的名称。
- 使用该名称将lambda转换为def语句。
- 删除评论。
但终究使用与风格因人而异。
References
Python官方文档:Python HOWTOs