用推导取代map和filter
序列推导可取代map和filter,优越性有:1可读性强2不需要map的函数
控制推导逻辑的子表达式不要超过2个
即推导的for层数最多建议两层,多了可读性会下降,反而用for循环会清晰
一层for内可连接多个if,if的关系为and:[i for i in range(20) if i % 2 ==0 if i % 3 == 0]
用赋值表达式消除推导中重复代码
序列推导中能定义变量的地方只有for内部,如果希望用if判断的结果,需要重写一遍if的内容,如果if用函数判断,那这个函数则要执行2次,用之前的知识可以通过海象运算符解决,可将函数调用次数只调一次
序列推导中海象运算符需要在for或if中先定义,如果在推导式中产生新值的地方(for前面)定义,然后在if引用,会报错变量未定义
如果在序列推导中没有if,然后在新值部分用了赋值表达式,那赋值表达式的变量会泄漏(类似于for i in range(5)),执行完后i是4,称这种叫泄漏。所以建议赋值表达式在序列推导中最好定义在if中
总结 1序列推导使用赋值运算符可简化代码提高可读性 2赋值运算符在序列推导里的定位位置建议优先在if中定义
函数不应该直接返回列表而是逐个生成列表里的值
当函数返回结果个数较多时,需要在函数构造返回列表并对列表append,消耗了一定代码,而且append是业务无关代码,体现不出业务,此时可用生成器,即yield,可避免结果列表的构造和append调用还能节省代码行还能节省构造列表产生的内存开销和列表操作时间开销
用yield注意点就是只能调用一次,再次调用需要重新调用函数,而不是把调用结果存到一个变量,下次调这个变量
小心迭代函数收到的参数
背景:当函数传入参数是生成器时,但函数内部又对这个生成器多次遍历,则只有第一次遍历会成功,其余遍历没有内容,可能会产生bug。但这样写的本意是减少对列表的构造,直接用序列
解决方法:在函数内部开始将生成器list化,但这样就起不到用生成器的优点了
解决方法1:改变函数的入参,不传入现成的生成器,而是传入产生生成器的函数,这样每次需要生成器时调用这个生成器函数,比如可以传入一个lambda匿名函数或者已经定义好的函数。这个方案的缺点是入参函数可读性可能较差,尤其是用lambda传入时
解决方法2:自定义一个实现__iter__方法的容器类,然后传参这个容器类的实例而不是解决方案1的入参函数。这样函数内每次遍历容器类时会调__iter__,每次调都会产生一个新的生成器。这个方案的缺点是每次构造生成器需要重新组装数据存在开销
综合解决方案:1先对入参校验类型,如果是自定义容器类可继续,如果是生成器则异常,判断生成器可用iter(generator) == generator实现,如果是生成器,iter(generator)会返回generator本身,让这个判别式为True,判断生成器类型也可使用collections.abc.Iterator类,然后用isinstance判断
用生成器表达式改写数据量较大的序列
数据量大时如果用列表生成则容易耗尽内存,此时建议用生成器,比较省内存
生成器也可以嵌套使用,即这个生成器定义时的数据来源可以是其他生成器
注意点是生成器只能用一次,多次使用时可以重新定义或调用其产生函数
用yield from连接多个生成器
连续使用多个生成器时,可能代码比较多
可以使用yield from优化,yield from后直接跟生成器,免去了for访问生成器,也节省代码行
不要用send给生成器注入数据
先快速理解send怎么玩
怎么理解:可理解为一个原子操作为先send再yield,而第一个yield前面需要有一个赋值,需要一个send,所以send None(因为send非None报错不知道为啥),后面正常操作即可
作用 生成器的send方法可实现对生成器双通道操作,即yield是生成器向外部传东西,而send是从外部向生成器内部传东西。使用yield很简单,对生成器迭代赋值即可,在生成器内部使用外部send进来的变量句法类似于a = yield b,最后a的值是外部send的传参而不是b,b是yield到外部的值
上面例子局限性 1看代码很难看懂,只有理解yield的人才能看懂 2多次调用时,总需要处理第一次send值为None的情况
解决方法 不用send了,send预期的结果可以通过向函数传递send参数列表组成的生成器实现,即避免了句法难懂和首次None的处理,功能也能ok
不要通过throw变换生成器的状态
原理 对生成器迭代时,可调用生成器的throw方法抛异常,此时如果成功抛异常,生成器下次next直接stopiteration,如果生成器处理了异常没有抛出,则next可继续往后迭代
好处 双通道
坏处 难懂,且要为生成器捕获异常写代码行,需要一些if else嵌套结构作为代码行开销
解决方法 不要通过throw给生成器注入,通过支持迭代(__iter__)的容器类作为解决方法,参数和异常通过类属性和类方法实现
考虑用itertools连接迭代器和生成器
迭代器连接方法
itertools.chain 首尾连接迭代器和生成器
itertools.repeat 构造元素重复指定次数的迭代器
itertools.cycle 迭代时循环返回入参的参数
itertools.tee 创多个平行迭代器
itertools.zip_longest zip压缩的多个迭代器长度不相同时,迭代次数为最长的那个迭代器,缺失值可提供参数fillvalue='xxx'实现
过滤迭代器元素
itertools.isslice(sequence, start_index, end_index, stride) 类似于序列切片
itertools.takewhile(sequence, take_condition)
itertools.dropwhile(sequence, drop_condition)