2021-04-26: python for ... in ... and generator and iterator

解释python的生成器,迭代器,和 for … in …语法糖


for ... in ...语法糖

  • for... in... 做了什么事情?产生一个迭代器(),并调用__next__方法逐一获得值
for value in iter_obj:
   <do something>
等同于:
iter_obj_itor = iter(iter_obj) # 返回一个迭代器

while True:
   try:
       value = iter_obj_itor.__next__()
       <do something>
   except:
       raise StopIteration
  • 其中, iter 方法返回了一个迭代器iterator(定义在后面解释),然后通过__next__方法不断取得迭代器对象的当前值; iter() 函数等同于:
def iter(obj):
    return obj.__iter__() # 返回了一个迭代器
  • next() 函数等同于
def next(obj):
    return obj.__next__()
  • for ...in... 举例:
lst = [1,2,3]
lst_ = []
for v in lst:
    lst_.append(v)
    
#等价于:
lst = [1,2,3]
lst_ = []
lst_iterator = lst.__iter__()
while True:
    v = lst_iterator.__next__()
其中,
from collections.abc import Iterable,Iterator,Generator
isinstance(lst_iterator, Iterator)  >> True

解释迭代器(iterator) 和可迭代对象(iterable)

  • 首先,在python的类型定义里:
class Iterable(object)
class Iterator(Iterable)
  • Iterator 是迭代器,迭代工具,功能是逐次调用__next__方法,返回当前值并运行到下一个迭代对象
  • Iterable 是可迭代对象,是一个可以返回自身所有值的容器,可以用于for ... in ... 语法糖或者map等方法, Iterable通过__iter__方法返回一个迭代器对象,然后后续迭代运行则利用此迭代器进行,比如上面的for ... in ...的等价运行
    值得注意的是Iterator也要实现__iter__方法并返回自身,因此Iterator 也是Iterable对象,这样做的好处是让iterator也可以用于for... in...等语法糖遍历方法(具体原因在for的运行机制里)
class Iterable_cls(object):
    def __init__(self):
        pass
    def __iter__(self):
        pass
    
class Iterator_cls(object):
    def __init__(self,):
        pass
    def __iter__(self):
        return self # 返回自身
    def __next__(self):
        pass
  • 举例:
class Iterable_cls(object):
    # 是iterable不是iterator
    def __init__(self, baselist):
        self.baselist = baselist
    def __iter__(self):
        return iter(self.baselist)

class Iterable_cls(object):
    # 是iterable不是iterator
    def __init__(self, baselist):
        self.baselist = baselist
    def __iter__(self):
        for i in self.baselist:
            yield i
    
class Iterable_cls(object):
    # 是iterator
    def __init__(self, baselist):
        self.baselist = baselist
        self.index=-1
    def __iter__(self):
        return self
    def __next__(self,):
        self.index += 1
        return self.baselist[self.index]
  • 非可迭代对象,但可以进行迭代操作:当类型实现了__getitem__的时候,也可以用于for…in…语法糖,只是迭代以下标为基础,默认从0下标开始;
  • 同样,iter()也可以针对这种类型实例返回一个迭代器;但是这种类不是iterable的
class NonIterable(object):
    def __init__(self, baselist):
        self.baselist = baselist
        
    def __getitem__(self,index):
        return self.baselist[index]
    
for i in NonIterable([1,2,3]): pass # 可运行
iterator_ = iter(NonIterable([1,2,3]))

from collections.abc import Iterable,Iterator,Generator
isinstance(iterator_, Iterator) #  True
isinstance(NonIterable([1,2,3], Iterable) # False

generator

  • generator的实现有两种,一种是列表表达式(x for x in <expression>), 另一种是使用yield关键字
  • yield 关键字:yield 将函数变成一个生成器函数,generator 必然有__iter__和__next__方法,与上述Iterator的方法执行效果一样
    当生成器函数被调用的时候,生成器函数不执行内部的任何代码,直接立即返回一个迭代器。
    当所返回的迭代器第一次调用next的时候,生成器函数从头开始执行,如果遇到了执行yield x,next立即返回yield值x。
    当所返回的迭代器继续调用next的时候,生成器函数从上次yield语句的下一句开始执行,
    直到遇到下一次执行yield任何时候遇到函数结尾,或者return语句,抛出StopIteration异常。
  • 启动生成器的一次迭代的方法可以是<generator>.send()<generator>.__next__()
def gen_func():
    print('head')
    yield 1
    print('first')
    
    yield 2
    print('second')

gen = gen_func() # 
ininstance(gen, Generator) >> True
isinstance(gen, Iterator) >> True

gen.__next__() # 第一次迭代, 函数从头部运行到第一个'yield x 运行结束' 处暂停
>> 'head'
>> 1
gen.__next__() # 第二次迭代, 函数从上一个暂停的地方运行到下一个'yield x 结束'处暂停
>> 'first'
>> 2
gen.__next__() # 第三次迭代, 函数从上一个暂停的地方运行到下一个yield,已经没有下一个元素,所以会抛出StopIteration
>> 'second'
>> StopIteration
  • 解释send: 上述函数也可以用send来进行迭代
gen = gen_func1()
gen.send(None)
gen.send(None)
gen.send(None)
结果和next一样
  • send(<input_arg>) 做了什么? 在yield x将x值输出到生成器外部后,将input_arg作为 yield x 语句返回值的替代输入到生成器内部;
  • 首先,yield x 语句是有返回值None的,因此yield x 是将x输出到外部,并在内部产生一个返回值None:
def gen_func2():
    # 生成器函数
    x = 1
    while True:
        y = yield x*2 # 将 yield语句的返回值赋给y
        x *= 2
        print('y: ',y)

gen = gen_func2()
gen.__next__() # 输出 x*2, 并停止在yield x结束处,具体在何处?下面会解释
>> 2
>> gen.__next__() # 
>> 'y: 'None # 可见yield x返回None
>> 4
  • 然后,y = yield x具体在一次迭代中暂停的位置,实际上是在yield x 的左侧,停在赋值之前:
y = /<pause>/(yield x*2)
  • python 代码的读法和运行是从左到右,但进行赋值语句时,先将=右侧的语句运行完后才会进行赋值,因此y = yield x的迭代过程:
y = (yield x):
1. yield x, Pause # 一次迭代,out x, return None,暂停
2. y = None,  <expression>, yield x, Pause # 下一次迭代,赋值,运行到yield x结束暂停
  • 所以,send(input_arg)代替了yield x的值,则将 input_arg 赋值给了y
gen = gen_func2()
gen.send(None) # 首次运行,没有到yield x,所以只能是None
gen.send(1) 
>> 2
gen.send(1) 
>> 'y: '1 # send(value) 替代yield x 的返回值,赋值给了y
>> 4

参考:

  1. https://www.zhihu.com/question/44015086
  2. https://zhuanlan.zhihu.com/p/266512848
  3. python-generator-send-function-purpose
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值