iterator&generator

时间:2021-08-09
本文章向大家介绍iterator&generator,主要包括iterator&generator使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

可迭代对象&迭代器&生成器

三者之间的关系:

a 集合包含图:                                                                                                                                     b 关系转换图: 

c从属关系图:

 可迭代对象(iterable)

   简单来说如果一个对象(python中一切都是对象)只要实现了__iter__()魔法方法,那么用isinstance()函数检查就是Iterable对象。

      python中常见的可迭代对象:

    1. 集合或者序列类型(如: list, tuple,  set, dict, str)

    2. 文件对象,socket对象

    3.在类中定义了__iter__()魔法方法的对象,可以被认为是Iterable对象,

      但是自定义的可迭代对象要能在for循环中正确使用,就需要保证__iter__()实现必须是正确的。(既可以通过内置的iter()函数转成Iterator对象。)

      iter()函数是能够将一个可以迭代的对象转成迭代器对象,然后再for循环中使用。

    4. 在类中实现了如果只实现了__getitem__()方法的对象可以通过iter()函数转成迭代器但其本身不是可迭代对象。

      所以一个对象能够在for循环中运行,但不一定是Iterator对象       

    e.g:

      理解1,2点:(list,tuple,set,dict,str,file,socket都是可迭代的iterable)

      

from collections.abc import Iterable, Iterator, Generator

print(isinstance([], Iterable))
print(isinstance((), Iterable))
print(isinstance({}, Iterable))
print(isinstance('', Iterable))
print(isinstance(set(), Iterable))

file = r"D:\Zj\sq_note"
with open(f'{file}\\test_getitem.txt') as f:
    print(isinstance(f, Iterable))

      

    这些内置集合或序列对象都有__iter__属性,即他们都实现了同名方法。但是这个可迭代对象要在for(for ……in……)循环中被使用,

    那么它就应该能够被内置的iter()函数调用并转化成Iterator对象。

    理解第3点

              e.g: 

 1 def IsContainIterMethod(x):
 2     if '__iter__' in dir(x): #判断对象中是否包含__iter__魔法方法
 3         print('True')
 4         
 5         
 6 IsContainIterMethod([])
 7 IsContainIterMethod({})
 8 IsContainIterMethod(())
 9 IsContainIterMethod('')
10 IsContainIterMethod(set())
11 
12 #输出全是:True
13 
14 #这里输出一个查看对象中包含的方法
15 print(dir([]))
16 """输出:
17 ['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', 
18 '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', 
19 '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', 
20 '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 
21 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
22 """
23 
24 #转化成迭代器
25 print(isinstance([], Iterable))#列表是可以迭代的 True
26 print(isinstance([], Iterator))#列表不是迭代器 Fales
27 
28 print(isinstance(iter([]), Iterator))#可迭代对象可以经过iter()函数转成迭代器True
29 print(isinstance(iter([]), Iterable))#迭代器是可以迭代的True

    

    e.g:

    一个对象实现了__iter__()魔法方法,那么用isinsatance()函数检查这个就像就是可迭代的对象:

    

1 class StuIterObj:
2     def __iter__(self):
3         return self
 4 5 a = StuIterObj() 6 print(isinstance(a, Iterable))#True 7 print(isinstance(a, Iterator))#False 8 print(isinstance(a, Generator))#False 9 print(iter(a)) 
#TypeError: iter() returned non-iterator of type 'NoneType'

    异常:iter()函数不能讲“非迭代器”类型转成迭代器。

    

    如何才能讲一个可迭代(iterable)对象转成迭代器(Iterator)对象呢?

    修改StuIterObj类的定义:

class StuIterObj:
    def __init__(self):
        self.a = [3, 5, 7, 11, 13, 17, 19]
    def __iter__(self):
        return iter(self.a)

it = StuIterObj()
print(isinstance(it, Iterable))#True
print(isinstance(it, Iterator))#False
print(isinstance(it, Generator))#False
print(isinstance(iter(it), Iterator))#True

for i in it:
    print(i)#将打印3,5,7,11,13,17,19

    我们在构造方法中定义了名为a的列表,然后还实现了__iter__()魔法方法。

    修改后的类可以被iter()函数调用,也可以使用for循环使用。 

    因此:在定义一个可迭代对象时,我们要非常注意__iter__()方法内部的实现逻辑,一般情况下,是通过一些已知的可迭代对象

    (例如:上文提到的集合,序列,文件等或其他正确定义的可迭代对象)开辅助我们来实现。

    

  

    理解第4点

      说明:第4点要说明的意思是iter()函数可以将一个实现了__getitem__()魔法方法的对象转成迭代器对象,也可以在for循环中使用,

      但是如果isinstance()方法来检测的时候,它不是一个可迭代对象

      e.g:

 1 class StuIterObj:
 2     def __init__(self):
 3         self.a = [3, 5, 7, 11, 13, 17, 19]
 4     def __getitem__(self, i):
 5         return self.a[i]
 6 
 7 it = StuIterObj()
 8 print(isinstance(it, Iterable))#false
 9 print(isinstance(it, Iterator))#false
10 print(isinstance(it, Generator))#false
11 print(hasattr(it, "__iter__"))#<iterator object at 0x0412AF10>
12 print(iter(it))
13 for i in it:
14     print(i)
15 
16 #输出3, 5, 7, 11, 13, 17, 19

    summary:(上面一个例子说明可以在for中使用的对象,不一定是可迭代对象)

      1. 一个可以迭代的对象是实现了__iter__()方法的对象
      2. 它要在for循环中使用,就必须满足iter()的调用(即调用这个函数不会出错,能够正确转成一个Iterator对象)
      3. 可以通过已知的可迭代对象来辅助实现我们自定义的可迭代对象
      4. 一个对象实现了__getitem__()魔法方法可以通过iter()函数转成Iterator,即可以在for循环使用
        但它不是一个可迭代对象(可用isinstance()检测)    

 迭代器(Iterator)

  一个对象实现了__iter__()和__next__()魔法方法阿么它就是一个迭代器对象。

  e.g:

class IterObj:
    def __init__(self):
        self.a = [3, 5, 7, 11, 13, 17, 19]
        self.n = len(self.a)
        self.i = 0
        
    def __iter__(self):
        return iter(self.a)
    
    def __next__(self):
        while self.i <self.n:
            v = self.a[self.i]
            self.i += 1
            return v
        else:
            self.i = 0
            raise StopIteration()

it = IterObj()
print(isinstance(it, Iterable))#ture
print(isinstance(it, Iterator))#ture
print(isinstance(it, Generator))#false
print(hasattr(it, "__iter__"))#ture
print(hasattr(it, "__next__"))#ture
            

    从以上可以发现:集合和序列是可以迭代的,但不是迭代器,文件是迭代器。

            一个迭代器对象不仅可以在for循环中使用,还可以通过内置函数next()函数调用

print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))

"""
3
5
7
11
13
17
19

    raise StopIteration()
StopIteration

"""

 

生成器(Generator)

   描述:一个生成器既是可迭代的又是迭代器

      是生成数据的表达式或者函数。

   定义生成器的两种形式:

     1 列表生成器

        2 使用yield定义生成器函数

  e.g.1:

 1 #列表生成式
 2 l = [x*2 for x in range(10)] # 0~18的偶数列表,这里最外面是一个方括号
 3 print(l)#[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
 4 print(type(l))#<class 'list'>
 5 
 6 #生成器
 7 g = (x*2 for x in range(10))#0~18的偶数生成器,这里最外面是一个圆括号
 8 print(isinstance(g, Generator))#True
 9 print(hasattr(g, "__iter__"))#True
10 print(hasattr(g, "__next__"))#True

   列表生成式:如果需要生成的列表非常大的时候会非常消耗内存([x for x in range(999999999)]),

   生成器表达式:是一个表达式,每次调用的时候(next())返回一个值,节省内存开销

  e.g:

    

def gen():
    for i in range(4):
        yield i

for i in gen():
    print(i)# 0  1  2  3 

x = gen()
print(next(x))#0
print(next(x))#1
print(next(x))#2
print(next(x))#3
print(next(x))#StopIteration

这里yield的作用相当于return,

  (这里说的相当于是可以返回值:returen函数结束返回值,没有状态;但是yield返回值之后会记住上次执行的位置,然后下次执行的时候,

    从上次结束的位置开始执行,这也是为什么输出会是0,1,2,3。

    之后尝试再从迭代器中输出的时候是:StopItreation ;体会和return的相同和不通之处) 

  这个函数是顺序的返回[0,4]之间的自然数,可以通过next()或使用for循环来遍历,当程序遇到yield关键字时,这个生成器函数就返回了;

  再次执行 了next()函数,它就会从上次函数返回的执行点继续执行,

  即:yield退出时,保存了函数执行的位置,变量等信息,再次执行时,就从这个yield退出的地方继续往下执行。

  在python中利用生成器的这些特点可以实现协程。协程可以理解一个轻量级的线程,它相对于线程处理高并发场景有很多优势。

协程的例子:

def prodecer(c):
    n = 0
    while n < 5:
        n += 1
        print('producer{}'.format(n))
        r = c.send(n)
        print('consumer return {}'.format(r))

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('consumer{}'.format(n))
        r = 'ok'

if __name__ == '__main__':
    c = consumer()
    next(c)#启动consumer
    prodecer(c)

协程实现了cpu 在两个函数之间进行切换从而实现并发的效果。

 魔法方法:__getitem__()

语法: object.__getitem__(self, key)

  该方法用于返回参数key所对应的值 。

官方文档释义:Called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence type) is up to the __getitem__() method. If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if key is missing (not in the container), KeyError should be raised.

概括翻译一下:__getitem__() 方法用于返回参数 key 所对应的值,这个 key 可以是整型数值和切片对象,并且支持负数索引;如果 key 不是以上两种类型,就会抛 TypeError;如果索引越界,会抛 IndexError ;如果定义的是映射类型,当 key 参数不是其对象的键值时,则会抛 KeyError 。

把类中的属性定义为序列,可以使用__getitem__()函数输出序列属性中的某个元素,这个方法返回与指定键想关联的值。对序列来说,键应该是0~n-1的整数,其中n为序列的长度。对映射来说,键可以是任何类型。

e.g:(序列类型):

#序列型
class MyList:
    def __init__(self):
        self.data = []
    def apppend(self, item):
        self.data.append(item)
    def __getitem__(self, key):
        print("key is :" + str(key))
        return self.data[key]

l = MyList()
l.apppend("My")
l.apppend("name")
l.apppend("is")
l.apppend("python")


print(l[3])#key is :3 ;python
print(l[:2])#key is :slice(None, 2, None); ['My', 'name']
print(l['hi'])#key is :hi;  TypeError: list indices must be integers or 
# slices, not str

e.g(map,映射类型):

 1 class Tag:
 2     def __init__(self,id):
 3         self.id = id
 4     def __getitem__(self, item):
 5         print('这个方法被调用')
 6         return self.id
 7 
 8 a = Tag("This is id")
 9 print(a.id)
10 print(a['python']) #This is id /n 这个方法被调用
11 
12 print(a["any code"])#This is id /n 这个方法被调用
13 """
14 如果是映射类型key--value;键可以是任意类型的,
15 序列无论是多少,__getitem__()总是会被调用
16 但是这样是不规范的:任意的键都指向相同的一个值
17 """

改写:

class Tag:
    def __init__(self):
        self.change = {'This is key': "This is value"}

    def __getitem__(self, key):
        print('This method is called')
        return self.change[key]

a = Tag()
print(a['This is key'])#This method is called \n;This is value
print(a['python'])#This method is called \n; return self.change[key];KeyError: 'python'

改写之后是映射:key-value相对应。

引用:

https://blog.csdn.net/finalbing/article/details/98958094?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-1.control&spm=1001.2101.3001.4242

https://www.cnblogs.com/lovellll/p/10230016.html

https://foofish.net/iterators-vs-generators.html

https://nvie.com/posts/iterators-vs-generators/

 

  

原文地址:https://www.cnblogs.com/liveforlearn/p/15117801.html