Python経験-反復器とジェネレータ

9675 ワード

反復器
  • 反復器は、集合内の要素にアクセスする方法(不活性)であり、以下の標的のアクセス方法とは異なり、アクセスを返すことができない.
  • 反復プロトコル:__iter____next__、IterableとIteratorに対応;
  • 反復可能オブジェクトは__iter__を実装するだけで、反復器を構築するには__next__を実装する必要があります.
  • from collections.abc import Iterable, Iterator
    a = [1, 2]      # list Iterable   Iterator
    iter_rator = iter(a)
    print (isinstance(a, Iterable))
    print (isinstance(iter_rator, Iterator))
    

    IterableとIterator
  • がiterを呼び出すと、まず__iter__が実現されたかどうかを判断し、存在しない場合はデフォルトの反復器を作成し、__getitem__を利用して遍歴する.
  • __iter__は、Iteratorタイプを返さなければならない.
  • が反復値を返すのは、Iteratorタイプで__next__で実現され、またIterator内部でインデックス値を維持する必要がある(ロールバックアクセスは許可されない).
  • カスタムタイプを巡回するには、その内部に__iter__を定義し、戻る反復器オブジェクトを定義する必要があります(内部実装__next__).

  • 例:
    from collections.abc import Iterator
    
    class Company(object):
        def __init__(self, employee_list):
            self.employee = employee_list
            
        def __iter__(self):
            return MyIterator(self.employee)
    
        # def __getitem__(self, item):
        #     return self.employee[item]
    
    class MyIterator(Iterator):
        def __init__(self, employee_list):
            self.iter_list = employee_list
            self.index = 0
    
        def __next__(self):
            try:
                word = self.iter_list[self.index]
            except IndexError:
                raise StopIteration
            self.index += 1
            return word
    
    if __name__ == "__main__":
        company = Company(["tom", "bob", "jane"])
        my_itor = iter(company)
        
        # while True:
        #     try:
        #         print (next(my_itor))
        #     except StopIteration:
        #         pass
    
        # next(my_itor)
        for item in company:
            print (item)
    

    ビルダー
  • yieldキーワードが存在する限り、ジェネレータ関数であり、一般的な関数としてアクセスできない.
  • ジェネレータオブジェクトはpythonがバイトコードをコンパイルするときに生成される.
  • は、すべてのメモリをすぐに割り当てる必要がなく、アクセスするたびにyieldの値を返します.

  • 例:
    def fib(index):
        if index <= 2:
            return 1
        else:
            return fib(index-1) + fib(index-2)
    
    def fib2(index):
        re_list = []
        n, a, b = 0, 0, 1
        while n

    実装の詳細:
    # python       
    import inspect
    frame = None
    
    def foo():
        bar()
    
    def bar():
        global frame
        frame = inspect.currentframe()
    
    #
    """
        python.exe  PyEval_EvalFramEx(c  )  foo         (stack frame)
         foo     bar        (python     ,    ,          ),
                     ,               。
    """
    import dis
    print(dis.dis(foo))
    
    foo()
    print(frame.f_code.co_name)             # bar
    
    caller_frame = frame.f_back
    print(caller_frame.f_code.co_name)      # foo
    
    
    def gen_func():
        yield 1
        name = "ywh"
        yield 2
        age = 30
        return "aimei"
    
    import dis
    gen = gen_func()                #      PyGenObject  frame   (PyCodeObject + PyCodeObject)
    print (dis.dis(gen))
    
    print(gen.gi_frame.f_lasti)     #              ,     
    print(gen.gi_frame.f_locals)
    next(gen)
    print(gen.gi_frame.f_lasti)
    print(gen.gi_frame.f_locals)
    next(gen)
    print(gen.gi_frame.f_lasti)
    print(gen.gi_frame.f_locals)
    
    class company:
        def __getitem__(self, item):
            pass
    
    from collections import UserList        # Python        List(    `__next__`,yield)
    

    例:ジェネレータで大きなファイルを読み込む
    #       ,      (  f.read())
    def myreadlines(f, newline):
        buf = ""        #   ,         
        while True:
            while newline in buf:   #              ,             buf 
                pos = buf.index(newline)
                yield buf[:pos]     #            
                buf = buf[pos + len(newline):]
            chunk = f.read(4096)    #     4096   
    
            if not chunk:           #       
                yield buf
                break
            buf += chunk
    
    with open("input.txt") as f:
        for line in myreadlines(f, "{|}"):
            print(line)
    

    ジェネレータ値
    ジェネレータの起動:
  • next:ジェネレータから値を取得します(他の関数呼び出しのために値を外部に送信します).
  • send:ジェネレータにパラメータを転送したり、ジェネレータを再起動して次のyieldに実行したりして、yieldの値を返したりすることができます.
  • ジェネレータがNone以外の値を起動、送信する前に、事前に起動する必要があります.
  • gen.send(None)
  • next(gen)

  • には、close(次回yieldを実行すると異常が放出される)、throw(実行関数が異常を自発的に放出する)の方法もあります.
  • def gen_func():
        html = yield "http://projectsedu.com"       #   gen.send(html)    html
        print(html)         #  send    
        return "ywh"
    
    gen = gen_func()
    # url = gen.send(None)
    url = next(gen)
    html = "ywh"
    gen.send(html)
    
    gen.stop()                          #      (     yield)
    gen.throw(Exception, 'message')     #       
    

    yield from chain(連結処理反復可能オブジェクト)、yield fromおよびyield
    from itertools import chain
    
    li = [1, 2, 3]
    di = {
        "ywh1": "1", "ywh2": "2"
    }
    
    for value in chain(li, di, range(5, 10)):
        print(value)
        
    def my_chain(*args, **kwargs):
        for my_iterable in args:
            yield from my_iterable      #  iterable  ,  yield 
            # for value in my_iterable:
                # yield value
    

    yield fromとyieldの違い
    def g1(iterable):                   #    ,  yield range(10)
        yield iterable
    
    def g2(iterable):                   #  range(10)  ,  yield 
        yield from iterable
    
    for value in g1(range(10)):
        print(value)
        
    for value in g2(range(10)):
        print(value)
    

    yield fromは、呼び出し元とサブジェネレータの間に双方向チャネル(コパスで別のコパスを呼び出す)を確立することもできます.
    final_result = {}
    
    def sales_sum(pro_name):
        total = 0
        nums = []
        while True:
            x = yield
            print(pro_name+"  : ", x)
            if not x:
                break
            total += x
            nums.append(x)
        return total, nums
        
    
    #      (    )
    def middle(key):
        while True:
            final_result[key] = yield from sales_sum(key)
            print(key + "      !!")
    
    def main():
        data_sets = {
            "ywh    ": [1200, 1500, 3000],
            "ywh    ": [28, 55, 98, 108],
            "ywh    ": [280, 560, 778, 70],
        }
        for key, data_set in data_sets.items():
            print("start key:", key)
            m = middle(key)
            m.send(None)            #   middle  
            for value in data_set:
                m.send(value)       #           
            m.send(None)
        print("final_result:", final_result)
    
    
    if __name__ == '__main__':
        main()
    

    実装の原理
    # pep380
    
    # 1. RESULT = yield from EXPR         
    #     
    """
    _i:    ,         
    _y:        
    _r:yield from        
    _s:     send()    
    _e:    
    
    """
    
    _i = iter(EXPR)  # EXPR        ,_i       ;
    try:
        _y = next(_i)  #       ,          _y ;
    except StopIteration as _e:
        _r = _e.value  #      `StopIteration`  ,         `value`     _r,            ;
    else:
        while 1:  #         ,        ;
            _s = yield _y  #         ,     `send()` ,          _s ;
            try:
                _y = _i.send(_s)  #   _s,        ;
            except StopIteration as _e:
                _r = _e.value  #           ,          `value`    _r,    ,          ;
                break
    RESULT = _r  # _r    yield from       。
    
    """
    1.              ,             ,      .throw() .close()  ;
    2.         .throw() .close()  ,         ,           ;
    3.               
    4.       next()  .send(None) ,          next()  ,      .send()    None   ,        .send()  ;
    """
    _i = iter(EXPR)
    try:
        _y = next(_i)
    except StopIteration as _e:
        _r = _e.value
    else:
        while 1:
            try:
                _s = yield _y
            except GeneratorExit as _e:
                try:
                    _m = _i.close
                except AttributeError:
                    pass
                else:
                    _m()
                raise _e
            except BaseException as _e:
                _x = sys.exc_info()
                try:
                    _m = _i.throw
                except AttributeError:
                    raise _e
                else:
                    try:
                        _y = _m(*_x)
                    except StopIteration as _e:
                        _r = _e.value
                        break
            else:
                try:
                    if _s is None:
                        _y = next(_i)
                    else:
                        _y = _i.send(_s)
                except StopIteration as _e:
                    _r = _e.value
                    break
    RESULT = _r
    

    まとめ:
  • サブジェネレータが生産した値は、呼び出し元に直接伝達される.呼び出し元がsend()を介して送信した値は、サブジェネレータに直接伝達される.送信がNoneである場合、サブジェネレータの__next__()メソッドが呼び出され、Noneでない場合、サブジェネレータのsend()メソッドが呼び出される.
  • サブジェネレータが終了すると、最後のreturn EXPRは、StopIteration(EXPR)の異常をトリガーします.
  • yield from式の値は、サブジェネレータが終了したときにStopIterationに渡される異常の最初のパラメータである.
  • 呼び出し時にStopIterationの異常が発生した場合、依頼ジェネレータは運転を再開し、他の異常は「泡が立つ」.
  • は依頼生成器の異常に伝達され、GeneratorExitを除いて、他のすべての異常がサブ生成器のthrow()方法に伝達される.throw()が呼び出されたときにStopIterationの異常が発生した場合、依頼ジェネレータの運転を再開し、他の異常はすべて上向きに「泡が立つ」.
  • は、委任ジェネレータで呼び出すとする.close()または受信GeneratorExit異常は、サブジェネレータのclose()メソッドを呼び出し、なければ呼び出さない.close()が呼び出されたときに異常が放出されると、上向きに「泡が立つ」ようになり、そうでなければ依頼ジェネレータはGeneratorExit異常を放出する.