• python(七):迭代器与生成器


    一、Python的迭代协议

      迭代器是访问集合内元素的一种方式。它只能依次访问集合内元素。其特点是惰性执行。

      collection.abc的迭代协议提供了两个概念:可迭代对象和迭代器。可迭代对象:必须具有__item__特殊方法;迭代器:必须具有__next__方法。

    class Iterable(metaclass=ABCMeta):
        __slots__ = ()
        @abstractmethod
        def __iter__(self):
            while False:
                yield None
        @classmethod
        def __subclasshook__(cls, C):
            if cls is Iterable:
                return _check_methods(C, "__iter__")
            return NotImplemented
    
    class Iterator(Iterable):
        __slots__ = ()
        @abstractmethod
        def __next__(self):
            'Return the next item from the iterator. When exhausted, raise StopIteration'
            raise StopIteration
        def __iter__(self):
            return self  # 重载了Iterable的__iter__
        @classmethod
        def __subclasshook__(cls, C):
            if cls is Iterator:
                return _check_methods(C, '__iter__', '__next__')
            return NotImplemented
    
    Iterator.register(bytes_iterator)
    Iterator.register(bytearray_iterator)
    #Iterator.register(callable_iterator)
    Iterator.register(dict_keyiterator)
    Iterator.register(dict_valueiterator)
    Iterator.register(dict_itemiterator)
    Iterator.register(list_iterator)
    Iterator.register(list_reverseiterator)
    Iterator.register(range_iterator)
    Iterator.register(longrange_iterator)
    Iterator.register(set_iterator)
    Iterator.register(str_iterator)
    Iterator.register(tuple_iterator)
    Iterator.register(zip_iterator)

      从上面可以看到,字节、字典、列表、集合、元组、字符串和range、zip都被注册到了迭代器元类中,成为可迭代对象。实际上,open文件句柄也是一个迭代器。

    二、可迭代对象与迭代器

      1.可迭代对象

      对象含有__iter__方法,就是可迭代对象。不含__iter__方法,也可能是可迭代对象。__getitem__方法会自动创建__iter__来满足遍历条件。

    class Person:
        def __init__(self, persons):
            self.persons = persons
    #     def __iter__(self):   
    #         return 1
        def __getitem__(self, item):  # 假如__iter__没有设定,解释器会继续查找__getitem__
            return self.persons[item]  # __getitem__会自动创建__iter___来进行迭代
    
    per = Person(["Bob", "Tom", "Li", "Jan"])
    print(per[:3])
    for p in per:
        print(p)
    
    """
    ['Bob', 'Tom', 'Li']
    Bob
    Tom
    Li
    Jan
    """

      2.迭代器

      iter()用来将可迭代对象生成迭代器。

    class Person:
        def __init__(self, persons):
            self.persons = persons
        def __getitem__(self, item):
            return self.persons[item]
            return len(self.persons)
    
    per = Person(["Bob", "Tom", "Li", "Jan"])
    my = iter(per)
    for i in range(len(per)):
        print(next(my))
    
    """
    Bob
    Tom
    Li
    Jan
    """

      3.迭代器的实现

      当需要定义可迭代对象时,一般单独写一个迭代器功能。不建议在可迭代对象内写__next__

    class MyIterator:
        def __init__(self, persons):
            self.persons = persons
            self.__index = 0
        def __iter__(self):
            return self
        def __next__(self):
            try:
                per = self.persons[self.__index]
            except IndexError:
                raise StopIteration
            self.__index += 1
            return per
    
    class Person:
        def __init__(self, persons):
            self.persons = persons
        def __len__(self):
            return len(self.persons)
        
        def __iter__(self):
            return MyIterator(self.persons)  # 当要定义可迭代对象时,通常不在类内写__next__,而是写在外面
                
    
    per = Person(["Bob", "Tom", "Li", "Jan"])
    my = iter(per)
    for i in range(len(per)):
        print(next(my))

    三、生成器

      当函数中存在yield时,它就不再是普通的函数了,而是一个生成器函数。

    def f1():
        print("-----------step1----------")
        yield 1
        print("-----------step2----------")
        yield 2
    
    g = f1()  # 这一步没执行
    print(next(g))
    print(next(g))

      生成器的实现,本质上是对函数的堆存储方式进行了一层封装。

      普通函数的调用和执行过程。被调用函数独在字节码这一步单独存储于PyCodeObject中。它必须一次性运行完,而不会保留执行时的栈帧。

        

    import inspect
    frame = None
    def foo():
        bar()
    def bar():
        global frame
        frame = inspect.currentframe()
        
    foo()
    print(dis.dis(foo))
    print(frame.f_code.co_name)
    print(frame.f_back.f_code.co_name, frame.f_back)  # 所有的栈帧都是分配在对内存当中,独立于调用者存在
    PyCodeObject

       生成器对象(PyGenObject)会被编译成相应的字节码,以及每一次yield的栈帧和本地变量。相比于普通函数,PyGenObject多了对f_lasti和f_locals的保存。只要每次yield拿到f_lasti和gen_fn's bytecode,就可以暂停或继续执行生成器。

        

    import dis
    def f1():
        print("-----------step1----------")
        name = "Li"
        yield 1
        print("-----------step2----------")
        age = 24
        yield 2
    
    g = f1()  # 这一步没执行
    print(g.gi_code)  # 显示代码的起止位置
    print(dis.dis(g))  # 显示栈帧与字节码
    print(g.gi_frame.f_lasti, g.gi_frame.f_locals)  # 显示代码执行至此的栈帧和本地变量集
    print(next(g))
    print(g.gi_frame.f_lasti, g.gi_frame.f_locals)
    print(next(g))
    print(g.gi_frame.f_lasti, g.gi_frame.f_locals)
    
    """
    <code object f1 at 0x110b71f60, file "<ipython-input-261-13e8f3cdc8da>", line 2>
      3           0 LOAD_GLOBAL              0 (print)
                  2 LOAD_CONST               1 ('-----------step1----------')
                  4 CALL_FUNCTION            1
                  6 POP_TOP
    
      4           8 LOAD_CONST               2 ('Li')
                 10 STORE_FAST               0 (name)
    
      5          12 LOAD_CONST               3 (1)
                 14 YIELD_VALUE
                 16 POP_TOP
    
      6          18 LOAD_GLOBAL              0 (print)
                 20 LOAD_CONST               4 ('-----------step2----------')
                 22 CALL_FUNCTION            1
                 24 POP_TOP
    
      7          26 LOAD_CONST               5 (24)
                 28 STORE_FAST               1 (age)
    
      8          30 LOAD_CONST               6 (2)
                 32 YIELD_VALUE
                 34 POP_TOP
                 36 LOAD_CONST               0 (None)
                 38 RETURN_VALUE
    None
    -1 {}
    -----------step1----------
    1
    None
    14 {'name': 'Li'}
    -----------step2----------
    2
    32 {'name': 'Li', 'age': 24}
    """
    PyGenObject

       待续。。。。。。

  • 相关阅读:
    Sql日期时间格式转换
    c#被指定为此窗体的 MdiParent 的窗体不是 MdiContainer?
    kmeans聚类分析
    C# VS2005打开没问题,但是运行解决方案时就整个自动关闭了
    PowerDesigner教程系列(一)概念数据模型
    PowerDesigner概念设计模型(CDM)中的3种实体关系
    spss clementine Twostep Cluster(两步聚类 二阶聚类)
    PowerDesigner教程系列(三)概念数据模型
    Kmeans聚类算法
    c# 中窗体居中代码怎么写?
  • 原文地址:https://www.cnblogs.com/kuaizifeng/p/9084542.html
Copyright © 2020-2023  润新知