快乐学习
前程无忧、中华英才非你莫属!

Python 两大凶器-迭代与生成

1、前言

迭代器与生成器在Python 中非常有用的工具,可以帮助我们处理复杂数据问题的同时也能让我们的代码更加整洁性能更好。


2、迭代器概念

英文名:iterator,实现了方法 iternext 的对象是迭代器。

python 中的for 在底层就实现了迭代器这种技术。所以我们可以使用统一的for xxx in xxx: 的这种固定格式来迭代我们的数据。

例子:

for element in [1, 2, 3]:
    print(element)
for element in (1, 2, 3):
    print(element)
for key in {'one':1, 'two':2}:
    print(key)
for char in "123":
    print(char)
for line in open("myfile.txt"):
    print(line, end='')

在代码的底层,for 语句会在容器对象上调用 iter()函数,该函数返回一个定义了 next() 方法的迭代器对象,此方法将逐一访问容器中的元素。 当元素用尽时,next() 将引发 StopIteration 异常来通知终止 for 循环。 你可以使用 next() 内置函数来调用 next() 方法;这个例子显示了它的运作方式:

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

3、创建迭代器

3.1 官方案例:

看过迭代器协议的幕后机制,给你的类添加迭代器行为就很容易了。 定义一个 iter() 方法来返回一个带有 next() 方法的对象。 如果类已定义了 next(),则 iter() 可以简单地返回 self:

class Reverse:
    """用于向后循环序列的迭代器."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print(char)
...
m
a
p
s

3.2 自定义案例:

class MultiplyByTwo:
    def __init__(self, number):
        self.number = number
        self.counter = 0

    def __next__(self):
        self.counter += 1
        return self.counter, self.number * self.counter

mul = MultiplyByTwo(200)
print(next(mul))
print(next(mul))
print(next(mul))

'''
输出结果
(1, 200)
(2, 400)
(3, 600)
'''

让我们看看在Python 中迭代器是如何工作的,上面的代码,我们实现了一个MultiplyByTwo的类,该类有一个名字为next 函数,每当它调用时。都会返回一个新的迭代器,迭代器内部通过维护一个变量来记录序列中的位置,我们可以看到next 方法中使用了counter变量。但是我们使用for循环遍历这它就会出现异常:

for i in mul:
    print(i)

# TypeError: 'MultiplyByTwo' object is not iterable

类型错误,MultiplyByTwo对象是不可迭代。这里强调,MultiplyByTwo它是一个迭代器,但是它不是一个可迭代的对象,为了能让他可以通过for循环遍历,需要把它变成可迭代对象,需要在类里面添加iter方法。

例如:


class MultiplyByTwo:
    def __init__(self, number):
        self.number = number
        self.counter = 0

    def __iter__(self):
        return self

    def __next__(self):
        self.counter += 1
        return self.counter, self.number * self.counter

for i in mul:
    print(i)

'''
输出结果:会一直循环遍历下去成死循环

(1, 200)
(2, 400)
(3, 600)
此处为省略
(12368, 2473600)
此处为省略
'''

虽然我们的MultiplyByTwo迭代器,可以让for循环遍历了,但是它成死循环了,不是我们想要的,所以我们需要继续优化它。

例如:

class MultiplyByTwo:
    def __init__(self, number, bigValue):
        self.number = number
        self.counter = 0
        self.bigValue = bigValue

    def __iter__(self):
        return self

    def __next__(self):
        self.counter += 1

        value = self.number * self.counter

        if value > self.bigValue:
            raise StopIteration
        else:
            return self.counter, value

mul = MultiplyByTwo(200, 1000)

for i in mul:
    print(i)

'''
输出结果为:返回有限数据的迭代器。
(1, 200)
(2, 400)
(3, 600)
(4, 800)
(5, 1000)
'''

当引发StopIteration 这个异常时,MultiplyByTwo对象收到已经达到限制的信号,for 循环会自动处理这个异常,并退出循环。


4、生成器概念

在 Python 中,使用了 yield 的函数被称为生成器(generator),它又叫做生成迭代器的工具

生成器 是一个用于创建迭代器的简单而强大的工具。 当它要返回数据时会使用 yield 语句。每次在生成器上调用 next() 时,它会从上次离开的位置恢复执行(它会记住上次执行语句时的所有数据值)。

生成器在读取大量数据或者大量文件时,非常有用,他可以被暂停和恢复。生成器返回一个像列表一样可被迭代的对象。

这里做重要的概念区分:

可迭代对象:

列表、字典、元组、字符串、文件对象、迭代器 他们都是可迭代对象
因为它们内部都包含iter函数。

列表、字典、元组、字符串、文件对象他们都不属于迭代器,因为他们并不包含 next函数。

看到这里有些小伙伴蒙圈了,如何区分呢:

第一种方式:

可以使用pycharm 快捷键,ctrtl+ 鼠标单击对应list、dict 、类对象来查看类内部是否包含iter和next 函数,来区分是迭代器,还是普通的可迭代对象。在这里我们可以把迭代器看成一种特殊的可迭代对象。

第二种方式

from collections.abc import Iterator # 迭代器
from collections.abc import Iterable # 可迭代对象

# rev 变量为3.1 案例中rev变量。
isinstance(rev, Iterator) # True 输出真 说明他是迭代器
isinstance(rev, Iterable) # True 输出真,说明它又是可迭代对象

isinstance([1, 2, 3], Iterable) # Ture ,他是可迭代对象
isinstance([1, 2, 3], Iterator) # False ,说明他不是迭代器。

# 我们通过第二种方式,来判断它到底是不是迭代器的一个好办法。

5、官方生成器的案例

可以用生成器来完成的操作同样可以用前一节所描述的基于类的迭代器来完成。 但生成器的写法更为紧凑,因为它会自动创建 iter() 和 next() 方法。

另一个关键特性在于局部变量和执行状态会在每次调用之间自动保存。 这使得该函数相比使用 self.index 和 self.data 这种实例变量的方式更易编写且更为清晰。

除了会自动创建方法和保存程序状态,当生成器终结时,它们还会自动引发 StopIteration。 这些特性结合在一起,使得创建迭代器能与编写常规函数一样容易。

def reverse(data):
    print('开始')
    for index in range(len(data) - 1, -1, -1):
        print('for开始')

        yield data[index]  # 调用next停止的地方 ,

        print('for结束')  # 生成器从它停止的地方恢复

    print('结束')

rev = reverse('golf')
next(rev)

这里不断调用next 来输出对应的值,小编在例子中加了打印语句,可以方便大家可以看到在哪里暂停,又是在哪里恢复。

我们在对rev 进行判断,看看它到底是什么类型:


rev = reverse('golf')
from collections.abc import Iterator  # 迭代器

isinstance(rev, Iterator)  # True

总结:生成器的最终概念,生成迭代器。想要用好yield 你可以把它当做return 使用即可,但是它不会停止程序哦,并且要记住当再次遍历它时,它会从上次离开的位置恢复执行(并且会记住上次执行语句时的所有数据值),


6、生成器表达式

某些简单的生成器可以写成简洁的表达式代码,所用语法类似列表推导式,但外层为圆括号而非方括号。 这种表达式被设计用于生成器将立即被外层函数所使用的情况。 生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。

官方案例:

>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
260

>>> unique_words = set(word for line in page  for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

7、生成器案例

当我们想要生成一个0.5 到6.5 步长为0.2 的序列,使用range函数会报错,因为它不支持浮点数:

range(0.5, 6.5, 0.2)
# TypeError: 'float' object cannot be interpreted as an integer

那我们使用生成器技术,来优化range函数,自定义一个来符合我们的要求

# 生产某个范围内浮点数的生成器:
def frange(start, stop, increment):
    x = start
    while x < stop:
        yield x
        x += increment

fr = frange(0.5,6.5,0.2)

next(fr)
0.5
next(fr)
0.7

读取linux 系统比较大的数据文件,进行范围查询时,可使用切片迭代器技术,来加速查询:

from itertools import islice #

f = open('/var/log/dmesg')
for line in islice(f, 100, 300):  # 截取100行到300行之间的内容
    print(line)
for line in islice(f, 500):  # 截取前500行之间的内容
    print(line)
for line in islice(f, 100, None):  # 截取100行到文件末尾之间的内容
    print(line)

可以让大家轻松写成可读性较高的python 点击这个连接可以查看itertools 模块来安排迭代器之间的交互关系。


8、何时使用迭代器

在处理大规模数据文件时或数据流(在线视频)时非常有用,迭代器可以让我们更加灵活地一次只处理一段数据,而不是必须一次性的将全部数都加载到内存当中。

函数不要直接返回列表,应该让他逐个生成列表里面的值,用生成器来实现比让函数把结果存储到列表里在返回更清晰一些。

生成器函数所返回的迭代器产生的一系列值,都是由函数体的下一条yield 表达式决定的。


总结:这一篇章,最重要的技术是迭代器,使用迭代器技术,才能性能更好,使用生成器才会让我们的代码更加简洁

打赏

未经允许不得转载:同乐学堂 » Python 两大凶器-迭代与生成

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

特别的技术,给特别的你!

联系QQ:1071235258QQ群:367203382
error: Sorry,暂时内容不可复制!