《Effective Python》笔记 第四章-推导与生成
阅读Effective Python(第二版)的一些笔记
第27条 用列表推导取代map与filter
下面的例子是遍历列表元素的时候,选出偶数,并计算偶数的平方;使用的for循环,for循环里面使用if进行判断,将满足条件的数据进行计算后加入结果集合中:
# coding:utf-8
# 普通遍历,选择偶数,进行计算平方
def test_common_traverse():
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
res = []
for i in arr:
if not (i % 2):
res.append(i * i)
print(res)
test_common_traverse() # [4, 16, 36, 64, 100]
上面的列子使用列表推导来实现,写法如下:
# coding:utf-8
# 使用列表推导式
def test_list_comprehension():
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 列表推导
res = [i * i for i in arr if not (i % 2)]
print(res)
test_list_comprehension() # [4, 16, 36, 64, 100]
列表推导式,不仅可以生成list,还可以生成dict、set,还可以推导dict(以及所有可迭代的对象),比如下面的例子:
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 列表推导,生成dict,选出偶数元素,key为元素值,value为平方值
res = {i: i * i for i in arr if not (i % 2)}
print("dict result:{}".format(res)) # dict result:{8: 64, 2: 4, 4: 16, 10: 100, 6: 36}
arr = [1, 2, 1, 2, 3, 4, 4]
# 列表推导,生成set
res = {i for i in arr}
print("set result:{}".format(res)) # set result:set([1, 2, 3, 4])
# 迭代dict
dict_data = {"name": "abc", "age": 99}
res = [str(k) + "=" + str(v) for k, v in dict_data.items() if k and v]
print("traverse dict result:{}".format(res)) # traverse dict result:['age=99', 'name=abc']
上面的操作,其实可以使用Python的map()
和filter()
来实现,map()
实现对每个元素的操作,filter()
实现元素的过滤,但是使用起来没有列表推导方便。
第28条 控制推导逻辑的子表达式不要超过两个
在遍历二维矩阵的时候,我们通常会使用两层for循环来实现;
matrix = [
[1, 2, 3],
[4, 5, 6]
]
# 使用常规的两层for循环嵌套
result = []
for row in matrix:
for item in row:
result.append(item)
print(result) # [1, 2, 3, 4, 5, 6]
有了上面的列表推导之后,列表推导也是支持嵌套的,示例如下:
# 使用两层嵌套的推导式
matrix = [
[1, 2, 3],
[4, 5, 6]
]
result = [item for row in matrix for item in row]
print(result) # [1, 2, 3, 4, 5, 6]
虽然可以使用嵌套的推导式,但是在看代码比较不方便,上面的例子是没有数据过滤的,如果嵌套次数再多几层,每一层for都有过滤,那么写出来的推导式将很难理解,所以在使用推导式时,只是用一个for配合一个if,不要组合太多的for和if了;如果有太多的嵌套和if判断,可以直接使用原始的for和if。
第29条 用赋值表达式消除推导中的重复代码
有时候,我们在循环中不是单纯的过滤元素,而是会使用元素进行计算,并收集计算后的数据,比如下面的例子:
# coding:utf-8
def do_compute(v):
return v * 3
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
res = []
for item in data:
# 计算
mod = do_compute(item)
# 计算后的数据满足条件,就加入结果集中
if mod % 2:
res.append(mod)
print(res) # [3, 9, 15, 21, 27]
上面的代码可以使用推导式来实现,如下:
# 使用列表推导式
res = [do_compute(item) for item in data if (do_compute(item) % 2)]
print(res) # [3, 9, 15, 21, 27]
上面列表推导式的实现方式,有个问题,do_compute()
调用了两次,判断和收集元素的时候,分别调用了一次,如果do_compute()
是非常耗时的操作,那么使用推导式的效率并不高。
在python 3.8里面新增的赋值表达式可以优化这个问题,写法如下:
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 使用python3.8的赋值表达式,在if里面将计算结果使用赋值表达式保存到变量computed_val,在for前面可以直接引用
res = [computed_val for item in data if (computed_val := do_compute(item)) % 2]
print(res) # [3, 9, 15, 21, 27]
第30条 不要让函数直接返回列表,应该让它逐个生成列表里的值
如果要返回1~100以内能被3整除的数字,可以像下面这么写:
# coding:utf-8
res = []
for i in range(1, 20):
if i % 3 == 0:
res.append(i)
print(res) # [3, 6, 9, 12, 15, 18]
# 使用推导式
res = [i for i in range(1, 20) if i % 3 == 0]
print(res) # [3, 6, 9, 12, 15, 18]
上面的写法是没有问题的,基本所有的编程语言实现思路都是这样。
上面的写法,其实在所有数据都处理完成后,就会将所有满足条件的元素加入到了结果集合中,如果数据量比较大,耗费的时间比较长,并且res占用的空间也会增加;对于Python而言,python有个东西叫生成器(generator),使用生成器可以优化结果集合占用空间大的问题。
生成器的原理就是:生成器的另外一个别名是“迭代器”,返回的不再是一个结果集合,而是一个迭代器,每次迭代的时候只获取一个元素,而不是一次性的返回所有数据,写法如下:
# coding:utf-8
# 使用迭代器,返回的是迭代器,而不是list
def test_generator():
for i in range(1, 20):
if i % 3 == 0:
print("handle i={} when {}".format(i, time.time()))
yield i
# 返回的是迭代器
iter_ = test_generator()
print(next(iter_))
# handle i=3 when 1649141922.667564
# 3
time.sleep(2)
print(next(iter_))
# handle i=6 when 1649141924.671442
# 6
time.sleep(2)
print(next(iter_))
# handle i=9 when 1649141926.6735291
# 9
注意上面的时间戳,可以看到,是在每次迭代的时候才进行处理,并不需要所有数据都计算完成才进行后面的处理,并且每次只会生成一个元素结果,这样并不会造成很多内存的开销,耗时也比较少。
上面虽然是每次手动调用next()来迭代,其实完全可以test_generator()的结果当成list来使用,示例如下:
# 使用迭代器
def test_generator():
for i in range(1, 20):
if i % 3 == 0:
print("handle i={} when {}".format(i, time.time() ))
yield i
# 返回的是迭代器
iter_ = test_generator()
# 直接使用for循环遍历迭代器,其实就是先判断迭代器是否已经结束,未结束的话,就调用next()方法获取一个元素。
for i in iter_:
print(i)
# 注意重新获取迭代器
iter_ = test_generator()
print(min(iter_))
当然,生成器也不是没有缺点的,缺点就是迭代器是有状态的,当迭代器遍历过元素后,就不能再次遍历(复用),如果继续遍历已经结束的迭代器,会引发StopIteration
异常,只能重新获取迭代器,重头开始遍历。
第31条 谨慎地迭代函数所收到的参数
如果有一个数组,要计算数组中每个元素占数组和的比重,那么可以用下面的方式:
# coding:utf-8
def compute_numbers_weight(data):
total = sum(data) # 计算总和
print("sum:{}".format(total))
result = {}
for v in data:
weight = v / total * 100
result.setdefault(v, weight)
print("result:{}".format(result))
arr_data = [2, 4, 3, 5, 6]
compute_numbers_weight(arr_data)
# sum:20
# result:{2: 10.0, 4: 20.0, 3: 15.0, 5: 25.0, 6: 30.0}
如果传入的data比较大(比如有1亿个数据),那么生成data将会比较耗时,并且会占用很多内存;有了Python的生成器,那么可以使用生成器来改写,也就是下面这样,传入一个生成器(迭代器):
# coding:utf-8
def compute_numbers_weight(data):
total = sum(data) # 计算总和
print("sum:{}".format(total))
result = {}
for v in data:
weight = v / total * 100
result.setdefault(v, weight)
print("result:{}".format(result))
# 模拟构造数据(返回生成器)
def my_generated_data():
my_arr_data = [2, 4, 3, 5, 6] # 模拟从外部获取数据,比如文件或者网络
for i in my_arr_data:
yield i
compute_numbers_weight(my_generated_data())
# sum:20
# result:{}
上面的运行结果明显是错误的,总和是正确的,但是结果result是错误的,不应该是空的呀。
出现这样的原因,就是之前提到的默认的生成器(迭代器)不能复用,compute_numbers_weight()
传入的迭代器,在sum()
计算总和时,已经将迭代器迭代结束了,后面for
循环会判断迭代器是否已经结束,若已结束后就不会进入循环,所以结果是个空dict。
# coding:utf-8
class DefaultGeneratdData(object):
def __init__(self, init_data):
self.init_data = init_data
self.count = 0
self.total = len(init_data)
# 第一次迭代,或者每次迭代完成再次迭代,都会返回新的迭代器
def __iter__(self):
for i in self.init_data:
self.count += 1 # 每次迭代都加1
if self.count >= self.total: # 如果迭代
return
yield i
为了解决上面的问题,我们可以自己定义迭代器,当迭代一个已经完成的迭代器时,返回新的迭代器;
# coding:utf-8
class MyGeneratedData(object):
def __init__(self, init_data):
self.init_data = init_data
# 第一次迭代,或者每次迭代完成再次迭代,都会返回新的迭代器
def __iter__(self):
for i in self.init_data:
yield i
def compute_numbers_weight(data):
total = sum(data) # 计算总和(会获取迭代器)
print("sum:{}".format(total))
result = {}
# 会重新获取一个迭代器,和上面sum获取的迭代器不是一个
for v in data:
weight = v / total * 100
result.setdefault(v, weight)
print("result:{}".format(result))
data = MyGeneratedData([2, 4, 3, 5, 6])
compute_numbers_weight(data)
# sum:20
# result:{2: 10.0, 4: 20.0, 3: 15.0, 5: 25.0, 6: 30.0}
而默认的不允许重复迭代的迭代器就类似下面这样:
# coding:utf-8
class DefaultGeneratdData(object):
def __init__(self, init_data):
self.init_data = init_data
self.count = 0 # 记录迭代器的迭代次数
self.total = len(init_data) # 元素总数
def __iter__(self):
for i in self.init_data:
self.count += 1 # 每次迭代都加1
if self.count >= self.total: # 如果已经迭代完成,则返回空
return
yield i
另外,上面的例子是由于重写了class的__iter__()
方法,所以支持迭代;那么如何判断传入的对象是否可迭代呢?有两种方式:
# coding:utf-8
from collections.abc import Iterator
# 判断是否对象可迭代
data = MyGeneratedData([1, 2, 3, 4])
if isinstance(data, Iterator):
# 如果是Iterator的实例,那么就属于类型错误
raise TypeError("wrong type")
else:
print("correct type")
注意,使用isinstance判断的时候,如果是Iterator的实例,才是类型错误。
第32条 考虑用生成器表达式改写数据量较大的列表推导
下面的例子是使用普通for循环和列表推导式来读取文件,然后计算每一行数据的字符数:
# coding:utf-8
# 使用简单for循环
count_list = []
for line in open('Effective_Python.md'):
count_list.append(len(line))
print(count_list)
# 使用列表推导
count_list = [len(line) for line in open('Effective_Python.md')]
print(count_list) # [19, 1, 22, 1, 63, 1, 1, 1 ...]
上面的代码执行时,会先将文件读入内存,然后进行迭代,如果文件内容比较多的时候,这样就非常耗费内存,而且会花费很多时间读取文件内容,有了之前说的生成器(迭代器),就可以来优化一下上面的代码:
# 使用for循环生成迭代器
def get_file_iter():
for line in open('Effective_Python.md'):
yield len(line)
print(get_file_iter()) # <generator object get_file_iter at 0x108493e40>
# 列表推导生成的迭代器
iter_ = (len(line) for line in open('Effective_Python.md'))
print(iter_) # <generator object <genexpr> at 0x101d75e40>
使用生成器(迭代器)要始终注意:迭代器类似于流处理,生成器是有状态的,不能重复迭代。
第33条 通过yield from把多个生成器连起来用
如果我们有3个生成器,需我们按照顺序将这3个生成器迭代完成,那么做简单的写法是这样的:
# coding:utf-8
def g_1():
for i in [1, 2, 3]:
yield i
def g_2():
for i in [4, 5, 6]:
yield i
def g_3():
for i in [7, 8, 9]:
yield i
# 迭代3个生成器
def do_compose():
for item in g_1():
yield item
for item in g_2():
yield item
for item in g_3():
yield item
# 获取迭代3个生成器的结果
res = [i for i in do_compose()]
print(res) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
上面的do_compose()
方法其实比较简单,就是每个迭代器使用for
循环进行再次生成迭代器,Python 3.3
中有yield from
的语句,可以简化上线的多个生成器的连接使用:
# python 3.3支持yield from语法
def do_compose_v2():
yield from g_1()
yield from g_2()
yield from g_3()
# 获取迭代3个生成器的结果
res = [i for i in do_compose()]
print(res) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
使用yield from
就比较干净整洁了,性能也会由于手写的for循环(书上说的)。
第34条 不要用send给生成器注入数据
yield的功能
带有yield的function称为生成器(迭代器)。在一定程度上,yield和return的功能都有相似的地方,比如都有返回数据的功能,但是也是有差异的,迭代器是在每次调用next()的时候才会执行循环,返回一个数据,
# coding:utf-8
def test_yield():
i = 0
print("while before\n")
while i < 3:
print("test_yield yield前,i:{}".format(i))
yield i
print("test_yield yield后,i:{}".format(i))
i += 1
print("while end")
print("开始执行test_yield")
it = test_yield()
print("准备开始执行next()")
res = next(it)
print("第1次执行next()结果:{}".format(res))
print("----------\n")
res = next(it)
print("第2次执行next()结果:{}".format(res))
print("----------\n")
res = next(it)
print("第3次执行next()结果:{}".format(res))
print("----------\n")
res = next(it)
print("第4次执行next()结果:{}".format(res))
输出结果如下:
开始执行test_yield
准备开始执行next()
while before
test_yield yield前,i:0
第1次执行next()结果:0
----------
test_yield yield后,i:0
test_yield yield前,i:1
第2次执行next()结果:1
----------
test_yield yield后,i:1
test_yield yield前,i:2
第3次执行next()结果:2
----------
test_yield yield后,i:2
while end
Traceback (most recent call last):
File "/Users/ganlixin/code/python_code_all/effective_python/item_34.py", line 49, in <module>
res = next(it)
StopIteration
观察上面的代码和执行结果,可以得出以下结论:
- 调用带有yield的方法时,并没有进入for循环
- 第1次调用next()方法时会进行一次循环,并且进入循环后,只执行到yield的一行代码就停了,返回一个数据给next(),后面的代码未执行;
- 第2次调用next()方法时,会接着第一次yield代码开始执行,进行一次循环,然后又停到yield的那里,返回一个数据给next(),后面的代码未执行;
- 第3次调用next()方法时,会接着yield代码开始执行,进行一次循环,然后又停到yield的那里,返回一个数据给next(),后面的代码未执行;
- 第4次调用next(),会接着执行yield后面的代码开始执行,进行一次循环,但是判断循环进入条件时,发现不满足条件了,此时结束循环,执行循环后面的代码,输出一行
while end
,但是next()是需要返回返回值的,但是这一次迭代没有返回值,抛出了StopIteration异常,表示迭代已经结束。
yield有返回值吗?
提个问题,yield有返回值吗?用下面代码
def test_yield():
i = 0
print("while before\n")
while i < 3:
print("test_yield yield前,i:{}".format(i))
yield_value = yield i
print("test_yield yield后,i:{}, yield_value:{}".format(i, yield_value))
i += 1
print("while end")
print("开始执行test_yield")
it = test_yield()
print("准备开始执行next()")
res = next(it)
print("第1次执行next()结果:{}".format(res))
print("----------\n")
res = next(it)
print("第2次执行next()结果:{}".format(res))
print("----------\n")
res = next(it)
print("第3次执行next()结果:{}".format(res))
print("----------\n")
输出结果如下:
开始执行test_yield
准备开始执行next()
while before
test_yield yield前,i:0
第1次执行next()结果:0
----------
test_yield yield后,i:0, yield_value:None
test_yield yield前,i:1
第2次执行next()结果:1
----------
test_yield yield后,i:1, yield_value:None
test_yield yield前,i:2
第3次执行next()结果:2
----------
可以看到yield_value都是None,这是否表示yield没有返回值呢?或者说yield的返回值只能是None呢?
其实不是的,yield的返回值是可以设置的,设置yield返回值的方法就是调用send():
- send的内容就是yield的返回值,
- send和next功能又有些相似,next()迭代可以获取结果,
send(val)
可以val作为yield的返回值,同时获取迭代结果,所以可以简单理解next() == send(None)
。 - 示例如下:
def test_yield_v2():
i = 0
print("while before\n")
while i < 3:
print("test_yield yield前,i:{}".format(i))
yield_value = yield i
print("test_yield yield后,i:{}, yield_value:{}".format(i, yield_value))
i += 1
print("while end")
print("开始执行test_yield")
it = test_yield_v2()
print("准备开始执行next()")
res = next(it)
print("第1次执行next()结果:{}".format(res))
print("第一次执行send()前")
send_res = it.send("第1次执行next()后send的内容")
print("第1次执行send()结果:{}".format(send_res))
print("----------\n")
res = next(it)
print("第2次执行next()结果:{}".format(res))
print("第2次执行send()前")
send_res = it.send("第2次执行next()后send的内容")
print("第2次执行send()结果:{}".format(send_res))
print("----------\n")
res = next(it)
print("第3次执行next()结果:{}".format(res))
print("第3次执行send()前")
send_res = it.send("第3次执行next()后send的内容")
print("第3次执行send()结果:{}".format(send_res))
print("----------\n")
执行的结果如下:
开始执行test_yield
准备开始执行next()
while before
test_yield yield前,i:0
第1次执行next()结果:0
第一次执行send()前
test_yield yield后,i:0, yield_value:第1次执行next()后send的内容
test_yield yield前,i:1
第1次执行send()结果:1
----------
test_yield yield后,i:1, yield_value:None
test_yield yield前,i:2
第2次执行next()结果:2
第2次执行send()前
test_yield yield后,i:2, yield_value:第2次执行next()后send的内容
while end
Traceback (most recent call last):
File "/Users/ganlixin/code/python_code_all/effective_python/item_34.py", line 83, in <module>
send_res = it.send("第2次执行next()后send的内容")
StopIteration
上面的执行结果看起来比较混乱,但是我们可以一步一步分析:
- 第一次调用next(),此时进入循环:
- 打印
test_yield yield前,i:0
, - 执行暂停在yield语句,返回数据0,外部打印内容
第1次执行next()结果:0
; - 循环的执行暂停在
yield_value = yield i
这里了;
- 打印
- 调用send方法,代码继续从暂停的地方开始执行,
- 打印了yield语句的返回值,
test_yield yield后,i:0, yield_value:第1次执行next()后send的内容
,这个yield_value
和send
的值一样; - 然后继续执行循环
i+=1
,然后i<3
满足循环条件,打印test_yield yield前,i:1
,此时返回数据1, - 外部打印
第1次执行send()结果:1
,然后yield暂停返回数据1后,执行又暂停在yield语句yield_value = yield i
;
- 打印了yield语句的返回值,
- 打印
"----------------------"
- 第二次调用next()方法,
- 接着前面yield执行,此时打印
test_yield yield后,i:1, yield_value:None
- 循环到yield前一行代码,打印
test_yield yield前,i:2
,然后返回数据2,代码又停在yield那里,此时打印第2次执行next()结果:2
- 接着前面yield执行,此时打印
- 第二次调用send方法
- 代码继续从yield执行,此时send的内容有作为yield的返回值赋值给yield_value,
- 接着打印
test_yield yield后,i:2, yield_value:第2次执行next()后send的内容
- 继续循环,
i+=1
,i<3不满足条件,此时循环结束,打印while end
- 由于迭代已经结束,所以抛出了StopIteration异常。
由于生成器一般使用的时候都是从里面获取迭代数据,很少有向迭代器中输入什么东西来更改迭代的执行,比如上面send就可以用来实现动态的更改循环执行,但是这样会使代码更加复杂,在排查问题的时候会非常难搞。
第35条 不要通过throw变换生成器的状态
用生成器的时候,不用一次性生成大量的数据,一次只获取一个数据进行处理;
如果处理一个数据后,发现有异常情况,此时要想终端迭代,可以直接使用迭代器来抛异常,中断执行,如下所示:
# coding:utf-8
def my_generator():
i = 0
while i < 5:
yield i
it = my_generator()
print(next(it))
print(next(it))
# 抛异常,中断循环
it.throw(Exception("中断yield执行"))
print("yes")
输出如下:
0
0
Traceback (most recent call last):
File "/Users/ganlixin/code/python_code_all/effective_python/item_35.py", line 14, in <module>
it.throw(Exception("中断yield执行"))
File "/Users/ganlixin/code/python_code_all/effective_python/item_35.py", line 7, in my_generator
yield i
Exception: 中断yield执行
上面的执行结果看起来,调用it.throw(Exception("中断yield执行"))
中断了整个执行流程(没有打印最后的yes
),和外面直接raise Exception一样的效果;
如果我们只是想中断迭代,而不是中断整个执行流程,这样的话,我们就可以在生成器function里面改动,如下所示:
def my_generator_v2():
i = 0
while i < 5:
try:
yield i
except Exception as e:
# 获取到异常后,中断循环
print(e)
break
# 由于中断循环后,还是需要继续往外返回一个迭代结果(否则会抛出StopIteration异常)
yield None
it = my_generator_v2()
print(next(it))
print(next(it))
# 抛异常,中断执行流程
it.throw(Exception("中断yield执行"))
print("yes")
执行输出:
0
0
中断yield执行
yes
上面虽然能实现功能,但是并不够优雅,可以使用这面这种check机制:
class MyGenerator(object):
def __init__(self, init_value, max_value):
self.init_value = init_value
self.max_value = max_value
self.stop_iter_flag = False # 是否停止迭代
def __iter__(self):
i = self.init_value
while i < self.max_value and not self.stop_iter_flag:
yield i
i += 1
def stop_iter(self):
self.stop_iter_flag = True
it = MyGenerator(init_value=0, max_value=5)
for item in it:
print(item)
if item >= 3:
it.stop_iter()
print("finish")
输出结果:
0
1
2
3
finish
第36条 考虑用itertools拼装迭代器与生成器
主要就是itertools下面的一些功能函数,可以帮助我们提高编程效率。
chain
除了使用yield from
将多个迭代器连接在一起之外,还可以使用chain来实现,示例如下:
# coding:utf-8
import itertools
def g_1():
for i in [1, 2, 3]:
yield i
def g_2():
for i in [4, 5, 6]:
yield i
def g_3():
for i in [7, 8, 9]:
yield i
def use_yield_from():
yield from g_1()
yield from g_2()
yield from g_3()
print(list(use_yield_from()))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
it = itertools.chain(g_1(), g_2(), g_3())
print("itertools.chain result type:{}".format(type(it))) # itertools.chain result type:<class 'itertools.chain'>
print(list(it))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
repeat
使用itertools.repeat()可以制作一个生成器,不断重复输出某个值:
it = itertools.repeat("hello", 3)
print(list(it))
# ['hello', 'hello', 'hello']
这个直接指定一个变量反复的方式,有啥区别吗?
cycle
一个生成器如果迭代完成后,再次迭代就会抛出StopIteration异常,由于迭代器没有reset接口,要么重新获取一个新的迭代器进行迭代,要么就是用itertools.cycle(),示例如下:
def my_generator():
i = 0
while i < 3:
yield i
i += 1
it = itertools.cycle(my_generator())
cnt = 0
res = []
while cnt < 10:
res.append(next(it))
cnt += 1
print(res)
# [0, 1, 2, 0, 1, 2, 0, 1, 2, 0]
tee
一般来说,调用一个迭代器的function会返回一个迭代器,这个迭代器迭代完成后,就需要重新获取一个新的迭代器。
而tee的话,就相当于一次性返回了多个迭代器,每个迭代器互不影响:
def my_generator_v2():
i = 0
while i < 3:
yield i
i += 1
it_1, it_2 = itertools.tee(my_generator_v2(), 2)
print(next(it_1)) # 0
print(next(it_1)) # 1
print(next(it_2)) # 0
print(next(it_1)) # 2
zip_longest
python有内置的zip函数,可以进行并行迭代,如下所示:
keys = [1, 2]
value = ["one", "two", "three"]
print("zip list result:{}".format(list(zip(keys, value))))
# zip list result:[(1, 'one'), (2, 'two')]
上面在并行迭代时,其中一个结束,那么整体的迭代就结束了。
使用itertools.zip_longest可以实现按照最长的list进行迭代,并设置空值,示例如下:
keys = [1, 2]
value = ["one", "two", "three"]
it = itertools.zip_longest(keys, value, fillvalue="empty_data")
print("zip list result:{}".format(list(it)))
# zip list result:[(1, 'one'), (2, 'two'), ('empty_data', 'three')]
islice
islice可以在不拷贝数据的前提下,按照下标切割源迭代器。
可以只给出切割的终点,也可以同时给出起点与终点,还可以指定步进值。
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print("只迭代前5个数据:{}".format(list(itertools.islice(data, 5))))
# 只迭代前5个数据:[1, 2, 3, 4, 5]
print("只迭代前2~5个数据:{}".format(list(itertools.islice(data, 2, 5))))
# 只迭代前2~5个数据:[3, 4, 5]
print("只迭代前1~8个数据,步长为2:{}".format(list(itertools.islice(data, 1, 8, 2))))
# 只迭代前1~8个数据,步长为2:[2, 4, 6, 8]
takewhile
takewhile会一直从源迭代器里获取元素,获取的元素需要满足条件,当不满足条件的时候,迭代终止。
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pass_condition = lambda i: i < 5
it = itertools.takewhile(pass_condition, data)
print(list(it))
# [1, 2, 3, 4]
dropwhile
与takewhile相反,dropwhile会一直跳过满足条件的源序列里的元素,然后它会从这个地方开始逐个取值,一直到迭代器结束。
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 , 1, 8]
skip_condition = lambda i: i < 5
it = itertools.dropwhile(skip_condition, data)
print(list(it))
# [5, 6, 7, 8, 9, 10, 1, 8]
filterfalse
python内置的filter可以过滤出满足条件的元素;而filterfalse则是过滤出不满足条件的,示例如下:
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list(filter(lambda i: i % 2 == 0, data)))
# [2, 4, 6, 8, 10]
print(list(itertools.filterfalse(lambda i: i % 2 == 0, data)))
# [1, 3, 5, 7, 9]
accumulate
这个函数和python的reduce()函数一样,都可以实现累加的功能
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(functools.reduce(lambda i, j: i + j, data))
# 55
print(list(itertools.accumulate(data)))
# [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
# 累加计算逻辑
def my_compute_logic(i, j):
return (i + j) * 2
print(list(itertools.accumulate(data, my_compute_logic)))
# [1, 6, 18, 44, 98, 208, 430, 876, 1770, 3560]
product
product接受多个迭代器,并进行计算笛卡尔积:
it = itertools.product(["one", "two"], [1, 2], ["x", "y"])
print(list(it))
# [('one', 1, 'x'), ('one', 1, 'y'), ('one', 2, 'x'), ('one', 2, 'y'),
# ('two', 1, 'x'), ('two', 1, 'y'), ('two', 2, 'x'), ('two', 2, 'y')]
permutations
计算出全排列
it = itertools.permutations([1, 2, 3])
print(list(it))
# [(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
it = itertools.permutations([1, 2, 3], 2) # 两两组成全排列
print(list(it))
# [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
combinations
在迭代器里面选择N个元素形成组合:
- 一个组合里面不会有相同的元素,比如不会出现
(1,1)
- 不会出现完全相同元素的组合,比如有
(1,4)
,但是就不会有(4,1)
it = itertools.combinations([1, 2, 3, 4], 3) # 后面的3表示选择3个元素
print(list(it))
# [(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]
combinations_with_replacement
在迭代器里面选择N个元素形成组合,不会出现相同元素的组合,比如有(1,4)
,但是就不会有(4,1)
it = itertools.combinations_with_replacement([1, 2, 3], 2) # 后面的2表示选择2个元素
print(list(it))
# [(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]