持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
目前在在阅读Python常用的标准库,避免重复造轮子,写出优雅的Pythonic代码,而网上大多对某个特定库的教程都是描述该库中几个函数如何使用的,没有全面的对该库库中的函数进行讲解,但在itertools — Iterator Functions 中发现了较为全面的itertools库函数的讲解,由于原网站讲解为英文,故翻译一下,方面今后使用的时候进行查找,也可以分享给大家。
\
目录:(方便在浏览器中进行Ctrl+F查询操作)
1.Merging and Splitting Iterators
2.Converting Inputs
3.Producing New Values
4.Filtering
5.Grouping Data
6.Combining Inputs
\
\
itertools
Iterator Functions
itertools提供的功能受Clojure,Haskell,APL和SML等功能性编程语言的类似功能的启发。 它们旨在快速有效地使用内存,也可以将它们连接在一起以表示更复杂的基于迭代的算法。
基于迭代器的代码比使用列表的代码提供更好的内存消耗特性。
由于数据不是从迭代器产生的,直到需要时,所有的数据不需要同时存储在内存中。 这种“懒惰”的处理模式可以减少大数据集的交换和其他副作用,提高性能。
除了itertools中定义的函数之外,本节中的示例还依赖于一些内置函数进行迭代。
\
Merging and Splitting Iterators
合并和拆分迭代器
\
chain()函数将几个迭代器作为参数,并返回一个迭代器,它产生所有输入的内容,就像它们来自单个迭代器一样。
\
# itertools_chain.py
from itertools import *
for i in chain([1, 2, 3], ['a', 'b', 'c']):
print(i, end=' ')
print()
\
chain()可以轻松处理多个序列,而不需要构建一个大的列表。
$ python3 itertools_chain.py
1 2 3 a b c
\
如果要组合的迭代不是全部事先声明的,或者需要延迟评估(evaluated ),可以使用chain.from_iterable()来构造链。(chain.from_iterable() can be used to
construct the chain instead.)
\
# itertools_chain_from_iterable.py
from itertools import *
def make_iterables_to_chain():
yield [1, 2, 3]
yield ['a', 'b', 'c']
for i in chain.from_iterable(make_iterables_to_chain()):
print(i, end=' ')
print()
.
$ python3 itertools_chain_from_iterable.py
1 2 3 a b c
\
内置函数zip()返回一个迭代器,它将几个迭代器的元素组合成元组。
#itertools_zip.py
for i in zip([1, 2,3], ['a', 'b', 'c']):
print(i)
和本模块中的其他函数一样,返回值是一个可迭代的对象,每次产生一个值。
$ python3 itertools_zip.py
(1, 'a')
(2, 'b')
(3, 'c')
\
当第一个输入迭代器耗尽时,zip()停止。 为了处理所有的输入,即使迭代器产生不同数量的值,使用zip_longest()。
# itertools_zip_longest.py
from itertools import *
r1 = range(3)
r2 = range(2)
print('zip stops early:')
print(list(zip(r1, r2)))
r1 = range(3)
r2 = range(2)
print('\nzip_longest processes all of the values:')
print(list(zip_longest(r1, r2)))
默认情况下,zip_longest()将任何缺失值替换为None。 使用fillvalue参数来使用不同的替代值。
$ python3 itertools_zip_longest.py
zip stopsearly:
[(0, 0), (1, 1)]
zip_longest processes all of the values:
[(0, 0), (1, 1), (2, None)]
\
islice()函数返回一个迭代器,它通过索引从输入迭代器中返回选定的项目。
# itertools_islice.py
from itertools import *
from itertools import *
print('Stop at 5:')
for i in islice(range(100), 5):
print(i, end=' ')
print('\n')
print('Start at 5, Stop at 10:')
for i in islice(range(100), 5, 10):
print(i, end=' ')
print('\n')
print('By tens to 100:')
for i in islice(range(100), 0, 100, 10):
print(i, end=' ')
print('\n')
islice()采用与列表运算符相同的参数:start,stop和step。 开始和步骤参数是可选的。
$ python3 itertools_islice.py
Stop at 5:
0 1 2 3 4
Start at 5, Stop at 10:
5 6 7 8 9
By tens to 100:
0 10 20 30 40 50 60 70 80 90
\
tee()函数根据单个原始输入返回几个独立的迭代器(默认为2)。
# itertools_tee.py
from itertools import *
r = islice(count(), 5)
i1, i2 = tee(r)
print('i1:', list(i1))
print('i2:', list(i2))
tee()具有与Unix tee实用程序相似的语义,它重复从它的输入中读取的值并将它们写入一个命名文件和标准输出。 由tee()返回的迭代器可用于将同一组数据馈入多个算法并行处理。
$ python3 itertools_tee.py
i1: [0, 1, 2, 3, 4]
i2: [0, 1, 2, 3, 4]
由tee()创建的新迭代器共享它们的输入,因此在创建新迭代器之后不应使用原始迭代器。
# itertools_tee_error.py
from itertools import *
r = islice(count(), 5)
i1, i2 = tee(r)
print('r:', end=' ')
for i in r:
print(i, end=' ')
if i > 1:
break
print()
print('i1:', list(i1))
print('i2:', list(i2))
如果从原始输入中消耗了值,则新的迭代器将不会生成这些值:
$ python3 itertools_tee_error.py
r: 0 1 2
i1: [3, 4]
i2: [3, 4]
\
\
Converting Inputs
转换输入
\
内置的map()函数返回一个迭代器,它对输入迭代器中的值调用一个函数,并返回结果。 它会在任何输入迭代器耗尽时停止。
# itertools_map.py
def times_two(x):
return 2 * x
def multiply(x, y):
return (x, y, x * y)
print('Doubles:')
for i in map(times_two, range(5)):
print(i)
print('\nMultiples:')
r1 = range(5)
r2 = range(5, 10)
for i in map(multiply, r1, r2):
print('{:d} * {:d} = {:d}'.format(*i))
print('\nStopping:')
r1 = range(5)
r2 = range(2)
for i in map(multiply, r1, r2):
print(i)
在第一个示例中,lambda函数将输入值乘以2.在第二个示例中,lambda函数将从两个单独的迭代器中获取的两个参数相乘,并返回一个包含原始参数和计算值的元组。
第三个示例在生成两个元组之后停止,因为第二个范围耗尽。
$ python3 itertools_map.py
Doubles:
0
2
4
6
8
Multiples:
0 * 5 = 0
1 * 6 = 6
2 * 7 = 14
3 * 8 = 24
4 * 9 = 36
Stopping:
(0, 0, 0)
(1, 1, 1)
\
starmap()函数与map()类似,但不是从多个迭代器构造元组,而是使用*语法将单个迭代器中的项作为参数分解为映射函数。
# itertools_starmap.py
from itertools import *
values = [(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]
for i in starmap(lambda x, y: (x, y, x * y), values):
print('{} * {} = {}'.format(*i))
其中map()的映射函数称为f(i1,i2),传递给starmap()的映射函数称为f(* i)。
$ python3 itertools_starmap.py
0 * 5 = 0
1 * 6 = 6
2 * 7 = 14
3 * 8 = 24
4 * 9 = 36
\
Producing New Values
产生新的值
\
count()函数将无限期地返回一个产生连续整数的迭代器。 第一个数字可以作为参数传递(默认值为零)。 没有上限参数(请参阅内置范围()以更好地控制结果集)。
# itertools_count.py
from itertools import *
for i in zip(count(1), ['a', 'b', 'c']):
print(i)
这个例子因为列表参数被使用而停止。
$ python3 itertools_count.py
(1, 'a')
(2, 'b')
(3, 'c')
\
count()的开始和步骤参数可以是任何可以加在一起的数字值。
# itertools_count_step.py
import fractions
from itertools import *
start = fractions.Fraction(1, 3)
step = fractions.Fraction(1, 3)
for i in zip(count(start, step), ['a', 'b', 'c']):
print('{}: {}'.format(*i))
在这个例子中,起点和步骤是来自分数模块的分数对象。
$ python3 itertools_count_step.py
1/3: a
2/3: b
1: c
\
cycle()函数返回一个迭代器,它会重复无限地给出的参数的内容。 由于必须导入输入迭代器的全部内容,因此如果迭代器很长,则可能会消耗相当多的内存。
# itertools_cycle.py
from itertools import *
for i in zip(range(7), cycle(['a', 'b', 'c'])):
print(i)
在这个例子中,一个计数器变量用来在几个周期之后跳出循环。
$ python3 itertools_cycle.py
(0, 'a')
(1, 'b')
(2, 'c')
(3, 'a')
(4, 'b')
(5, 'c')
(6, 'a')
\
repeat()函数返回一个迭代器,每次迭代器访问时都会产生相同的值。
# itertools_repeat.py
from itertools import *
for i in repeat('over-and-over', 5):
print(i)
repeat()返回的迭代器将永远返回数据,除非提供可选的times参数来限制它。
$ python3 itertools_repeat.py
over-and-over
over-and-over
over-and-over
over-and-over
over-and-over
\
当需要将不变值包括在其他迭代器的值中时,将repeat()与zip()或map()组合起来是非常有用的。
# itertools_repeat_zip.py
from itertools import *
for i, s in zip(count(), repeat('over-and-over', 5)):
print(i, s)
在本例中,计数器值与repeat()返回的常量组合在一起。
$ python3 itertools_repeat_zip.py
0 over-and-over
1 over-and-over
2 over-and-over
3 over-and-over
4 over-and-over
\
本示例使用map()将范围从0到4的数字乘以2。
# itertools_repeat_map.py
from itertools import *
for i in map(lambda x, y: (x, y, x * y), repeat(2), range(5)):
print('{:d} * {:d} = {:d}'.format(*i))
repeat()迭代器不需要显式限制,因为map()在其任何输入结束时停止处理,并且range()只返回五个元素。
$ python3 itertools_repeat_map.py
2 * 0 = 0
2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
2 * 4 = 8
\
Filtering
过滤(过滤器?)
\
dropwhile()函数返回一个迭代器,它在条件第一次变为false时生成输入迭代器的元素。
# itertools_dropwhile.py
from itertools import *
def should_drop(x):
print('Testing:', x)
return x < 1
for i in dropwhile(should_drop, [-1, 0, 1, 2, -2]):
print('Yielding:', i)
dropwhile()不会过滤每个输入项; 在条件为第一次错误之后,输入中的所有剩余项目都会返回。(可以尝试改下for内的数组为[-1, 0, 1, 2, -1]看看会发生什么)
$ python3 itertools_dropwhile.py
Testing: -1
Testing: 0
Testing: 1
Yielding: 1
Yielding: 2
Yielding: -2
\
dropwhile()的反义词是taketime()。 它返回一个迭代器,只要测试函数返回true,就会从输入迭代器返回项目。
# itertools_takewhile.py
from itertools import *
def should_take(x):
print('Testing:', x)
return x < 2
for i in takewhile(should_take, [-1, 0, 1, 2, -2]):
print('Yielding:', i)
只要should_take()返回False,takewhile()就停止处理输入。
$ python3 itertools_takewhile.py
Testing: -1
Yielding: -1
Testing: 0
Yielding: 0
Testing: 1
Yielding: 1
Testing: 2
\
内置函数filter()返回一个只包含测试函数返回值为true的项的迭代器。
# itertools_filter.py
from itertools import *
def check_item(x):
print('Testing:', x)
return x < 1
for i in filter(check_item, [-1, 0, 1, 2, -2]):
print('Yielding:', i)
filter()不同于dropwhile()和takewhile(),因为每个项目在返回之前都经过测试。
$ python3 itertools_filter.py
Testing: -1
Yielding: -1
Testing: 0
Yielding: 0
Testing: 1
Testing: 2
Testing: -2
Yielding: -2
\
filterfalse()返回一个只包含测试函数返回false的项目的迭代器。
# itertools_filterfalse.py
from itertools import *
def check_item(x):
print('Testing:', x)
return x < 1
for i in filterfalse(check_item, [-1, 0, 1, 2, -2]):
print('Yielding:', i)
check_item()中的测试表达式是相同的,所以在这个例子中filterfalse()的结果与前面例子的结果是相反的。
$ python3 itertools_filterfalse.py
Testing: -1
Testing: 0
Testing: 1
Yielding: 1
Testing: 2
Yielding: 2
Testing: -2
\
compress()提供了另一种方法来过滤可迭代的内容。 它不是调用一个函数,而是使用另一个迭代器中的值来指示何时接受一个值以及何时忽略它。
# itertools_compress.py
from itertools import *
every_third = cycle([False, False, True])
data = range(1, 10)
for i in compress(data, every_third):
print(i, end=' ')
print()
第一个参数是可迭代处理的数据,第二个是可迭代的选择器,生成布尔值,指示从数据输入中取出哪些元素(真值导致产生值,错误值导致忽略)。
$ python3 itertools_compress.py
3 6 9
\
Grouping Data
分组数据
\
groupby()函数返回一个迭代器,该迭代器生成由公用密钥组织的值集合。 此示例说明了基于属性对相关值进行分组。
# itertools_groupby_seq.py
import functools
from itertools import *
import operator
import pprint
@functools.total_ordering
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return '({}, {})'.format(self.x, self.y)
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def __gt__(self, other):
return (self.x, self.y) > (other.x, other.y)
# Create a dataset of Point instances
data = list(map(Point,
cycle(islice(count(), 3)),
islice(count(), 7)))
print('Data:')
pprint.pprint(data, width=35)
print()
# Try to group the unsorted data based on X values
print('Grouped, unsorted:')
for k, g in groupby(data, operator.attrgetter('x')):
print(k, list(g))
print()
# Sort the data
data.sort()
print('Sorted:')
pprint.pprint(data, width=35)
print()
# Group the sorted data based on X values
print('Grouped, sorted:')
for k, g in groupby(data, operator.attrgetter('x')):
print(k, list(g))
print()
输入序列需要按照键值进行排序才能按照预期进行分组。
$ python3 itertools_groupby_seq.py
Data:
[(0, 0),
(1, 1),
(2, 2),
(0, 3),
(1, 4),
(2, 5),
(0, 6)]
Grouped, unsorted:
0 [(0, 0)]
1 [(1, 1)]
2 [(2, 2)]
0 [(0, 3)]
1 [(1, 4)]
2 [(2, 5)]
0 [(0, 6)]
Sorted:
[(0, 0),
(0, 3),
(0, 6),
(1, 1),
(1, 4),
(2, 2),
(2, 5)]
Grouped, sorted:
0 [(0, 0), (0, 3), (0, 6)]
1 [(1, 1), (1, 4)]
2 [(2, 2), (2, 5)]
\
\
Combining Inputs
合并输入
\
accumulate()函数处理输入迭代,将第n个和第n + 1个项传递给一个函数,并产生返回值而不是任何一个输入。 用于组合这两个值的缺省函数将它们相加,所以可以使用accumulate()来产生一系列数字输入的累积和。
# itertools_accumulate.py
from itertools import *
print(list(accumulate(range(5))))
print(list(accumulate('abcde')))
当与一系列非整数值一起使用时,结果取决于将两个项目“添加”在一起的含义。 这个脚本中的第二个例子显示,当accumulate()接收到一个字符串输入时,每个响应都是该字符串的逐渐变长的前缀。
$ python3 itertools_accumulate.py
[0, 1, 3, 6, 10]
['a', 'ab', 'abc', 'abcd', 'abcde']
\
可以将accumulate()与任何其他带有两个输入值的函数结合以获得不同的结果。
# itertools_accumulate_custom.py
from itertools import *
def f(a, b):
print(a, b)
return b + a + b
print(list(accumulate('abcde', f)))
这个例子结合了字符串值的方式,使一系列(无意义的)回文。 每次调用f()时,都会打印由accumulate()传递给它的输入值。
$ python3 itertools_accumulate_custom.py
a b
bab c
cbabc d
dcbabcd e
['a', 'bab', 'cbabc', 'dcbabcd', 'edcbabcde']
\
嵌套for循环遍历多个序列通常可以替换为product(),它产生一个单一的迭代,其值是输入值集的笛卡尔乘积。
# itertools_product.py
from itertools import *
import pprint
FACE_CARDS = ('J', 'Q', 'K', 'A')
SUITS = ('H', 'D', 'C', 'S')
DECK = list(
product(
chain(range(2, 11), FACE_CARDS),
SUITS,
)
)
for card in DECK:
print('{:>2}{}'.format(*card), end=' ')
if card[1] == SUITS[-1]:
print()
product()产生的值是元组,从每个迭代中取得的成员按照它们传递的顺序作为参数传入。
返回的第一个元组包含每个可迭代的第一个值。 传递给product()的最后一个迭代先被处理,然后是最后一个处理,依此类推。结果是,返回值是基于第一个迭代的顺序,然后是下一个可迭代的,等等。
在这个例子中,牌是按价值排序,然后是套装。
$ python3 itertools_product.py
2H 2D 2C 2S
3H 3D 3C 3S
4H 4D 4C 4S
5H 5D 5C 5S
6H 6D 6C 6S
7H 7D 7C 7S
8H 8D 8C 8S
9H 9D 9C 9S
10H 10D 10C 10S
JH JD JC JS
QH QD QC QS
KH KD KC KS
AH AD AC AS
\
要更改卡的顺序,请将参数的顺序更改为product()。
# itertools_product_ordering.py
from itertools import *
import pprint
FACE_CARDS = ('J', 'Q', 'K', 'A')
SUITS = ('H', 'D', 'C', 'S')
DECK = list(
product(
SUITS,
chain(range(2, 11), FACE_CARDS),
)
)
for card in DECK:
print('{:>2}{}'.format(card[1], card[0]), end=' ')
if card[1] == FACE_CARDS[-1]:
print()
在这个例子中的打印循环查找Ace卡,而不是spade suit,然后添加一个换行来打破输出。
$ python3 itertools_product_ordering.py
2H 3H 4H 5H 6H 7H 8H 9H 10H JH QH KH AH
2D 3D 4D 5D 6D 7D 8D 9D 10D JD QD KD AD
2C 3C 4C 5C 6C 7C 8C 9C 10C JC QC KC AC
2S 3S 4S 5S 6S 7S 8S 9S 10S JS QS KS AS
\
要计算序列与其自身的乘积,请指定输入应该重复的次数。
# itertools_product_repeat.py
from itertools import *
def show(iterable):
for i, item in enumerate(iterable, 1):
print(item, end=' ')
if (i % 3) == 0:
print()
print()
print('Repeat 2:\n')
show(list(product(range(3), repeat=2)))
print('Repeat 3:\n')
show(list(product(range(3), repeat=3)))
由于重复单个迭代就像多次传递相同的迭代,因此product()生成的每个元组将包含与重复计数器相等的项目数。
$ python3 itertools_product_repeat.py
Repeat 2:
(0, 0) (0, 1) (0, 2)
(1, 0) (1, 1) (1, 2)
(2, 0) (2, 1) (2, 2)
Repeat 3:
(0, 0, 0) (0, 0, 1) (0, 0, 2)
(0, 1, 0) (0, 1, 1) (0, 1, 2)
(0, 2, 0) (0, 2, 1) (0, 2, 2)
(1, 0, 0) (1, 0, 1) (1, 0, 2)
(1, 1, 0) (1, 1, 1) (1, 1, 2)
(1, 2, 0) (1, 2, 1) (1, 2, 2)
(2, 0, 0) (2, 0, 1) (2, 0, 2)
(2, 1, 0) (2, 1, 1) (2, 1, 2)
(2, 2, 0) (2, 2, 1) (2, 2, 2)
permutations()函数根据给定长度的可能排列组合输入可迭代的项目。 它默认生成所有排列的全部集合。
# itertools_permutations.py
from itertools import *
def show(iterable):
first = None
for i, item in enumerate(iterable, 1):
if first != item[0]:
if first is not None:
print()
first = item[0]
print(''.join(item), end=' ')
print()
print('All permutations:\n')
show(permutations('abcd'))
print('\nPairs:\n')
show(permutations('abcd', r=2))
使用r参数来限制返回的单个排列的长度和数量。
$ python3 itertools_permutations.py
All permutations:
abcd abdc acbd acdb adbc adcb
bacd badc bcad bcda bdac bdca
cabd cadb cbad cbda cdab cdba
dabc dacb dbac dbca dcab dcba
Pairs:
ab ac ad
ba bc bd
ca cb cd
da db dc
要将值限制为唯一组合而不是置换,请使用combinations()。 只要输入的成员是唯一的,输出将不包含任何重复的值。
# itertools_combinations.py
from itertools import *
def show(iterable):
first = None
for i, item in enumerate(iterable, 1):
if first != item[0]:
if first is not None:
print()
first = item[0]
print(''.join(item), end=' ')
print()
print('Unique pairs:\n')
show(combinations('abcd', r=2))
与排列不同,combinations()的r参数是必需的。
$ python3 itertools_combinations.py
Unique pairs:
ab ac ad
bc bd
cd
虽然combinations()不重复单个输入元素,但有时考虑包含重复元素的组合是有用的。 对于这些情况,使用combinations_with_replacement()。
# itertools_combinations_with_replacement.py
from itertools import *
def show(iterable):
first = None
for i, item in enumerate(iterable, 1):
if first != item[0]:
if first is not None:
print()
first = item[0]
print(''.join(item), end=' ')
print()
print('Unique pairs:\n')
show(combinations_with_replacement('abcd', r=2))
在这个输出中,每个输入项与其自身以及输入序列的所有其他成员都是成对的。
$ python3 itertools_combinations_with_replacement.py
Unique pairs:
aa ab ac ad
bb bc bd
cc cd
dd
到这里也结束了,熟练掌握Python标准库对工作和日常编程速度的提升真的有效,而且也使程序的可读性变得更好,另外推荐一本书《编写高质量代码 改善Python程序的91个建议》
关于itertools的问题可以评论或者联系judgezhang123@163.com
感谢你看到了这里