返回顶部

浅析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等等的方法。迭代器可以当做一个强大的类的配件。

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

posted on 2021-12-15 20:12  weilanhanf  阅读(186)  评论(0编辑  收藏  举报

导航