zoukankan      html  css  js  c++  java
  • 浅析Python迭代器、可迭代对象以及生成器

    一、迭代器

    1、迭代器定义

    1. 当类中定义了__iter____next__ 两个方法。
    2. __iter__ 方法需要返回对象本身, 即: self
    3. __next__ 方法,返回下个数据,如果没有数据了,则需要抛出一个StopIteration的异常。

    鸭子类型:语言层面约定一个对象当满足一些鸭子的特点的时候,比如嘎嘎叫,会游泳,就可以把这个对象当做鸭子。
    所以当一个对象满足如上迭代器限制条件的时候,就可以将其当做迭代器对象。
    首先创建迭代器类型

    class IT(object):
        def __init__(self):
            self.counter = 0
            
        def __iter__(self):
            return self
        
        def __next__(self):
            self.counter += 1
            if self.counter == 3:
                raise StopIteration()
            return self.counter
    

    可以将迭代理解为逐步获取数据。当无法获取数据就会抛出异常。

    2、获取数据

    根据迭代器类型实例化可得到迭代器对象,调用魔法方法__next__逐步获取数据。如下

    obj = IT()
    v1 = obj.__next__()
    v2 = obj.__next__()
    v3 = obj.__next__()  # raise StopIteration
    

    但是通常不希望类型内部的一些定义暴露给外部,也就是最好不要自己定义或者直接调用双下划线开头的魔法方法,关于命名规则请看一.3:Python命名下划线,可以调用next内置函数,迭代获取数据。如下

    obj = IT()
    v1 = next(obj)
    v2 = next(obj)
    v3 = next(obj)  # raise StopIteration
    

    只不过使用next内置函数,不仅要自己一步一步取调用,还需要自己处理因无法数据而抛出的异常。所以可以直接使用for循环处理迭代器对象,如下

    obj = IT()  # obj是一个迭代器对象
    for item in obj:
        print(item)
    

    for循环内部在循环时,先执行_iter_方法, 获取一个迭代器对象,也就是自身,然后不断执行的next取值(直到抛出异常stopIteration终止循环)

    3、Python下划线命名

    额外记录一下,单下划线和双下划线在Python变量名和方法名中都有各自的含义。但是更多是作为约定,或者说建议,而非强制性。
    最多的四种类型:

    • 前置单下划线:_var:前置单下划线开头的通常只是约定变量方法是内部私有,不建议外部使用,但是也未禁止。约定下划线开头的函数是模块私有,其他模块无法使用通配符导入调用。
    • 前置双下划线:__var:前置双下划线会触发解释器的变量名称改写机制,如果是类变量会变成_类__var的形式,避免父类子类继承时候,子类变量对父类变量的覆盖。
    • 后置单下划线:var_:后置单下划线可以用来解决一下关键字命名冲突,比如classclass_
    • 前后双下划线:__var__:前后双下划线魔法属性魔法方法了,属于语法规定,自定义要避免,也要避免调用。
    • 单下划线:_:用作一些无关变量,比如列表生成式中[_ for _ in range(10)]_做匿名变量,或者拆包中*_, a, b = [1, 2, 3, 4, 5]*_来接受1,2,3三个不需要的值。

    具体Python下划线变量见:传送门

    二、生成器

    1、生成器简介

    生成器编写方式和表现方式与迭代器不同,但是生成器可以看作是一种特殊的迭代器,因为内部也声明了__iter____next__方法

    # 创建生成器函数
    def fun():
        yield 1
        yield 2
        
    # 创建生成器对象(内部是根据生成器类generator创建的对象,生成器内部也声明了__iter__、__next__方法)
    obj = func()
    v1 = next(obj)
    v2 = next(obj)
    v2 = next(obj)  # raise异常
    

    yield关键字的函数可以看做是生成器对象,所以刚开始obj = func()的时候,并不会直接执行函数,而是调用next的时候才会逐步执行生成数据。

    2、生成器示例

    示例:使用生成器自定义range

    class Xrange:
      def __init__(self, max_num):
        self.max_num = max_num
      def __iter__(self):
        counter = 0
        while counter < self.max_num:
          yield counter
          counter += 1
    
    obj = Xrange(100)
    for item in obj:
      print(item)
    

    3、生成器面试题

    >>> L = [x*x for x in range(10)]
    >>> L
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    >>> g = (x*x for x in range(10))
    >>> g
    <generator object <genexpr> at 内存地址>
    

    讲上述列表生成式中的[]改成()之后,L从列表变成了生成器。
    列表生成式可以创建一个列表。但是,受到内存限制,列表容量也是受限。创建一个包含百万元素的列表,不仅是占用很大的内存空间,如:我们只需要访问后边的几个元素,使用列表生成式就需要创建一个巨大的列表空间保存大部分元素,太浪费。因此,没有必要创建完整的列表(节省大量内存空间)。在Python中,我们可以采用生成器:边循环,边计算的机制—>generator。生成器不会在内存中存储所有的数据,逐步生成。

    三、可迭代对象

    1、可迭代对象定义

    定义:如果一个类中实现了__iter__方法,并且返回的是一个迭代器对象(生成器对象,因为生成器是特殊的迭代器),可以称这个类实例化的对象为可迭代对象。
    伪码示例如下:

    class Foo():
        def __iter__(self):
            return 迭代器对象
            
     obj = Foo()  
     for item in obj:
        pass
    

    以上,obj为可迭代对象。可迭代对象是可以用for来循环,先调用__iter__方法获取迭代器对象,然后调用迭代器对象的__next__()方法逐步迭代数据。

    2、可迭代对象实现

    迭代器与可迭代对象实例如下

    # 迭代器对象类型 实例化成迭代器对象
    class IT(object):
        def __init__(self):
            self.counter = 0
            
        def __iter__(self):
            return self
        
        def __next__(self):
            self.counter += 1
            if self.counter == 3:
                raise StopIteration()
            return self.counter
            
     
     # 可迭代对象类型,实例化成可迭代对象
     class Foo():
        def __iter__(self):
            return IT()  # 返回一个迭代器对象
    
    obj = Foo()
    # 循环可迭代对象,先从obj.__iter__获取迭代器对象
    for item in obj:
        print(item)
    

    range实例:range其实也是一个可迭代类型,实例化之后获取可迭代对象。通过dir自省查看魔法方法

    r = range(100)  # 可迭代对象
    dir(r) #[... '__iter__'...]
    # 只有iter,没有next说明r是可迭代对象,不是迭代器对象
    
    v = r.__iter__()  # r调用iter之后获取v为迭代器对象
    dir(v) #[... '__iter__'..., '__next__']  # 有iter和next说明v是迭代器对象
    

    四、总结

    1、判断类型

    常见的一些容器类型比如列表。 属于可迭代对象,有 __iter____next__方法。
    可以通过collections.abc模块提供的Iterator,Iterable判断对象为可迭代对象或者迭代器。

    from collections.abc import Iterator, Iterable
    
    # Iterator判断是否是迭代器对象
    # Iterable判断是否是可迭代对象
    
    
    m = [1, 2, 3]
    n = m.__iter__()
    
    isinstance(m, Iterator) # False
    isinstance(n, Iterator) # True
    
    isinstance(m, Iterable) # True
    isinstance(n, Iterable) # False
    

    2、三种类型小结

    • 可迭代对象:含__iter__方法,且该方法返回一个迭代器。
    • 迭代器对象:含__iter__方法和__next__方法,其中__iter__方法返回对象自身(self),__next__方法返回迭代的数据,没有数据时需要抛出 StopIteration 异常,以终止迭代。
    • 生成器:在函数内使用 yield 关键字,每次调用函数类似于对迭代器执行 next() 方法,生成器实际是一种特殊的迭代器。

    3、迭代器与可迭代对象?

    有了迭代器为什么还要可迭代对象?都是要逐步获取数据,两者重复吗?
    因为可迭代对象可以通过增加类方法实现更多的功能,比如list,它是个可迭代对象,但是它的功能远远超出迭代器,还有一些比如append,clear,copy等等的方法。迭代器可以当做一个强大的类的配件。

    :以上仅为学习记录总结笔记,如有错误或者相同,轻喷,谢谢谢谢。

  • 相关阅读:
    hasCode in Java
    如何区分同一Class的不同实例对象
    如何构建XML文件
    Spring <context:property-placeholder/>的作用
    关于List的几个方法
    Java 中那些不常用的关键字
    设计模式
    Java源代码阅读-Object.toString()
    修复启动项
    centos关闭防火前
  • 原文地址:https://www.cnblogs.com/welan/p/15694886.html
Copyright © 2011-2022 走看看