《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)]
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2020-04-10 Java将\u开头的unicode字符串转换为中文