zoukankan      html  css  js  c++  java
  • guxh的python笔记四:迭代

    1,可迭代对象iterable,迭代器iterator,生成器generator

    可迭代对象iterable:

    • 实现__iter__方法的类。__iter__方法返回iterator或者generator。
    • 实现__getitem__方法的类。其参数是从0开始的索引。

    迭代器Iterator:

    • 实现__iter__方法和__next__方法的类(即自遍历)。其中__iter__方法返回iterator自身,__next__方法不断返回元素直到没有元素后抛出StopIteration异常。

    生成器generator:

    • 一个含有yield句法的函数。generator支持next(),属于iterator。

    上述三种类型都可作用于for循环。

    备注:

    1)list、dict、str虽然是iterable,却不是iterator,为什么呢?因为iterator表示的是一个数据流,可以被next()调用不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把数据流看作一个序列,但我们无法提前知道序列长度,只有不断通过next()进行下一个计算。iterator甚至可以表示无限大的数据流,list不可能无限大。

    2)iterable没有__next__(自遍历)。

    3)iterator没有__getitem__不支持[]分量取值和切片,没有__len__不支持获取长度。

    4)可获得iterator的内置方法:zip,enumerate,map,filter,reversed。

    5)可获得iterable的内置方法:range。

    6)iterator(包括generator)只能被消费一次,第二次调用时会直接返回空。

    2,可迭代对象iterable与迭代器iterator的关系

    关于可迭代对象iterable与迭代器iterator的实现细节可参考“3实现可迭代的方法”中的经典版方法。

    2.1,iterable

    s = 'abc'   # s是个iterable,<class 'str'>

    s是iterable,可以被迭代:

    for i in s:
       print(i)

    2.2,iterable可迭代的本质

    迭代的本质是从iterable获取iterator(iterable的__iter__方法return了一个iterator),然后再不断使用iterator的next()方法获取值,直到StopIteration异常结束:

    sit = iter(s) # it是个iterator,<class 'str_iterator'>
    while True:
      try:
         print(next(sit))
      except StopIteration:
        break 

    next()可以接收默认值,当运行到StopIteration时就返回该默认值,上述代码页可以改写成这样:

    sit = iter(s) # it是个iterator,<class 'str_iterator'>
    i = next(sit) 
    while i:
        print(i)
        i = next(sit, None)

    手工next()的话是这样的过程:

    >>> s = 'abc'
    >>> sit = iter(s)
    >>> next(sit)
    'a'
    >>> next(sit)
    'b'
    >>> next(sit)
    'c'
    >>> next(sit)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

    另外,如果一个obj没有实现__iter__方法,即无法通过iter(obj)获取到迭代器,但是它实现了__getitem__,那它也是可以被迭代的。

    2.3,iterable和iterator判断方法

    s可迭代却不能被next自遍历。sit可以迭代也可以被next自遍历。

    可以用抽象基类判断iterable,iterator:

    from collections import abc
    print(isinstance(s, abc.Iterable))   # True
    print(isinstance(s, abc.Iterator))   # False
    print(isinstance(sit, abc.Iterable)) # True
    print(isinstance(sit, abc.Iterator)) # True  

    或者用迭代的协议去判断:

    print(hasattr(s, '__iter__'))    # True
    print(hasattr(s, '__next__'))    # False
    print(hasattr(sit, '__iter__'))  # True
    print(hasattr(sit, '__next__'))  # True
    

      

    3,实现对象可迭代的方法

    实现一个序列类型,接受输入值x,返回从x到11的值。 

    3.1,方法一:python序列鸭子类型

    python在尝试迭代对象时,找不到__iter__就会去调用__getitem__,__getitem__实现从0开始的索引取值即可

    class Foo:
        def __init__(self, data):
            self.data = data
    
        def __getitem__(self, i):
            return range(self.data, 11)[i]
    

     

    3.2,方法二:经典版

    iterable+iterator,构建Foo的iterator,缺点是代码量大。

    关键点:iterable的__iter__返回iterator;iterator的__iter__返回self,__next__逐个取值。

    class Foo:
        def __init__(self, data):
            self.data = list(data)
    
        def __iter__(self):    # iterable中的__iter__返回iterator
            return Foo_iterator(self.data)
    
    class Foo_iterator:
        def __init__(self, data):
            self.data = data
    
        def __iter__(self):    # iterator中的__iter__返回自己
            return self
    
        def __next__(self):    # iterator实现__next__
            if self.data > 10:
                raise StopIteration
            else:
                num = self.data
            self.data += 1
            return num

    自己实现的iterable/iterator:

    f = Foo(1)
    fit = iter(f)
    print(type(f))    # <class '__main__.Foo'>
    print(type(fit))  # <class '__main__.Foo_iterator'>

    对比下内置的iterable/iterator:

    s = 'abc'
    sit = iter(s)
    print(type(s))     # <class 'str'>
    print(type(sit))   # <class 'str_iterator'>
    
    r = range(10)
    rit = iter(r)
    print(type(r))     # <class 'range'>
    print(type(rit))   # <class 'range_iterator'>
    

      

    3.3,方法三:糟糕版

    Foo自己实现__next__和__iter__,让Foo既是iterable,也是自己的iterator,混淆了iterable和iterator,糟糕不推荐

    class Foo:
        def __init__(self, data):
            self.data = data
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.data > 10:
                raise StopIteration
            else:
                num = self.data
            self.data += 1
            return num
    

      

    3.4,方法四:generator版

    用generator实现iterable中的__iter__方法

    class Foo:
        def __init__(self, data):
            self.data = data
    
        def __iter__(self):
            for i in range(self.data, 11):
                yield i
    

    iter()返回的类名是generator,而不是iterator:

    f = Foo(1)
    fit = iter(f)
    print(type(f))     #  <class '__main__.Foo'>
    print(type(fit))   #  <class 'generator'>

    备注:Foo中的__iter__获取数据时,用的是惰性获取range(非惰性就是list(range(self.data, 11)))。一般推荐用惰性函数实现,例如用finditer替代findall。

    3.5,方法五:生成器表达式

    class Foo:
        def __init__(self, data):
            self.data = data
    
        def __iter__(self):
            return (i for i in range(self.data, 11))
    

    3.6,方法六:委托

    将iter方法委托其他iterable背后的iterator,range()是个iterable,可以通过iter(range())获取到它的iterator。

    下例将Foo的迭代器实现委托给了range()的迭代器:

    class Foo:
        def __init__(self, data):
            self.data = data
    
        def __iter__(self):
            return iter(range(self.data, 11))

    iter()返回的是range的iterator:

    f = Foo(3)
    fit = iter(f)
    print(isinstance(fit, abc.Iterator))   # True
    print(type(fit))    # <class 'range_iterator'>
    

     

    4,生成器函数

    4.1,生成器函数

    如果函数返回的是列表,可以用generator改写,优点是代码简介,节省内存。

    函数中只要出现了yield就会转变为一个generator,其核心特性是只会在响应迭代过程中的next操作时才会运行,和iterator一致。

    generator属于iterator,3.4中就直接返回了generator作为iterator。

    并发的协程还会继续深入讨论generator的yield语法。

    这里写看看适合单独编写一个generator的场景。

    例如输入一个序列,返回其中的偶数列表:

    def fun(components):
        result = []
        for c in components:
            if divmod(c, 2)[1] == 0:
                result.append(c)
        return result
    

     生成器改写:

    def fun(components):
        for c in components:
            if divmod(c, 2)[1] == 0:
                yield c
    

    调用生成器:

    f = fun(range(20))
    print(f)  # <generator object fun at 0x0000022C2F1AB258>
    print(list(f))  # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
    print(isinstance(f, abc.Iterator))   # f是个iterator,因为generator属于iterator
    

    4.2,生成器应用 - 生成器实现管道

    脚本所在目录下有foo和bar两个文件夹,分别有多个防火墙日志文件,需要取出里面的access-list配置条目,可以:

    def gen_find(filepat, top):
        # filepat: 文件名匹配模式;top:os.walk遍历的top目录。返回所有符合条件的文件名路径。
        for path, dirlist, filelist in os.walk(top):
            for name in [file for file in filelist if re.search(filepat, file)]:
                yield os.path.join(path, name)
    
    def gen_opener(files):
        # 返回所有文件生成器。
        for file in files:
            with open(file, 'rt') as f:
                yield f     # f是个生成器,只能for循环被消费一次
    
    def gen_concatenate(iterators):
        for it in iterators:
            yield from it
    
    def gen_grep(pattern, lines):
        for line in lines:
            if re.search(pattern, line):
                yield line
    
    lognames = gen_find('防火墙.*log', os.getcwd())
    files = gen_opener(lognames)
    lines = gen_concatenate(files)
    acclines = gen_grep('^access-list', lines)
    for each_acc in acclines:
        print(each_acc)
    

    4.3,yield from扁平化处理嵌套序列

    def flatten(items):
        for x in items:
            if isinstance(x, Iterable):
                yield from flatten(x)   # 递归
            else:
                yield x
    >>> list(flatten([1, (3, 4, 5, {6, 7})]))
    [1, 3, 4, 5, 6, 7]
    

    5,其他

    5.1,iterator(包括generator)只能被迭代一次

    iterable可以被多次迭代:

    >>> l = range(5)
    >>> list(l)
    [0, 1, 2, 3, 4]
    >>> list(l)
    [0, 1, 2, 3, 4]

    iterator只能被迭代一次:

    >>> lit = iter(range(5))   # lit是iterator
    >>> list(lit)
    [0, 1, 2, 3, 4]
    >>> list(lit)
    []

     generator也是只能迭代一次:

    >>> ge = (i for i in range(5))
    >>> list(ge)
    [0, 1, 2, 3, 4]
    >>> list(ge)
    []

      

    5.2,iter(it,x)函数的哨符值

    有第二个参数时,it必须callable:

    >>> iter(range(10))
    <range_iterator object at 0x0000026F8968AEF0>
    >>> iter(range(10), 3)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: iter(v, w): v must be callable

    作改动:

    l = iter(range(10))
    def run():
        return next(l)
    lit = iter(run, 3)  # 遇到3时终止
    print(list(lit))  # [0, 1, 2]
    

    5.3,生成器表达式高效处理文本

    下面代码处理文本时,会惰性处理,不会事先读取文本至内存:

    with open(filename) as f:
        lines = (line.strip() for line in f)
        for line in lines:
            ......
    
  • 相关阅读:
    【北邮人论坛帖子备份】【心得】20年公考经验分享
    如何写一封国际会议的交流信?
    花呗广告趣图
    《第九个寡妇》读后感
    沟通的五个层次
    部署多功能模块依赖项目中解决的问题
    maven: can't resolve plugin xxxmaven-xxxx-plugin:x.x
    C++编译报错:need 'typename' before 'std::map<T, S>::iterator' because 'std::map<T, S>' is a dependent scope
    详细js中(function(window,document,undefined))的作用
    201509020-js
  • 原文地址:https://www.cnblogs.com/guxh/p/10235220.html
Copyright © 2011-2022 走看看