《Effective Python》笔记 第四章-推导与生成

阅读Effective Python(第二版)的一些笔记



原文地址:https://www.cnblogs.com/-beyond/p/16127475.html


第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

观察上面的代码和执行结果,可以得出以下结论:

  1. 调用带有yield的方法时,并没有进入for循环
  2. 第1次调用next()方法时会进行一次循环,并且进入循环后,只执行到yield的一行代码就停了,返回一个数据给next(),后面的代码未执行;
  3. 第2次调用next()方法时,会接着第一次yield代码开始执行,进行一次循环,然后又停到yield的那里,返回一个数据给next(),后面的代码未执行;
  4. 第3次调用next()方法时,会接着yield代码开始执行,进行一次循环,然后又停到yield的那里,返回一个数据给next(),后面的代码未执行;
  5. 第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():

  1. send的内容就是yield的返回值,
  2. send和next功能又有些相似,next()迭代可以获取结果,send(val)可以val作为yield的返回值,同时获取迭代结果,所以可以简单理解next() == send(None)
  3. 示例如下:
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

上面的执行结果看起来比较混乱,但是我们可以一步一步分析:

  1. 第一次调用next(),此时进入循环:
    1. 打印test_yield yield前,i:0
    2. 执行暂停在yield语句,返回数据0,外部打印内容第1次执行next()结果:0
    3. 循环的执行暂停在yield_value = yield i这里了;
  2. 调用send方法,代码继续从暂停的地方开始执行,
    1. 打印了yield语句的返回值,test_yield yield后,i:0, yield_value:第1次执行next()后send的内容,这个yield_valuesend的值一样;
    2. 然后继续执行循环i+=1,然后i<3满足循环条件,打印test_yield yield前,i:1,此时返回数据1,
    3. 外部打印第1次执行send()结果:1,然后yield暂停返回数据1后,执行又暂停在yield语句yield_value = yield i
  3. 打印"----------------------"
  4. 第二次调用next()方法,
    1. 接着前面yield执行,此时打印test_yield yield后,i:1, yield_value:None
    2. 循环到yield前一行代码,打印test_yield yield前,i:2,然后返回数据2,代码又停在yield那里,此时打印第2次执行next()结果:2
  5. 第二次调用send方法
    1. 代码继续从yield执行,此时send的内容有作为yield的返回值赋值给yield_value,
    2. 接着打印test_yield yield后,i:2, yield_value:第2次执行next()后send的内容
    3. 继续循环,i+=1,i<3不满足条件,此时循环结束,打印while end
  6. 由于迭代已经结束,所以抛出了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)
  2. 不会出现完全相同元素的组合,比如有(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)]
posted @ 2022-04-10 20:57  寻觅beyond  阅读(126)  评论(0编辑  收藏  举报
返回顶部