最新消息:欢迎光临 魔力 • Python!大家可以点开导航菜单中的【学习目录】,这个目录类似图书目录,更加方便学习!

Python3萌新入门笔记(32)

Python教程 小楼一夜听春语 6682浏览 0评论

这一篇教程,我们来接触两个很有趣的内容,因为很有趣,所以很长。

一、迭代器

“迭代”这个词,如果没有接触过(例如IT界之外的人或IT小白),可能理解起来会有一些困难。

其实,迭代就是指重复的去做一些事情(例如:一晚上迭代七次,就是把某件事情做七次)。

不过,有些时候,迭代需要有限制,例如刚才的例子,迭代太多了容易精尽人亡。

还有就是,有需要了再迭代,不需要的时候迭代,往往会影响性能。

那么,迭代器又是什么?

迭代器就是一段代码,这段代码能够根据需求,重复完成某些事情。

这段代码由两部分组成,一部分是__iter__()方法,一部分是__next__()方法。

你可能会理解成,“官人我还要”是__next__方法来完成,而官人要干的事是由__iter__()方法完成。

那你就想错了!

正好相反,要做的事是通过__next__()方法来实现。

我们来看一段关于偶数迭代器的代码。(又是偶数……)

示例代码:

class Even:
    def __init__(self):
        self.even = 0  # 定义变量保存数值

    def __next__(self):  # 重写next方法
        self.even += 2  # 计算每次迭代的偶数
        return self.even  # 返回计算结果

    def __iter__(self):  # 重写iter方法
        return self  # 返回实例对象为可迭代对象


even = Even()
# print(list(even)) # 想体验精尽人亡就取消这句代码的注释,然后运行......
for i in range(5):
    print(next(even))  # 显示输出结果为:2 4 6 8 10

上方这段代码中,__next__()方法中的代码定义了如何获取偶数。

而__iter__()方法只是返回实例对象。

其实,实现了__next__()方法的对象就是迭代器。

大家可以注释掉上方代码中的__iter__()方法,运行依然正常。

那么,__iter__()方法的作用是什么呢?

大家先别取消__iter__()方法的注释,把上面代码中的for循环改成下面这段代码。

示例代码:

for i in even:
    if i <= 10:
        print(i)  # 显示输出结果为:2 4 6 8 10
    else:
        break

运行代码,会发现这次for循环抛出了异常。

TypeError: ‘Even’ object is not iterable

类型错误: ‘Even’对象不能够迭代

然后,大家取消__iter__()方法的注释,再次运行代码,就能够正常运行了。

也就是说,实现了__iter__()方法的对象才是可迭代对象。

接下来,我们观察一下刚才的代码。

这个迭代器能够生成无限数量的偶数。

所以,如果不加限制,直接通过print语句显示输出迭代器的后果……(如果运行后没死机,请告诉我电脑哪买的……)

通过print()函数显示输出迭代器对象,就会让迭代器不停…..不停…..不停……生成偶数,一直到系统资源耗光……

实际上,迭代器并不是直接生成所有的计算结果。

如果不用print()函数调用,它不会有任何偶数产生。

我们通过内置函数next(),可以每次获取迭代器计算出来的一个偶数。

每一次取值的时候,迭代器对象才会调用自身的__next()__方法计算新的数值并返回。

直到迭代器没有值被返回时,会抛出StopIteration(停止迭代)异常。

所以,当我们需要一个很多值的列表(list),这个列表又能够用迭代器产生,就不要用列表(list)。

列表包含的值太多,会占用大量内存,而迭代器则不会。

接下来,我们看一下如何通过迭代器获取一个有限数量列表。

还是用获取偶数的例子。

示例代码:

class Even:
    def __init__(self, count):  # 定义获取次数的参数
        self.even = 0
        self.count = count  # 定义变量保存获取次数

    def __next__(self):
        if self.even < self.count * 2:  # 判断最后一次生成的偶数小于最大的偶数
            self.even += 2
            return self.even
        else:
            raise StopIteration  # 到达指定数量时停止迭代

    def __iter__(self):
        return self

even = Even(10)  # 实例化并传入参数
print(list(even))  # 显示输出结果为:[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

通过上方代码大家能够看到,通过内置函数list()可以把有限数量的迭代器对象转换为列表。

另外,内置函数iter()能够从可迭代对象中获取生成器,并通过内置函数next()逐一读取。

示例代码:

tup=(1,2,3,4,5)
ite=iter(tup)
for i in range(5):
    print(next(ite)) # 显示输出结果为:1 2 3 4 5

注意:for语句不要写成“for i in ite”,因为这样也在调用迭代器取值,每个迭代器的值只能获取一次,取完为止。

示例代码:(错误)

for i in ite:
    print(next(ite)) # 显示输出结果为:2 4和StopIteration异常

上面代码之所以显示输出2和4,是因为1、3、5被for语句中的i取走了。

而StopIteration异常,是因为没有限制取值次数,取不到值所产生的。

二、生成器

生成器是通过函数实现的迭代器。

这里我们会接触到一个新的关键词yield(生产)。

看到英文后面的中文,是不是感到亲切了很多?

所以,不要被任何英文单词所困扰。

yield这个关键词能够指定生成某个函数计算结果。

所以,包含yield语句的函数都是生成器。

和迭代器一样,这些结果不是直接生成好的,而是能够在使用时逐一生成。

例如,能够从一个列表中获取所有能够被3整除的数字的生成器。

示例代码:

def generate(lst):  # 定义生成器函数
    for i in lst:  # 循环遍历参数列表
        if i % 3 == 0:  # 判断是否符合条件
            yield i  # 生成符合条件的数值

lst = [97, 19, 29, 72, 16, 93, 47, 92, 26, 75, 62, 89, 58, 10, 65, 63, 13, 52, 51, 60]
g = generate(lst)
while True:  
    try:
        print(next(g))  # 通过内置函数next逐一生成数值
    except:  # 捕获到异常时跳出循环
        break

上方代码中的while循环也可以用for语句代替

for num in g: print(num)

这里主要展示可以使用内置函数next()逐一获取生成器的计算结果。

另外,像这种通过生成器生成列表的操作,我们还可以使用生成器推导式(也叫生成器表达式)。

这个生成器推导式和我们学过的列表推导式非常相像,不同的是生成器推导式是写在两个圆括号“()”中。

刚才的例子,我们可以写成生成器推导式:

exp=(x for x in lst if x %3==0 ) # 通过生成器推导式定义生成器
print(list(exp))  # 使用内置函数list()将生成器转换为列表,显示输出结果为:[72, 93, 75, 63, 51, 60]

接下来,我们来看一个能够将嵌套列表中的所有元素取出生成非嵌套列表的生成器。

例如:通过列表[[1,2],3,4,[5,6],[7,8,9]]生成列表[1,2,3,4,5,6,7,8,9]

目标列表是双层嵌套的列表,所以我们可以通过嵌套双层for循环来完成。

示例代码:

def generate(lst):  # 定义生成器函数
    for sublst in lst:  # 循环遍历参数列表
        try:
            for num in sublst:  # 循环遍历参数列表的子列表
                yield num  # 生成符合条件的数值
        except:  # 如果sublst为单个数字会发生异常
            yield sublst  # 发生异常时将单个数字生成


lst = [[1, 2], 3, 4, [5, 6], [7, 8, 9]]
print(list(generate(lst)))  # 使用内置函数list()将生成器转换为列表,显示输出结果为:[1, 2, 3, 4, 5, 6, 7, 8, 9]

看上去完美解决了问题,但是,如果不知道列表的嵌套层级呢?

例如,通过列表[1,2,[3,[4,5]],[6,[7,[8,9]]]]生成列表[1,2,3,4,5,6,7,8,9]

很显然,上面的生成器就没有办法处理了。

我们来定义一个完美的生成器,不管列表嵌套多少层都能够给出正确的结果。

这就需要使用递归来实现了。

示例代码:

def generate(lst):  # 定义生成器函数
    for sublst in lst:  # 循环遍历参数列表
        try:
            for element in generate(sublst):  # 循环遍历递归的生成器元素
                yield element  # 将递归的生成器元素生成(3,4,6,7,8,9)
        except:  # 如果sublst为单个数字会发生异常
            yield sublst  # (1,2,5)发生异常时将数字生成,递归中(3,4,6,7,8,9)发生异常时将数字生成。

lst = [1, [2, [3, 4]], 5, [6, [7, [8, 9]]]]
print(list(generate(lst)))  # 使用内置函数list()将生成器转换为列表,显示输出结果为:[1, 2, 3, 4, 5, 6, 7, 8, 9]

在上方的代码中,列表中的单个数字元素,都会直接发生异常,通过yield sublst语句直接生成,而列表中嵌套的列表数量决定了递归的次数,每一次的递归都会产生一个生成器,这些生成器中的元素内容全部通过yield element语句生成。

这样的一个生成器,不管列表有多少层嵌套都能够进行转换。

但是,这个生成器只能够对元素全部都是数字的列表进行处理。

如果包含字符串元素,就无法得到正确结果。

例如,对列表[[1, 2], 3,’abc’, 4, [[5, 6, [7, 8]]]]的处理结果是[1, 2, 3, ‘a’, ‘b’, ‘c’, 4, 5, 6, 7, 8]。

而我们期望的结果是[1, 2, 3, ‘abc’, 4, 5, 6, 7, 8]

所以,代码需要进行更改。

示例代码:(带有注释的语句为新增语句)

def generate(lst):
    for sublst in lst:
        try:
            try:  # 验证元素是否为字符串
                sublst + ''
            except:  # 如果发生异常(不是字符串)
                pass  # 继续执行下方的for循环
            else:  # 否则抛出异常,被外层except捕获。
                raise Exception
            for element in generate(sublst):
                yield element

        except:
            yield sublst

到这里,我们来做个总结。

生成器是由生成器函数和返回的迭代器组成。

print(generate)  # 未调用函数时,显示输出结果为:<function generate at 0x000000000294E730>
print(generate([]))  # 调用函数时,显示输出结果为:<generator object generate at 0x0000000002576888>

最后,我们再来看3个关于生成器的方法:send()、throw()和close()。

1、send()

send()方法不能直接使用,他只有在生成器挂起状态(至少生成一次)下才能使用。

这里通过前面的案例,演示一下send()方法的使用。

大家可以通过代码注释进行理解。

示例代码:

def generate(lst):
    for i in lst:
        if i % 3 == 0:
            s = yield i  # 通过变量s获取外部send()函数传入的字符串
            print(s % i)  # 显示输出传入的字符串,并将上一次的生成结果插入到字符串中转换说明符的位置。

lst = [97, 19, 29, 72, 16, 93, 47, 92, 26, 75, 62, 89, 58, 10, 65, 63, 13, 52, 51, 60]
g = generate(lst)
next(g) # 生成第1个数值,此时生成器为挂起状态。
while True:
    try:
        g.send('数字%s能够被3整除...')  # 将格式化字符串发送到生成器内部
    except:
        break

注意:send()方法获取到的是前一次生成的结果。

2、throw()

这个方法能够引发生成器异常,还是借用之前的案例。

在生成器中,我们加入try/except语句,捕捉并抛出异常。

示例代码:

def generate(lst):
    try:  # 捕获异常
        for i in lst:
            if i % 3 == 0:
                yield i
    except:  # 抛出异常
        raise Exception('程序已终止')

lst = [97, 19, 29, 72, 16, 93, 47, 92, 26, 75, 62, 89, 58, 10, 65, 63, 13, 52, 51, 60]
g = generate(lst)
print(next(g))  # 显示输出结果为:72
g.throw(Exception)  # 引发异常

3、close()

close()方法用于关闭生成器,关闭之后无法再继续生成。

示例代码:

def generate(lst):
    for i in lst:
        if i % 3 == 0:
            yield i

lst = [97, 19, 29, 72, 16, 93, 47, 92, 26, 75, 62, 89, 58, 10, 65, 63, 13, 52, 51, 60]
g = generate(lst)
print(next(g))  # 显示输出结果为:72
g.close()
print(next(g))  # 抛出异常:StopIteration

另外,我们还可以通过生成器表达式创建生成器。

大家应该还记得列表推导式可以帮助我们创建一个列表。

生成器表达式和列表表达式非常相像,它的语法是:(表达式 for 变量 in 迭代对象 [条件表达式])。

举个简单的例子。

示例代码:

n = (i * 2 for i in [1, 2, 3, 4])
print(type(n))
print(next(n))
print(next(n))
print(next(n))
print(next(n))
print(next(n))

上方的代码运行结果如下:

<class 'generator'>
2
4
6
8
Traceback (most recent call last):
  File "G:/Python/abc.py", line 7, in <module>
    print(next(n))
StopIteration

本节知识点:

1、迭代器的概念及使用

2、生成器的概念及使用

3、在生成器中使用递归

4、与生成器相关的方法

5、生成器表达式

本节英文单词与中文释义:

1、expression:表达式

2、yield:生产

3、generate:生成

4、send:发送

5、throw:抛出

6、close:关闭

练习:

1、通过迭代器生成斐波那契数列。

2、通过生成器生成斐波那契数列。

答案:(见评论1楼)

转载请注明:魔力Python » Python3萌新入门笔记(32)

头像
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网站 (可选)

网友最新评论 (6)

  1. 小楼一夜听春语
    练习1:
    class Fibonacci:
        def __init__(self):
            self.x = 0  # 定义变量为初始值
            self.y = 1  # 定义变量为初始值
    
        def __next__(self):
            self.x, self.y = self.y, self.x + self.y  # 设置x的值为y,设置y的值为前两数之和。
            return self.x  # 返回计算结果中的首个数字
    
        def __iter__(self):
            return self
    
    fibo = Fibonacci()
    for i in range(10):  # 循环指定次数
        print(next(fibo))  
    
    练习2:
    def fibonacci(count):
        x = 0  # 定义初始值变量
        y = 1  # 定义初始值变量
        i = count  # 定义计数变量
        while i > 0:  # 如果计数大于零进行数值计算
            x, y = y, x + y
            i -= 1  # 计数递减
            yield x  # 生成计算结果
    
    print(list(fibonacci(10)))
    
    小楼一夜听春语7年前 (2017-09-30)回复
  2. 头像
    可能是我自己问题,表示晦涩看不懂,到3个关于生成的器的方法,那就更晕了。
    走路爱走神7年前 (2018-05-29)回复
  3. 头像
    每次使用迭代器都会报错 ---- F0002:: generator raised StopIteration ---- 但是不影响代码运行
    iKurum7年前 (2018-06-05)回复
    • 小楼一夜听春语
      try一下
      小楼一夜听春语7年前 (2018-06-06)回复
  4. 头像
    真的好难···在这一章上停留了4天了>_<
    MM5年前 (2019-07-25)回复
  5. 头像
    from collections import Iterable from collections import Iterator class Even: def __init__(self): self.even = 0 def __next__(self): # 重写next方法 self.even += 2 # 计算每次迭代的偶数 return self.even # 返回计算结果 def __iter__(self): # 重写iter方法 return self # 返回实例对象为可迭代对象 even = Even() print(isinstance(even,Iterable)) print(isinstance(even,Iterator)) 把__iter__方法和第一条print语句注释掉,输出的是假 把__next__方法和第二条print语句注释掉,输出的是真 不做注释输出的都是真 所以得出结论,只实现了__next__方法,并不是迭代器啊 要同时实现了__next__和__iter__方法才是迭代器.
    dcr5年前 (2019-11-29)回复