前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >搞懂这10 行代码,究竟有多难?

搞懂这10 行代码,究竟有多难?

作者头像
double
发布2019-11-14 17:13:18
5360
发布2019-11-14 17:13:18
举报
文章被收录于专栏:算法channel算法channel

本文为第321篇原创

我愿竭尽所能,为你带些温暖

1初衷

文章 Python要点总结,我使用了100个小例子!发出后,有几个朋友问我itertools 的函数实现不太理解,问我是否能添加详细的注释,以解惑。今天我拿出2个多小时总结一个函数,一共10几行代码,并添加详细注释,希望能帮助到更多人!

2 例子

这是zip的加强版函数大概实现过程,下面逐行分析每行代码含义。

from itertools import *

def zip_longest(*args, fillvalue=None):
    iterators = [iter(it) for it in args]
    num_active = len(iterators)
    if not num_active:
        return
    while True:
        values = []
        for i, it in enumerate(iterators):
            try:
                value = next(it)
            except StopIteration:
                num_active -= 1
                if not num_active:
                    return
                iterators[i] = repeat(fillvalue)
                value = fillvalue
            values.append(value)
        yield tuple(values)

使用zip_longest:

a = [1, 2, 3]
b = [4, 5, 6,7,8,9]
r = zip_longest(a, b, fillvalue='-')
while True:
    try:
        val = next(r)
        print(val)
    except StopIteration:
        print('end')
        break
(1) 导入itertools
from itertools import *
(2) 定义函数
def zip_longest(*args, fillvalue=None):

参数args前面带星表示可变参数,支持传入多个关键字参数a, b, c, …

fillvalue=None, 表示fillvalue是位置参数,此处表示短列表的默认填充值

(3) 列表生成式
iterators = [iter(it) for it in args]

args是可迭代对象,it为其元素,iter(it)表示转换it为迭代类型的对象,此处因为 a=[1,2,3], b=[4,5,6,7,8,9] 所以args = ([1,2,3],[4,5,6,7,8,9]), iterators = [iter(a),iter(b)]

(4) 需要zip的个数
num_active = len(iterators)
(5) 边界检查
if not num_active:
        return

如果num_active为0,即*args为空,则直接返回

(6) 终止条件
while True:
   ...

    if not num_active:
          return

whileyield tuple(values)组合后,控制着yield的继续执行后,下一行代码在values=[]

同时组成zip_longest的终止条件,只有当*args中的所有参数都扫描一遍后,zip的任务才结束。

(7) 最大程度节省内存
values = []
...
yield tuple(values)

不要小瞧values置为空,它和yield这句代码遥相呼应,保证每完成yield后都会及时清空values,确保内存使用到最少。

(8) enumerate装饰
for i, it in enumerate(iterators):
    ...
    iterators[i] = repeat(fillvalue)

enumerate装饰后会得到一个由index和元素本身组成的tuple,举个例子:

In [1]: for i,ele in enumerate([9,4,2,5,8]):
   ...:     print((i,ele))
   ...:
(0, 9)
(1, 4)
(2, 2)
(3, 5)
(4, 8)

此处使用enumerate,因后面先遍历完的iter(a),为保证和iter(b)个数对齐,需要填充fillvalue值,需要知道a的index,此处为0.

(9) 捕获迭代终止异常
 try:
     value = next(it)
 except StopIteration:
      ...

try… except是异常捕获的标准模板,next(it)第一次执行返回it的第一个元素,a=[1,2,3],所以value值为1.

(10)计数

num_active表示当前存活的列表个数,一旦一个列表迭代到终点,num_active立即减1

num_active -= 1
(11) repeat
iterators[i] = repeat(fillvalue)

repeat是itertools模块内另一个函数,如果times为默认值,则表示重复无限次,此处是一个repeat的典型用法。此处稍加注意,不要以为times参数默认值为1,或者不理解为什么要无限次重复,记住这种用法。

此处,repeat返回一个无限次重复fillvalue值的迭代器,并赋值给此时触发StopIteration异常的列表。此处就是iter(a),令其指向repeat创建的迭代器。

(12)yield返回值
        value = fillvalue
    values.append(value)
yield tuple(values)

第一句,是在触发StopIteration时,将默认值fillvalue赋给value,values保存住列表对齐后位置的元素,第一次for退出时,values内值为[1,4]

(13)yield

有些朋友不理解yield用法,简单来说它就是特殊的伪return返回一次值;且我们再次调用next(r)时,它会再次跑到yield的下一行重新开始执行,此处yield的下一行是values = []

a = [1, 2, 3]
b = [4, 5, 6,7,8,9]
r = zip_longest(a, b, fillvalue='-')
while True:
    try:
        val = next(r)
        print(val)
    except StopIteration:
        print('end')
        break

故输出如下:

(1, 4)
(2, 5)
(3, 6)
('-', 7)
('-', 8)
('-', 9)
end

3 完整注释

from itertools import *

#参数args前面带星表示可变参数,支持传入多个关键字参数a, b, c, ...
#fillvalue=None, 表示fillvalue是位置参数,
#此处表示短列表的默认填充值
def zip_longest(*args, fillvalue=None):
    #`args`是可迭代对象,`it`为其元素,`iter(it)`表示
    #转换`it`为迭代类型的对象,此处因为 a=[1,2,3],       
    #b=[4,5,6,7,8,9] 所以args = ([1,2,3],[4,5,6,7,8,9]), 
    #iterators = [iter(a),iter(b)]
    iterators = [iter(it) for it in args]
    num_active = len(iterators)
    #如果num_active为0,即*args为空,则直接返回
    if not num_active:
        return
    #`while`和`yield tuple(values)`组合后,
    #控制着yield的继续执行后,下一行代码在`values=[]`
    #同时组成zip_longest的终止条件,只有当*args中的所有参数都扫描一遍后,
    #zip的任务才结束。
    while True:
        #不要小瞧values置为空,它和yield这句代码遥相呼应,
        #保证每完成yield后都会及时清空values,确保内存使用到最少。
        values = []
        #enumerate装饰后会得到一个由index和元素本身组成的tuple
        #此处使用enumerate,因后面先遍历完的iter(a),
        #为保证和iter(b)个数对齐,需要填充fillvalue值,需要知道a的index,此处为0.
        for i, it in enumerate(iterators):
            #try... except是异常捕获的标准模板,
            #next(it)第一次执行返回it的第一个元素,a=[1,2,3],所以value值为1.
            try:
                value = next(it)
            except StopIteration:
                #num_active表示当前存活的列表个数,
                #一旦某个列表迭代到终点,num_active立即减1
                num_active -= 1
                if not num_active:
                    return
                #repeat返回一个无限次重复`fillvalue`值的迭代器,
                #并赋值给此时触发`StopIteration`异常的列表。
                #此处就是iter(a),令其指向repeat创建的迭代器。
                iterators[i] = repeat(fillvalue)
                #第一句,是在触发StopIteration时,
                #将默认值fillvalue赋给value,
                #values保存住列表对齐后位置的元素,第一次for退出时,values内值为`[1,4]`
                value = fillvalue
            values.append(value)
        #有些朋友不理解yield用法,简单来说它就是特殊的`伪return`返回一次值;
        #且我们再次调用next(r)时,它`会再次跑到yield的下一行重新开始执行`,
        #此处yield的下一行是`values = []`
        yield tuple(values)

以上,通过一个函数zip_longest,我们就能学到很多个Python知识点,并且真正理解它们的用法。如果觉得有帮助,欢迎在看或分享

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员郭震zhenguo 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1初衷
  • 2 例子
    • (1) 导入itertools
      • (2) 定义函数
        • (3) 列表生成式
          • (4) 需要zip的个数
            • (5) 边界检查
              • (6) 终止条件
                • (7) 最大程度节省内存
                  • (8) enumerate装饰
                    • (9) 捕获迭代终止异常
                      • (10)计数
                        • (11) repeat
                          • (12)yield返回值
                            • (13)yield
                            • 3 完整注释
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档