0%

Effective python笔记四

第四章 推导与生成

1.用列表推导取代map与filter

python中可以根据某个序列或可迭代对象派生出一份新列表,称为列表推导:

image-20221031193539378

它还可以过滤原列表,把某些输入值对应的计算结果从输出结果中排除:

image-20221031193745000

相当于同时使用了map与filter,但是比那种写法要更易懂

image-20221031194038187

字典与集合也有相应的推导机制,分别叫做字典推导与集合推导

image-20221031194128079

2.控制推导逻辑的子表达式不要超过两个

列表推导支持多层循环,例如将二维列表转换为一维:

image-20221031194307442

多层循环也可以用来设置两层深的结构

image-20221031195019243

但是更深的层次会导致列表推导过于难懂,不如直接使用for循环构造:

image-20221031195329701

image-20221031195338350

顺带一提,多个if条件在同一级别是and关系:

image-20221031195637328

在表示推导逻辑时,最多之应该写两个子表达式(例如两个if条件、两个for循环、或者一个if条件与一个for循环)。只要实现的逻辑比这个要复杂,就应该采用普通的if与for语句实现,并可以考虑编写辅助函数。

3. 用赋值表达式消除推导中的重复代码

image-20221031200429059

假设要对订单中每个货物以8个一批的方式发货,并要判断货物库存是否能够够一批标准,通过order与stock的相同字段来进行匹配。

image-20221031200602394

这里调用了两次get_batches方法,在只更改其中一个方法时会导致方法错误。因此要使用赋值表达式来消除推导中的重复代码。

image-20221031200953832

image-20221031201055313

4. 不要让函数直接返回列表,应该让它逐个生成列表里的值

写一段代码来找出文本中每个单词的开头位置

image-20221114192619978

改用生成器会更好,首先,会更加简洁:

image-20221114210102183

其次,不用一次性构造整个列表,例如,不用先读出全文,可以每行每行递进

image-20221114210254760

image-20221114210319964

5. 函数在迭代参数前要先判断参数的类型

源于这样一种考量:

  • 若传来的是一个迭代器或生成器,则只能产生一次结果,不能够遍历两边

    image-20221115120059625

    image-20221115120136765

  • 使用list构造列表时也会出现同样问题,导致已经耗完数据的迭代器返回空列表

    image-20221115120222787

  • 先copy出所有结果又会导致数据过多,从而有内存崩溃的风险

    image-20221115120334187

因此最好的方式是在函数的两次迭代sum(numbers)for value in numbers中每次都先拿到一个新的迭代器:

image-20221115120452537

image-20221115120502305

python中提供了一个接口用于实现迭代器协议(iterator protocol):

Python执行for x in foo这样的语句时,实际上会调用iter(foo),也就是把foo传给内置的iter函数。这个函数会触发名为foo.__iter__的特殊方法,该方法必须返回迭代器对象(这个迭代器对象本身要实现__next__特殊方法)。最后,Python会用迭代器对象反复调用内置的next函数,直到数据耗尽为止(如果抛出StopIteration异常,就表示数据已经迭代完了)。

因此只需要新建一个容器类:

image-20221115120724313

所以,在编写函数处理迭代时,最好先检查一下迭代的类型,防止在函数中出现数据耗尽的问题,并报出TypeError错误来提示接口使用者:

image-20221115120901712

image-20221115120812819

6. 考虑用生成器表达式改写数据量较大的列表推导

例如读写文件时,直接使用列表推导需要将文件中的每行文本都载入内存,对于庞大的文件会导致内存耗尽。

image-20221116191746244

要想处理大规模数据,可以使用生成器表达式,生成器表达式每次给出一个结果:

image-20221116191925589

同时生成器表达式还可以进行嵌套:

image-20221116192114634

使用时可以用内置的next函数取得结果:

image-20221116192159277

但是要注意迭代器在跑完一轮后会导致数据耗尽,无法继续使用,此时需要新生成一个迭代器。

7.通过yield from连接多个生成器

image-20221116193338341

image-20221116193346997

连接多个生成器可以采用上面的方式,采用多个for遍历。

image-20221116193436127

可以改成使用yield from来简化,且这种方式速度更快,因此优先使用yield from来连接生成器:

image-20221116193613100

8. 不要用send给生成器注入数据

send,调用send方法时传入的参数会成为上一条yield表达式的值,生成器拿到这个值后,推进到下一条yield。在刚开始推进生成器时,是从头执行的,而不是从某一条yield继续,因此首次调用时只能传None。

image-20221116203636008

image-20221116203645304

由于这种逻辑上的复杂性,这样写会导致代码可读性较差,因此应当用多个yield from来代替。

image-20221116203741474

image-20221116203756046

9. 不要通过throw变换生成器的状态

通过iter.throw为生成器注入异常,则异常会被重新抛出。

image-20221116204051352

这样的方式通常被用来改变生成器的状态,例如如下的一个计时器的重置。

image-20221116204341428

image-20221116204349695

然而这样的双向通信逻辑复杂,不如采用如下的一个可迭代的容器对象:

image-20221116204432596

image-20221116204443237

10. 考虑用itertools拼装迭代器与生成器

如果要实现比较难写的迭代逻辑,可以查看itertools文档,去查找可能用得到的函数。

这些函数分为三大类:

连接多个迭代器

chain

chain可以把多个迭代器从头到尾连接成一个迭代器。

image-20221116204715587

repeat

repeat可以不停地输出某个值:

image-20221116204754918

cycle

cycle可以循环地输出各项元素:

image-20221116204838412

tee

分裂多个平行的迭代器,每个迭代器推进的速度可能不一致

image-20221116204936412

zip_longest

与zip类似,如果长度不同,会用fillvalue来填补耗尽的那些迭代器留下的空缺:

image-20221116205030455

过滤源迭代器中的元素

islice

按照下标切割源迭代器,给出起点、终点、步进值。

image-20221116205158086

takewhile

一直获取元素,直到某元素让测试函数返回False为止。

image-20221116205254111

dropwhile

会一直跳过元素,知道某个元素让测试函数返回True为止,然后开始逐个取值。

image-20221116205405197

filterfalse

与filter相反,逐个返回False的那些元素

image-20221116205453294

用源迭代器中的元素合成新元素

accumulate

取出元素,与累加结果一起传给表示累加逻辑的函数,然后输出那个函数的计算结果作为新的累计值。

image-20221116205653430

product

获取元素计算笛卡尔积,可以取代那种多层嵌套的列表推导代码:

image-20221116205753288

permutations

输出所有有序排列,如果元素相同但顺序不同算两种排列。

image-20221116205901224

combinations

所有无序排列,即如果元素相同但顺序不同算同一种组合:

image-20221116205958924

combinations_with_replacement

可放回的排列,允许同一个元素在组合里多次出现。

image-20221116210057837

欢迎关注我的其它发布渠道