欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python:可迭代对象,迭代器,生成器

程序员文章站 2024-01-15 12:34:04
...

提纲:生成器 \subsetneqq 迭代器 \subsetneqq 可迭代对象

1. 可迭代对象:

可迭代对象包括:

  1. 迭代器 (包括生成器)。
  2. 字符串 str,列表 list, 字典 dict,元组 tuple,集合 set。
  3. 实现了 __iter__ 方法的对象。

判断一个变量是否为可迭代对象:

from collections import Iterable
isinstance(变量, Iterable)

测试代码如下:

from collections import Iterable

l1 = [1, 2, 3]
t1 = (1, 2, 3)
d1 = {'key1': 'value1'}
str1 = 'James'
set1 = {'a', 'b', 'c'}


class MyObject:
    def __init__(self):
        pass


class MyIter:
    def __init__(self):
        pass

    def __iter__(self):
        yield 1


a = MyObject()
b = MyIter()

print(isinstance(l1, Iterable))
print(isinstance(t1, Iterable))
print(isinstance(d1, Iterable))
print(isinstance(str1, Iterable))
print(isinstance(set1, Iterable))
print(isinstance(a, Iterable))
print(isinstance(b, Iterable))

运行结果如下:
Python:可迭代对象,迭代器,生成器
特别注意变量 a 和 b,a 是 MyObject 的一个实例,但是由于没有实现 __iter__ 方法,所以不是一个可迭代对象,而 b 是 MyIter 的一个实例并且实现了 __iter__ 方法,所以就是一个可迭代对象。

2. 迭代器

迭代器可以通过 next() 方法不断重复获取下一个值,直到所有元素全部输出完之后,返回 StopIteration才停止。同时实现在 __iter____next__ 两个函数的对象,就是迭代器。其中 __iter__ 方法需要返回一个迭代器, 而 __next__ 方法返回下一个返回值或者 StopIteration

判断是否为迭代器:

from collections import Iterator
isinstance(变量, Iterator)

测试代码如下:

from collections import Iterable
from collections import Iterator

l1 = [1, 2, 3]
t1 = (1, 2, 3)
d1 = {'key1': 'value1'}
str1 = 'James'
set1 = {'a', 'b', 'c'}


class MyObject:
    def __init__(self):
        pass


class MyIterable:
    def __init__(self):
        pass

    def __iter__(self):
        yield 1


class MyIterator:
    def __init__(self):
        pass

    def __iter__(self):
        yield 1

    def __next__(self):
        return self


a = MyObject()
b = MyIterable()
c = MyIterator()

print(isinstance(l1, Iterable))
print(isinstance(t1, Iterable))
print(isinstance(d1, Iterable))
print(isinstance(str1, Iterable))
print(isinstance(set1, Iterable))
print(isinstance(a, Iterable))
print(isinstance(b, Iterable))
print(isinstance(c, Iterable))

print('********************')

print(isinstance(l1, Iterator))
print(isinstance(t1, Iterator))
print(isinstance(d1, Iterator))
print(isinstance(str1, Iterator))
print(isinstance(set1, Iterator))
print(isinstance(a, Iterator))
print(isinstance(b, Iterator))
print(isinstance(c, Iterator))

结果如下:
Python:可迭代对象,迭代器,生成器
主要关注第二部分,发现字符串 str,列表 list, 字典 dict,元组 tuple,集合 set,没有实现 __iter__ MyObject 和仅实现 __iter__ 的 MyIterable 都不是迭代器,只有实现了 __iter____next__ 的 MyIterator 才是迭代器。

对于可迭代对象可以使用 iter() 转为迭代器:

l2 = iter(l1)
t2 = iter(t1)
d2 = iter(d1)
str2 = iter(str1)
set2 = iter(set1)
b2 = iter(b)

print('*************************')

print(isinstance(l2, Iterator))
print(isinstance(t2, Iterator))
print(isinstance(d2, Iterator))
print(isinstance(str2, Iterator))
print(isinstance(set2, Iterator))
print(isinstance(b2, Iterator))

结果如下:
Python:可迭代对象,迭代器,生成器
迭代器的优点:

  1. 提供了一种不依赖于索引的取值方式
  2. 惰性计算,节省内存

这里的节省内存是指 迭代器在迭代过程中,不会一次性把可迭代对象的所有元素都加载到内存中,仅仅是在迭代至某个元素时才加载该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点就使得迭代器适合用于遍历一些巨大的 或是 无限的集合。
迭代器的缺点:

  1. 取值不如按照索引取值来的方便(索引可以直接定位某一个值,迭代器不行,只能一个一个地取下去)
  2. 迭代器只能往后迭代,不能回退(执行 __next__ 方法只能向后,不能向前)
  3. 无法获取迭代器的长度

3. 生成器

生成器是一种特殊的迭代器,不过实现更加简单。实现的方式有两种:

  1. 生成器表达式
  2. 带有 yield 关键字的函数

生成器作为一种特殊的迭代器,相比于一般迭代器更加优雅。它不需要再像上面的类一样写 __iter____next__ 方法了,只需要一个 yield 关键字或生成器表达式。

def fib(n):
    i, a, b = 0, 0, 1
    while i < n:
        yield b
        a, b = b, a + b
        i = i + 1


g1 = (x * x for x in range(1, 11))
g2 = fib(6)

print('********************')

print(isinstance(g1, Iterator))
print(isinstance(g2, Iterator))
for i in range(10):
    print(next(g1))
for i in range(6):
    print(next(g2))

结果如下:
Python:可迭代对象,迭代器,生成器
Python的yield关键字的作用,就是把一个普通的函数变成生成器。当一个函数内出现yield关键字后,就会变异为生成器,其行为与普通函数不同。
从动态的角度,生成器在运行过程中:

  1. 当生成器函数被调用的时候,生成器函数不执行内部的任何代码,直接立即返回一个迭代器。
  2. 当所返回的迭代器第一次调用 next 的时候,生成器函数从头开始执行,如果遇到了执行 yield x,next立即返回 yield 的值 x。
  3. 当所返回的迭代器继续调用next的时候,生成器函数从上次yield语句的下一句开始执行,直到遇到下一次执行yield。
  4. 任何时候遇到函数结尾,或者 return 语句,抛出 StopIteration 异常。

这里关注一下第三点:

def g():
    print('L1')
    yield 1
    print('L2')
    yield 2
    print('L3')
    yield 3
    print('L4')


it = iter(g())
print('............')
v = next(it)
print(v)
v = next(it)
print(v)
v = next(it)
print(v)
v = next(it)
print(v)

结果如下:
Python:可迭代对象,迭代器,生成器
可见 it = iter(g()) 语句之后生成器函数不执行内部的任何代码,直接立即返回一个迭代器直到第一次执行 next ,就从头开始执行到 yield 语句,然后状态挂起,等下一次执行 next 时,从上次的 yield 语句处继续执行。

4. 惰性计算

我查找的资料上面对于生成器的惰性计算都是认可的,但有的认为迭代器不是惰性计算,有的认为是惰性计算。实践出真理,我动手试验了一下

import os
import psutil
import gc


class MyIterator2:
    def __init__(self, n):
        self.i = -1
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        while (self.i + 1) < self.n:
            self.i = self.i + 1
            return self.i


class MyIterator3:
    def __init__(self, n):
        self.i = -1
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        while (self.i + 1) < self.n:
            self.i = self.i + 1
            yield self.i


def show_memory_info(hint):
    pid = os.getpid()
    p = psutil.Process(pid)
    info = p.memory_full_info()
    memory = info.uss/1024/1024
    print('{} memory used: {} M'.format(hint, memory))


def test_iterable():
    show_memory_info('initing iterable')
    my_iterable = [x * x for x in range(100000000)]
    show_memory_info('after iterable initiated')
    print('********')


def test_iterator1():
    show_memory_info('initing iterator1')
    my_iterable = [x * x for x in range(100000000)]
    my_iterator1 = iter(my_iterable)
    del my_iterable
    gc.collect()
    print(next(my_iterator1))
    show_memory_info('after iterator1 initiated')
    print('********')


def test_iterator2():
    show_memory_info('initing iterator2')
    my_iterator2 = MyIterator2(100000000)
    show_memory_info('after iterator2 initiated')
    print('********')


def test_iterator3():
    show_memory_info('initing iterator3')
    my_iterator3 = MyIterator3(100000000)
    show_memory_info('after iterator3 initiated')
    print('********')


def test_generator():
    show_memory_info('initing generator')
    my_generator = (x * x for x in range(100000000))
    show_memory_info('after generator initiated')
    print('********')


if __name__ == '__main__':
    test_iterable()
    test_iterator1()
    test_iterator2()
    test_iterator3()
    test_generator()

结果如下:
Python:可迭代对象,迭代器,生成器
1. 对于可迭代对象,显然不是惰性的,占用了大量内存。
2. 这个迭代器是用 iter() 方法将一个可迭代对象转为一个迭代器,结果还是占用了大量内存,我一开始推测是由于一开始的可迭代对象占用了大量内存。但是我发现删除可迭代对象并回收内存之后还是占有了大量内存,因此我判定用这种方式生成的迭代器不是惰性的。
3. 手动定义 __iter__ 和 __next__ 生成的迭代器确实是惰性的,占用的内存大大减少。
4. 一开始我在解决生成器的内存减少是 yield 关键字的问题,结合 3 和 4 的对比,发现及时是 return 语句也是惰性的,所以惰性的关键不是 yield 的语句。
5. 生成器的惰性也是显然地。

总结

  1. 生成器 \subsetneqq 迭代器 \subsetneqq 可迭代对象。
  2. 任何可迭代对象都可以使用 for…in 语句。实际上在运行 for…in 时,由解释器帮助我们对可迭代独享调用了 iter() 方法,得到一个迭代器,然后每次使用 next 取一个值出来。
  3. 以列表 list 为例,list 本身只有 __iter__ 方法,因此只是一个可迭代对象,但是执行 for…in 的时候,调用 __iter__ 方法 返回一个迭代器,此迭代器有 __next__ 方法,正在用来执行 for…in…
  4. 迭代器的适用的场景:不关心元素的随机访问,元素的个数不可提前预测。
  5. 使用 iter(可迭代对象) 生成的迭代器不是惰性的,而严格手动定义 __iter__ 和 __next__ 方法的迭代器是惰性的,且与使用 return 语句和 yield 语句无关。
  6. 生成器是惰性的。
  7. 生成器使用 yield 语句更加优雅,但本身就是一种特殊的迭代器。
  8. 有的资料提到的生成器的延时操作可以用惰性计算理解:延迟操作就是在需要的时候才产生结果,不是立即产生结果,显然和惰性计算本质是类似的。
相关标签: Python