Guido van Rossum
Fred L. Drake, Jr., editor
April 29, 2011
Python Software Foundation
Email: docs@python.org
在该文档中,我们浏览Python的特性,该特性以一种函数式的风格实现编程。在介绍了函数式编程的概念后,我们将查看语言特征例如迭代器与生成器还有相关的库模块例如itertools 和functools
1.介绍
这一节解释函数式编程的基本概念,如果你只是对于Python的语言特性感兴趣,请跳到下一节
程序设计语言支持用几种不同的方式分解问题:
•许多编程语言是过程化的:程序由指令组成,指令告诉计算机伴随着输入要去做什么,C,Pascal,和Unix shell 都是过程化的语言
•声明式语言,你书写了一个说明,它描述了要解决的问题,语言的实现计算出如何高效的执行,SQL是声明式语言,你很可能对它很熟悉:SQL查询描述了你要检索的数据集,SQL引擎描述是否浏览表或者使用索引,哪一个子语句应该首先被执行等等。
•面向对象的程序操控对象的集合,对象有内部的状态以及支持的方法,这些特性以一些方式支持查询以及修改这些内部的状态,Smalltalk 以及java 是面向对象语言,C++和Python是支持面向对象的语言,但并不强迫使用面向对象的特性。
•函数式编程分解问题为一系列的函数,理想的函数仅仅获取输入以及产生输出,没有任何的内部状态,这些内部的状态影响给定输入的输出,我们熟知的函数式语言包括ML家族(标准ML,Ocaml,以及另外的变体)和Haskell
一些计算机语言的设计选择去强调编程的某一特定的方法,这通常使得应用不同的的、方法去写程序变得困难。另外的编程语言是支持多范式的语言,它们支持不同的方法,Lisp,C++,Python就是这样的语言,在这些语言中可以书写程序或者库主要以过程,面向对象以及函数,在一个大型的程序,不同的部分可能采用不同的方法,例如,GUI程序可能是面向对象的,逻辑的处理就可能是过程式或者函数式的。
在一个函数式的程序里,输入流经过一系列的函数,每一个函数操作它的输入并且产生输出。函数式的风格不鼓励有副作用的函数,副作用修改了内部的状态或者做了另外的改变这血、些改变在函数的返回值中是不可见的。没有副作用的幻术被称为纯函数,避免副作用意味着不要使用数据结构,这些数据结构在程序运行是会被更新;每一个函数的输出仅仅依赖于它的输入。
一些语言对于纯净的约束是很严格的,甚至没有赋值语句像a=3 or c = a + b,但是很难避免副作用,例如,向屏幕打印或者写入到硬盘都是副作用。例如在python中一个对print()的调用或者time.sleep()调用都不会返回有用的值,调用它们仅仅是为了其副作用,发送一段文字到屏幕或者停止执行几秒。
以函数式风格书写的python程序通常不用去极端的避免所用的I/O或者赋值操作;相反,它们将提供一个函数式外观接口,但在内部使用非函数式的特性。例如,一个函数的实现任然使用局部变量赋值操作,但是不用修改全局变量或者产生另外的副作用。
函数式编程可以看作是面向对象编程的对立面。对象是小的太空舱,它包含了一些个内部状态和一些个函数调用,函数允许你修改这些内部状态,程序是由正确的状态集合组成。函数式编程想要状态尽可能的不改变,与函数间流动的数据工作。在python中你可能结合两种方法通过书写函数,该函数获取并且返回对象的实例在你的应用程序中(email消息,事物等).
函数式设计对于当前的工作可以被看做一个奇怪的约束。为什么要避免对象以及副作用?对于函数式风格有一些理论上和实际上的好处
• Formal provability. 形式上可证
• Modularity.模块性
• Composability.组合性
• Ease of debugging and testing.容易调试和测试
1.1 形式上可证
理论上的好处是容易用数学证明函数式编程的正确性。
在一个很长的时间内,研究员们对于使用数学证明程序的正确性很感兴趣。这不同于在在很多的输入数据上测试程序并且得出程序的输出通常是正确的,或者读取一个程序的源码并且得出源码看起来是正确的;目标是替代一个缜密的证明,一个程序对于所有可能的输入产生了正确的结果。
被用来证明程序正确的技术是写下n个永远为真的不变量,输入数据的属性,程序的变量。对于每行代码,你能展示在代码执行前if 不变量X和Y为true。直到到达程序的结尾,在该行程序别执行后不同的不变量XY为true。这将继续直到程序结束,在该处不变量应该匹配期望的在程序的输出的条件。
函数式编程回避赋值操作的使用主要是因为赋值操作很难操作这种技术,赋值能够打断在赋值前为true不用产生任何新的能够向前传播的不变量,
不幸的是,证明程序正确性在很大程度上不切实际且没有相关的Python软件。即使是微小的程序证明都需要几页的代码;合适复杂度的程序需要的关于正确性的证明就更加大了。你日常使用的几个程序(Python 解释器,你的XML解析器,Web浏览器)能够被证明是正确的。即使你写下或者产生一个证明,接着的问题就是验证证明;在证明中可能存在错误,但是你错误的认为你证明的程序是正确的。
1.2 模块性
函数式编程中模块的实用价值就是它迫使你将你的问题分割为小的片段,程序是许多模块组成的。写一个小的程序它只做一件事情比写一个大程序它做一个复杂的事物处理要容易的多。小函数也容易阅读和检查错误。
1.3 容易调试与测试
测试和调试一个函数式风格的程序更加容易。
调试是简单的,因为函数通常是短小和指定明确的。但一个程序不能工作时,每一个函数就是一个接口点,在该处你能够检查数据的正确性。你能看到输入和输出去快速的分离存在bug的函数。
测试是容易的,因为每一个函数是一个潜在的单元测试主题,函数不依赖与系统的状态,系统的状态在运行一个测试前需要被复制;替代你仅仅需要合成正确的输入接着检查输出匹配期望。
1.4 组合性
当你工作在一个函数式风格的程序上,你将写出一些函数,并验证输入与输出。这些函数中的一些将不可避免的从事于一个特定的应用程序,另外的将在广泛多样的程序上有用。例如,一个函数输入一个目录路径返回该目录中所有的XML文件,或者一个函数输入一个文件名返回文件的内容,着可以被应用到很多不同的情形中。
随着时间的推移,你将拥有个人的工具库,通常你通过在一个新的配置中安排存在的函数将可以组装新的程序,写一些函数王城指定的任务。
2 迭代器
我们首先看看Python语言的一个特性,这个特性对于书写函数式风格的程序是重要的:迭代器。
迭代器是一个对象,代表了一个数据流;这个对象一次返回一个数据元素。一个Python迭代器必须支持__next__()方法,该方法没有参数并且总是返回流中的下一个元素。如果在流中没有更多的元素,__next__()方法必须抛出StopIteration异常,迭代器并不是必须有限,虽然写出一个迭代器能够产生一个无限的数据流是完全合理的。
内建函数iter()输入一个任意的对象,函数尝试返回一个迭代器,它能够返回对象的内容或者元素,如果对象不支持迭代,抛出TypeErr异常。一些Python的内建函数支持迭代操作,最常见的就是list 和dict 。一个对象被称为迭代器对象如果你能从它得到一个迭代器。
你能够手工的使用迭代器接口做如下实验:
>>> L = [1,2,3]
>>> it = iter(L)
>>> it
<...iterator object at ...>
>>> it.__next__()
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration
>>>
Python 期待可迭代的对象在不同的上下文中,最重要的是for语句,在语句for X in Y,Y必须是一个迭代器,或者可以使用iter()产生一个迭代器的对象。这两个语句是等效的:
for i in iter(obj):
print(i)
for i in obj:
print(i)
通过使用list()或者tuple()构造函数,迭代器能够被具体化为列表或者元组。
>>> L = [1,2,3]
>>> iterator = iter(L)
>>> t = tuple(iterator)
>>> t
(1, 2, 3)
序列的解包操作也支持迭代器,如果你知道一个迭代器将返回N个元素,你可以解包它们成为一个N元tuple:
>>> L = [1,2,3]
>>> iterator = iter(L)
>>> a,b,c = iterator
>>> a,b,c
(1, 2, 3)
内建函数例如max()和min()能够接受一个迭代器参数然后返回最大或者最小的元素。In或者not in 依然支持迭代器操作,X in iterator 为true,如果X在迭代器返回的流中找到。你将会运行出一些明显的问题如果迭代器是无限的;max() min() not in将不会返回,如果元素X不在流中出现,in操作也不会返回。
主意在一个迭代器中你仅仅能向前操作,没有方法可以获取之前的元素,重置迭代器或者获取一个复制。迭代器对象能够提供这些个附加的能力,但是迭代器协议仅仅指定next()方法,函数可以因此消耗所有的迭代器输出,如果你需要使用相同的流做一些不同的事情,你不得不创建新的迭代器。
2.1支持迭代器的数据类型
我们已经看到列表和字典是如何支持迭代器的,事实上,任何的python序列类型,例如string,都将自动支持创建一个迭代器。
在一个字典上调用iter()返回一个迭代器将循环字典的键
>>> m = {’Jan’: 1, ’Feb’: 2, ’Mar’: 3, ’Apr’: 4, ’May’: 5, ’Jun’: 6,
... ’Jul’: 7, ’Aug’: 8, ’Sep’: 9, ’Oct’: 10, ’Nov’: 11, ’Dec’: 12}
>>> for key in m:
... print(key, m[key])
Mar 3
Feb 2
Aug 8
Sep 9
Apr 4
Jun 6
Jul 7
Jan 1
May 5
Nov 11
Dec 12
Oct 10
注意序列是随机的,因为它是按照对象在字典中的hash序列为基础的
对字典应用iter()方法将总是循环迭代所有的键,但是字典拥有另外的方法返回另外的迭代器,如果你要迭代value或者key/value对,你可以显示的调用values() 或者items()方法去获取一个合适的迭代器
dict()构造器可以接受一个迭代器,它返回一个有限的(key,value)元组流。
>>> L = [(’Italy’, ’Rome’), (’France’, ’Paris’), (’US’, ’Washington DC’)]
>>> dict(iter(L))
{’Italy’: ’Rome’, ’US’: ’Washington DC’, ’France’: ’Paris’}
文件也支持迭代通过调用readline()方法直到在文件中没有更多的行数据。这将意味着你可以像这样读到文件中的每一行
for line in file:
# do something for each line
...
集合也可以从一个迭代器中拿出他们的内容让你迭代集合中的元素
S = {2, 3, 5, 7, 11, 13}
for i in S:
print(i)
3 生成器表达式和列表解析
两个常见的在迭代器输出上的操作是1.对每一个元素执行一些操作2.选择满足条件的一个子集。例如给出一个字符列表,你可能想要去去掉每行尾部的空格或者提取出包含给定子字符串的项。
列表解析和生成器表达式(简写“listcomps” 和 “genexps”)是一个简洁的标记用于此种操作,借鉴与函数式编程语言Haskell(http://www.haskell.org/)。你可以使用下面的代码去掉来自于流中的所有的空格:
line_list = [’ line 1/n’, ’line 2 /n’, ...]
# Generator expression -- returns iterator
stripped_iter = (line.strip() for line in line_list)
# List comprehension -- returns list
stripped_list = [line.strip() for line in line_list]
你可以选择特定的元素通过增加一个if条件判断:
stripped_list = [line.strip() for line in line_list if line != ""]
通过列表解析,你可以得到python列表,stripped_list是一个包含结果行的列表不是一个迭代器。迭代器表达式返回一个迭代器,它必须计算值,但不需要马上具体化所有的值,这意味着列表解析不是很有用,如果你正在与迭代器上工作,该迭代器返回一个无限的流或者一个非常大两的数据。生成器表达式在这些情形下更合适。
生成器表达式被一对圆括号()包围,列表解析被一对方括号包括,生成器有下列形式:
( expression for expr in sequence1
if condition1for expr2 in sequence2
if condition2 for expr3 in sequence3 ...
if condition3for exprN in sequenceN
if conditionN )
对于一个列表解析仅仅外部的括号是不同的(方括号而不是圆括号)
生成器的输出元素将是连续表达式的值。If子句是可选的;如果出现,表达式仅仅是经过评估的并添加到结果中,当条件为真。
生成器表达式重视被写在圆括号中,但是圆括号也是函数调用的信号,如果你想要创建一个迭代器,它将直接传递给函数可以这样写:
obj_total = sum(obj.count for obj in list_all_objects())
for…in 语句包含将被重复迭代的序列,序列不需要相同的长度,因为重复迭代从左到右,不是平行的,对于序列sequence1中的每一个元素,sequence2从开始被循环迭代,sequence3接着循环迭代每一个来自sequence1和sequence2的结果元素对。
下面的代码中列表解析和生成器表达式是等效的
for expr1 in sequence1:
if not (condition1):
continue # Skip this element
for expr2 in sequence2:
if not (condition2):
continue # Skip this element
...
for exprN in sequenceN:
if not (conditionN):
continue # Skip this element
# Output the value of
# the expression.
这意味着当有多个for…in 子句,但是没有if子句时,输出结果的长度将等于所有序列的长度之积。如果你有两个列表的长度为3,那么输出的列表的长度为9个元素的长。
>>> seq1 = ’abc’
>>> seq2 = (1,2,3)
>>> [(x,y) for x in seq1 for y in seq2]
[(’a’, 1), (’a’, 2), (’a’, 3),
(’b’, 1), (’b’, 2), (’b’, 3),
(’c’, 1), (’c’, 2), (’c’, 3)]
为了避免引入模糊的python语法,if表达式创建了一个元组,他必须被圆括号包围,第一个列表解析的语法是错误的,第二个是正确的。
# Syntax error
[ x,y for x in seq1 for y in seq2]
# Correct
[ (x,y) for x in seq1 for y in seq2]
4.生成器
生成器是一类特殊的函数,它简化了书写迭代器的任务。正常的函数计算一个值并且返回它,但是生成器返回一个迭代器,该迭代器返回的是一个值的流。
你无疑的对在python或者C中常规函数的调用工作很熟悉,当你调用了一个函数,它得到了一个私有的命名空间,在这里局部变量被创造,当函数到达return语句后,局部变量被摧毁,只返回到调用者,后来的对与相同函数的调用创建了一个相同的私有命名空间,并且刷新了局部变量集合。但是,如果局部变量在函数退出的时候没有丢弃掉会该如何?如果你能够重新恢复函数在它被终止的地方又该如何?这些功能生成器提供,它们能够被称作是可恢复函数。
这儿有一个生成器函数的简单例子:
def generate_ints(N):
for i in range(N):
yield i
任何包含yield关键字的函数都是生成器函数,python 的字节码编译器能够探测到,字节码编译器它编译函数作为输出。
当你调用了一个生成器函数时,它没有返回一个单个的值,它返回一个支持迭代器协议的迭代器对象。在执行yield表达式时,生成器输出i的值,和return语句相似。关于yield和return最大的不同在于在到达一个yield迭代器的执行状态被挂起局部变量被保存,在下一个对于迭代器的__next__()方法调用时,函数恢复执行。
下面是一个关于函数generate_ints()生成器的简单的使用:
>>> gen = generate_ints(3)
>>> gen
<generator object generate_ints at ...>
>>> next(gen)
0
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
Traceback (most recent call last):
File "stdin", line 1, in ?
File "stdin", line 2, in generate_ints
StopIteration
你可以等效的书写for i in generate_ints(5), or a,b,c = generate_ints(3).。
在一个生成器的内部,return语句不被用于返回值,在执行一个return后生成器不能返回更多的值,return带上一个值,例如return 5 被认为是生成器内部语法错误。生成器结果的最后也能够通过手工抛出StopIteration异常指明。或者仅仅让执行流到达函数的底部。
你能够获得生成器的感受通过写一些自己的类存储所有的生成器局部变量为实例变量。例如,返回一个整形列表能够被完成通过设定self.count 为0.使得__next__()方法增加self.count并且返回它。然而,对于一个适度复杂的生成器,书写一个相应的类变得相当messier
测试套件被包含在python的库中,test_generators.py,包含一些更加有趣的例子,这儿是一个生成器使用迭代器递归实现了中序遍历树
# A recursive generator that generates Tree leaves in in-order.
def inorder(t):
if t:
for x in inorder(t.left):
yield x
yield t.label
for x in inorder(t.right):
yield x
两个另外的例子在test_generators.py产生N-queens(在N*N的棋盘上放置N个皇后,确保没有皇后威胁到其它皇后)问题的解决方案和Knight’s Tour(找到一个路径,它带领骑士不重复饿到N*N的棋盘上任何的方格)
4.1传值到生成器
在python2.4或早期版本中,生成器仅仅产生输出。一旦生成器被调用生成一个迭代器,那就没有方法去传递任何的信息到函数中,当函数的执行被恢复时。你可以hack together这种能力通过使得生成器看起来像一个全局变量或者传递一些可变对象,这些可变对象随后可以修改,但是这种方法不够优美。
在python2.5中有简单的方式传递值到生成器。Yield是一个表达式,返回一个值,该值可以被赋值给一个一个变量或者别的操作。
val = (yield i)
当你需要对返回值做一些事情时,我推荐你总是用一对圆括号包围yield表达式。上面的例子中,圆括号不总是必须的,但是添加它们总是容易的而不是记得他们在需要的时候。
(PEP342 准确解释了规则,那就是yield表达式必须总是被加上圆括号除非当它出现在顶级表达式的赋值语句的右边。这意味着你可以书写val = yield i但是不得不使用圆括号当有一个操作,就像val = (yield i) + 12.)
值被发送到一个生成器通过调用它的sent(value)方法。这个方法恢复生成器的代码yield表达式返回指定的值。如果正规的__next__()方法被调用,yield返回None。
这儿有一个简单的计数器,它每次增加一,允许改变内部计数器的值。
def counter (maximum):
i = 0
while i < maximum:
val = (yield i)
# If value provided, change counter
if val is not None:
i = val
else:
i += 1
这儿有一个改变计数器的例子:
>>> it = counter(10)
>>> next(it)
0
>>> next(it)
1
>>> it.send(8)
8
>>> next(it)
9
>>> next(it)
Traceback (most recent call last):
File "t.py", line 15, in ?
it.next()
StopIteration
因为yield经常返回None,你应该总是检查这种情况。在表达式中不要仅仅使用它的值除非你确定send()方法将是唯一的方法被用来恢复你的生成器函数。
除了send()方法,关于生成器还有几个另外的方法。
• throw(type, value=None, traceback=None)被用于在生成器中抛出一个异常;异常通过yield表达式抛出,在该处生成器执行中止。
• close() 抛出一个 GeneratorExit 异常,在生成器内部终止迭代。在收到这个异常时,生成器代码必须要么抛出GeneratorExit或者StopIteration异常。捕获异常并且做另外的一些事情是不合法的会触发RuntimeError. close(),该方法也会被python的垃圾收集器调用在生成器被垃圾收集器收集时。
如果你需要去运行清理代码当一个GeneratorExit异常出现时,我建议使用try: ...finally:套件而不是捕获GeneratorExit。
这些改变的累积影响是将生成器从信息产生者转换为信息的产生于消费者。
生成器也可以成为协同程序,是一个更一般的子程序形式。子程序在一点进入而在另一点退出(在函数的开始,一个return语句)是协同程序可以进入、退出、恢复在许多不同的点(yield语句)
5 内建函数
让我们看看经常被使用的迭代器内建函数的更多细节
两个python内建函数map()和filter()复制了生成器表达式的特性。
map(f, iterA, iterB, ...)返回一个序列迭代器f(iterA[0], iterB[0]),
f(iterA[1], iterB[1]), f(iterA[2], iterB[2]), ....
>>> def upper(s):
... return s.upper()
>>> list(map(upper, [’sentence’, ’fragment’]))
[’SENTENCE’, ’FRAGMENT’]
>>> [upper(s) for s in [’sentence’, ’fragment’]]
[’SENTENCE’, ’FRAGMENT’]
使用列表解析你也可以产生相同的效果
filter(predicate, iter) 返回一个迭代器,该迭代器迭代序列中所有的元素返回满足条件的元素,通过列表解析也可以。断言是一个函数返回一些条件的真值;为了使用filter(),断言必须获取一个单个值。
>>> def is_even(x):
... return (x % 2) == 0
>>> list(filter(is_even, range(10)))
[0, 2, 4, 6, 8]
这也可以通过列表解析写出来:
>>> list(x for x in range(10) if is_even(x))
[0, 2, 4, 6, 8]
enumerate(iter) counts off在迭代器中的元素,返回2元tuple包括编号以及每一个元素。
>>> for item in enumerate([’subject’, ’verb’, ’object’]):
... print(item)
(0, ’subject’)
(1, ’verb’)
(2, ’object’)
enumerate()经常被使用于列表循环通并且记录满足条件元素的每一个索引
f = open(’data.txt’, ’r’)
for i, line in enumerate(f):
if line.strip() == ’’:
print(’Blank line at line #%i’ % i)
sorted(iterable, [key=None], [reverse=False])收集所有的迭代元素到一个列表,排序列表,返回排好序的列表。Key,和reverse参数传递到list.sort()方法
>>> import random
>>> # Generate 8 random numbers between [0, 10000)
>>> rand_list = random.sample(range(10000), 8)
>>> rand_list
[769, 7953, 9828, 6431, 8442, 9878, 6213, 2207]
>>> sorted(rand_list)
[769, 2207, 6213, 6431, 7953, 8442, 9828, 9878]
>>> sorted(rand_list, reverse=True)
[9878, 9828, 8442, 7953, 6431, 6213, 2207, 769]
(一个更加详细的关于排序的讨论,可以参照Sorting mini-HOWTO,在Python wiki http://wiki.python.org/moin/HowTo/Sorting)
any(iter)与all(iter)内建函数查看可迭代内容的真值。any()返回真值如果元素中人一个元素为真,all()返回真如果所有的元素都为真:
>>> any([0,1,0])
True
>>> any([0,0,0])
False
>>> any([1,1,1])
True
>>> all([0,1,0])
False
>>> all([0,0,0])
False
>>> all([1,1,1])
True
zip(iterA, iterB, ...) takes one element from each iterable and returns them in a tuple:拿到一个迭代器中的元素返回它们到一个元组
zip([’a’, ’b’, ’c’], (1, 2, 3)) =>
(’a’, 1), (’b’, 2), (’c’, 3)
它没有构造一个在内存中的列表并且在返回前消耗掉所有的输入迭代器;tuple被构造并且返回仅仅在它们被需要的时候(这种技术被称为懒估值)
这个迭代器企图所有的迭代器拥有相同的长度,如果迭代器有不同的长度,结果流将是最短迭代器的长度
zip([’a’, ’b’], (1, 2, 3)) =>
(’a’, 1), (’b’, 2)
你应该避免这些,虽然,因为一个元素可以从一个长的迭代器中拿来和丢弃。这意味着你不能继续去使用高级迭代器因为你冒险跳过一个丢弃的元素。
6 Itertools模块
该模块包含一些通用的迭代器和函数用于结合一些迭代器,这一节将通过一些小的例子介绍该模块。
该模块的一些函数可以分为一些级别:
•依赖存在的迭代器穿件新的迭代器饿函数
•作为函数参数用于处理迭代器元素的函数
•用于选择迭代器部分输出的函数
•用于分组迭代器输出的函数
6.1创建新的迭代器
itertools.count(n) 返回一个无限的整数流,每次递增一。你可以随意的提供开始的数字,它默认是0:
itertools.count() =>
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...
itertools.count(10) =>
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...
itertools.cycle(iter)保存提供的迭代器的一个拷贝并且返回一个新的迭代器它返回所有的元素,新迭代器无限的循环这些元素
itertools.cycle([1,2,3,4,5]) =>
1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...
itertools.repeat(elem, [n])返回提供的元素n次,或者如果n没有提供就无限循环。 itertools.repeat(’abc’) =>
abc, abc, abc, abc, abc, abc, abc, abc, abc, abc, ...
itertools.repeat(’abc’, 5) =>
abc, abc, abc, abc, abc
itertools.chain(iterA, iterB, ...)以一个任意的数字作为迭代器的输入,返回第一个迭代器的所有元素,接着是第二个迭代器的所有元素等等,直到所有的迭代器已经被用尽。
itertools.chain([’a’, ’b’, ’c’], (1, 2, 3)) =>
a, b, c, 1, 2, 3
itertools.islice(iter, [start], stop, [step])返回一个部分迭代器的流,如果有一个stop参数,他将返回第一个stop元素,如果你提供一个开始的索引,你将得到一个stop-start元素,如果你提供一个步长,元素将会相应的跳过相应的步长。不想Python的字符串以及列表,你不可以对start, stop, step使用负数.
itertools.islice(range(10), 8) =>
0, 1, 2, 3, 4, 5, 6, 7
itertools.islice(range(10), 2, 8) =>
2, 3, 4, 5, 6, 7
itertools.islice(range(10), 2, 8, 2) =>
2, 4, 6
itertools.tee(iter, [n])复制一个迭代器,它返回n个独立的迭代器,它们将返回原始迭代器的内容。如果你没有提供一个n值,默认为2。复制迭代器需要保存一些原始迭代器的内容,以至于这将能够耗费宝贵的内存资源如果迭代器很大,并且一个新的迭代器耗费超过其它的 itertools.tee( itertools.count() ) =>
iterA, iterB
where iterA ->
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...
and iterB ->
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...
6.2在元素上调用元素
Operator模块包含一系列的函数对应于Python的操作。一些例子是operator.add(a, b)(相加两个值)operator.ne(a, b)(等于a!=b)operator.attrgetter(’id’)(返回id的属性)
itertools.starmap(func, iter)假设迭代器将会返回一个元组的流,使用这些元组作为参数调用f()
itertools.starmap(os.path.join,
[(’/usr’, ’bin’, ’java’), (’/bin’, ’python’),
(’/usr’, ’bin’, ’perl’),(’/usr’, ’bin’, ’ruby’)])
=>
/usr/bin/java, /bin/python, /usr/bin/perl, /usr/bin/ruby
6.3 选择元素
另外的一组函数依照断言选择一个迭代器子集
itertools.filterfalse(predicate, iter)是相反的,返回所有断言返回false的元素。 itertools.filterfalse(is_even, itertools.count()) =>
1, 3, 5, 7, 9, 11, 13, 15, ...
itertools.takewhile(predicate, iter)返回所有的元素只要断言为真,一旦断言返回假,迭代器将会结束。
def less_than_10(x):
return (x < 10)
itertools.takewhile(less_than_10, itertools.count()) =>
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
itertools.takewhile(is_even, itertools.count()) =>
0
itertools.dropwhile(predicate, iter)丢弃元素当断言为真时,接着返回剩下的迭代器的结果。
itertools.dropwhile(less_than_10, itertools.count()) =>
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ...
itertools.dropwhile(is_even, itertools.count()) =>
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...
6.4 元素分组
我们要讨论的最后的函数是,itertools.groupby(iter, key_func=None),是最复杂的。key_func(elem)是一个函数,它能够对每一个通过迭代器返回的元素计算一个键值。如果你不提供一个函数,key就简单是每一个元素自身。
groupby()从拥有相同键值的基础的迭代器收集所有的包含元素。返回一个二元元组流,包含一个键值和一个迭代器对于包含该键的元素
city_list = [(’Decatur’, ’AL’), (’Huntsville’, ’AL’), (’Selma’, ’AL’),
(’Anchorage’, ’AK’), (’Nome’, ’AK’),
(’Flagstaff’, ’AZ’), (’Phoenix’, ’AZ’), (’Tucson’, ’AZ’),
...
]
def get_state (city_state):
return city_state[1]
itertools.groupby(city_list, get_state) =>
(’AL’, iterator-1),
(’AK’, iterator-2),
(’AZ’, iterator-3), ...
where
iterator-1 =>
(’Decatur’, ’AL’), (’Huntsville’, ’AL’), (’Selma’, ’AL’)
iterator-2 =>
(’Anchorage’, ’AK’), (’Nome’, ’AK’)
iterator-3 =>
(’Flagstaff’, ’AZ’), (’Phoenix’, ’AZ’), (’Tucson’, ’AZ’)
groupby()假设基础的迭代器的内容已经在键上被排序。注意返回的迭代器也使用基础的迭代器,因此你不得不在请求iterator-2和它相对应的键之前销毁iterator-1的结果
7 functools模块
在Python2.5 中Functools模块包含一些higher-order函数。higher-order函数接收一个或者更多的函数作为输入返回一个新的函数。在这个模块中最有用的函数就是functools.partial()。
对于使用函数式风格书写的程序来说,你有时想要去构造一个存在函数的变量,它们拥有一些参数,考虑一个Python函数f(a, b, c);你可以创建一个新的函数g(b, c)它等价于f(1, b, c);你
向f()的参数中填入一个值。这被称为,部分函数调用。
Partial的构造器获取参数(function, arg1, arg2, ... kwarg1=value1, kwarg2=value2).结果对象是可调用的,因此你能通过调用它而引用添加了参数的函数。
这儿有一些小而现实的例子:
import functools
def log (message, subsystem):
"Write the contents of ’message’ to the specified subsystem."
print(’%s: %s’ % (subsystem, message))
...
server_log = functools.partial(log, subsystem=’server’)
server_log(’Unable to open socket’)
functools.reduce(func, iter, [initial_value])累积的在所有的迭代器元素上执行一个操作,因此,不能在无限的迭代器上使用(注意它不再内建函数中,而是在functools模块中)func必须有两个参数并且返回一个值。functools.reduce()获取由迭代器返回的头两个元素A,B,并且计算func(A,B)接着请求第三个元素C,计算func(func(A, B), C),与返回的第四个元素结合,继续直到迭代器元素用完。如果迭代器不再返回值TypeError异常被抛出。如果初始的值被提供,它被作为开始的点,func(initial_value, A) 首先被计算。
>>> import operator, functools
>>> functools.reduce(operator.concat, [’A’, ’BB’, ’C’])
’ABBC’
>>> functools.reduce(operator.concat, [])
Traceback (most recent call last):
...
TypeError: reduce() of empty sequence with no initial value
>>> functools.reduce(operator.mul, [1,2,3], 1)
6
>>> functools.reduce(operator.mul, [], 1)
1
如果你使用operator.add()作为functools.reduce()的参数,你将加起迭代器的所有元素,这个案例太常见以至于内建函数sum()也可以:
>>> import functools
>>> functools.reduce(operator.add, [1,2,3,4], 0)
10
>>> sum([1,2,3,4])
10
>>> sum([])
0
对于很多的functools.reduce()的使用,尽管它可以被很清晰的书写为明显的for循环:
import functools
# Instead of:
product = functools.reduce(operator.mul, [1,2,3], 1)
# You can write:
product = 1
for i in [1,2,3]:
product *= i
7.1 operator 模块
operator 模块被过早的提及。它包含一些列的函数对应到Python的操作符上,这些函数经常被用在函数式风格的代码中,因为它们使得你不用去写一些琐碎的用来执行单一操作的函数。
•数学操作: add(), sub(), mul(), floordiv(), abs(), ...
•逻辑操作: not_(), truth().
• 位操作: and_(), or_(), invert().
• 比较: eq(), ne(), lt(), le(), gt(), and ge().
• 对象特性: is_(), is_not().模块中有下面的一些函数:
查看operator 模块的文档去获得一个完整的列表
7 .2 functional模块
Collin Winter的函数模块提供了一些更加高级的工具用于函数式编程。它也重新实现了一些Python内建的函数,尝试去使它们更加直观,对那些在其它语言中使用函数式编程。
这一节里介绍一些在functional模块里的重要函数。完整的文档可以再the project’s website.找到。
compose(outer, inner, unpack=False)
compose()函数实现函数的组合。换句话说,它返回一个包装,inner被outer包装,来自inner的返回值直接到outer 也就是:
>>> def add(a, b):
... return a + b
...
>>> def double(a):
... return 2 * a
...
>>> compose(double, add)(5, 6)
22
is equivalent to
>>> double(add(5, 6))
22
Unpack关键字