韩非囚秦
——独善其身者,难成大事也。

导航

 

一、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

   待续。。。。。。

posted on 2018-08-16 14:09  一只火眼金睛的男猴  阅读(230)  评论(0编辑  收藏  举报