Python程序设计 生成器

1. 基础概念

在讲迭代之前,先搞清楚这些名词:

  • 循环(loop),指的是在满足条件的情况下,重复执行同一段代码。比如,while 语句。
  • 迭代(iterate),指的是按照某种顺序逐个访问列表中的每一项。比如,for 语句。
  • 递归(recursion),指的是一个函数不断调用自身的行为。比如,以编程方式输出著名的斐波纳契数列。
  • 遍历(traversal),指的是按照一定的规则访问树形结构中的每个节点,而且每个节点都只访问一次。

迭代的话题如果要说起来,会很多,这里介绍比较初级的。

1.1 可迭代对象(iterable)

生成器(英文:generator)是一个非常迷人的东西,也常被认为是 Python 的高级编程技能。

什么是可迭代对象,简单的理解就是可以用作for循环上的一些对象就是可迭代对象。常见的可以迭代对象有哪些呢?

列表、元组、字典、集合字符串和open()打开的文件

从代码角度来说,对象内部实现了 __iter__() 方法或者实现了 __getitem__() 的方法。

简单理解:可以被 for 语句遍历的对象都是可迭代对象

1.2 迭代器(iterator)

迭代器是相对于可迭代对象(iterable)来说的,迭代器是继承了可迭代对象的。它相对于It而able而言,它实现了 __iter__() 和 __next__() 方法。另外,迭代器不会一次性把所有的元素都加载到内存中,只是在需要的时候才返回结果。可以把一个可迭代对象通过一定的方法转变为迭代器。

pro_lau = ['Python','Java','C++'] # 列表是一个可迭代对象a = iter(pro_lau) # 由可迭代对象的iter方法返回一个迭代器

1.3 生成器(generator)

生成器就是一种特殊的迭代器,可以由关键字yield来实现;同时迭代器并不是生成器,因为迭代器并没有生成器的部分功能,如数据传入功能。

总之迭代器和生成器在一定的功能上具有很高的相似性,都能起到节约内存的作用——就这个特点,就值得我们去学习,然后应用到编程中。

 

2. 自定义迭代器

2.1 判断可以迭代对象

可以使用 isinstance() 判断一个对象是否是 Iterable 对象:

isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。

In [50]: from collections import IterableIn [51]: isinstance([], Iterable)
Out[51]: TrueIn [52]: isinstance({}, Iterable)
Out[52]: TrueIn [53]: isinstance('abc', Iterable)
Out[53]: TrueIn [54]: isinstance(100, Iterable)
Out[54]: False

2.2 iter() 函数与 next() 函数

list、tuple等都是可迭代对象,我们可以通过 iter() 函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用next()函数来获取下一条数据。iter() 函数实际上就是调用了可迭代对象的 __iter__ 方法。

>>> li = [11, 22, 33, 44, 55]
>>> li_iter = iter(li)
>>> next(li_iter)
11
>>> next(li_iter)
22
>>> next(li_iter)
33
>>> next(li_iter)
44
>>> next(li_iter)
55
>>> next(li_iter)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
StopIteration
>>>

注意,当我们已经迭代完最后一个数据之后,再次调用next()函数会抛出 StopIteration 的异常,来告诉我们所有数据都已迭代完成,不用再执行next()函数了。

2.3 自定义迭代器

迭代器是用来帮助我们 记录每次迭代访问到的位置 ,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的__next__方法。所以,我们要想构造一个迭代器,就要实现它的__next__ 方法。但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可。

一个实现了__iter__ 方法和__next__ 方法的对象,就是迭代器。

listtuplesetdict 对象有__iter__()方法,标着他们能够迭代。这些类型都是 Python 中固有的,我们能不能自己写一个对象,让它能够迭代呢?

当然呢!要不然 python 怎么强悍呢。

# coding=utf-8"""
自己实现range方法
"""
class MyRange(object):def __init__(self, n):self.i = 0self.n = ndef __iter__(self):return selfdef __next__(self):if self.i < self.n:i = self.iself.i += 1return ielse:raise StopIteration()if __name__ == "__main__":x = MyRange(7)print("x.next()==>", x.__next__())print("x.next()==>", x.__next__())print("------for loop--------")for i in x:print(i)

以上代码的含义,是自己仿写了拥有 range() 的对象,这个对象是可迭代的。分析如下:

类 MyRange 的初始化方法 __init__() 就不再讲解了。

__iter__() 是类中的核心,它返回了迭代器本身。一个实现了__iter__()方法的对象,即意味着其实可迭代的。

含有 next() 的对象,就是迭代器,并且在这个方法中,在没有元素的时候要发起 StopIteration() 异常。

如果对以上类的调用换一种方式:

x = MyRange(7)
print(list(x))
print("x.next()==>", x.next())

print(list(x)) 将对象返回值都装进了列表中并打印出来,这个正常运行了。此时指针已经移动到了迭代对象的最后一个,next() 方法没有检测也不知道是不是要停止了,它还要继续下去,当继续下一个的时候,才发现没有元素了,于是返回了 StopIteration()

2.31 for循环的本质

for item in Iterable 循环的本质就是先通过 iter() 函数获取可迭代对象 Iterable 的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到 StopIteration 的异常后循环结束。

2.32 迭代器应用场景

迭代器最核心的功能就是可以通过next()函数的调用来返回下一个数据值。如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成的,那么也就意味着可以不用再依赖一个已有的数据集合,也就是说不用再将所有要迭代的数据都一次性缓存下来供后续依次读取,这样可以节省大量的存储(内存)空间。

举个例子,比如,数学中有个著名的斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

现在我们想要通过for...in...循环来遍历迭代斐波那契数列中的前n个数。那么这个斐波那契数列我们就可以用迭代器来实现,每次迭代都通过数学计算来生成下一个数。

def fib(n):num = 0a, b = 0, 1while num < n:print(a)a, b = b, a + bnum += 1print()fib(8)
2.33 可迭代对象的转化

除了for循环能接收可迭代对象,list、tuple等也能接收。

li = list(FibIterator(15))
print(li)
tp = tuple(FibIterator(6))
print(tp)

3. 生成器

生成器(英文:generator)是一个非常迷人的东西,也常被认为是 Python 的高级编程技能。

我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合 next() 函数进行迭代使用,我们可以采用更简便的语法,即 生成器(generator)。生成器是一类特殊的迭代器

3.1 生成器推导式

>>> my_generator = (x*x for x in range(4))

这是不是跟列表解析很类似呢?仔细观察,它不是列表,如果这样的得到的才是列表:

>>> my_list = [x*x for x in range(4)]

以上两的区别在于是 [] 还是 (),虽然是细小的差别,但是结果完全不一样。

创建 my_list 和 my_generator 的区别仅在于最外层的 [ ] 和 ( ) , my_list 是一个列表,而 my_generator 是一个生成器。我们可以直接打印出列表 L 的每一个元素,而对于生成器 my_generator,我们可以按照迭代器的使用方法来使用,即可以通过 next() 函数、for循环、list() 等方法使用。

In [19]: next(my_generator)
Out[19]: 0In [20]: next(my_generator)
Out[20]: 2In [21]: next(my_generator)
Out[21]: 4In [22]: next(my_generator)
Out[22]: 6In [23]: next(my_generator)
Out[23]: 8In [24]: next(my_generator)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-24-380e167d6934> in <module>()
----> 1 next(my_generator)StopIteration:In [25]:
In [26]: my_generator = ( x*2 for x in range(5))In [27]: for x in my_generator:....:     print(x)....:     
0
2
4
6
8In [28]:

难道生成器就是把列表解析中的 [] 换成 () 就行了吗?这仅仅是生成器的一种表现形式和使用方法罢了,仿照列表解析式的命名,可以称之为“生成器解析式”(或者:生成器推导式、生成器表达式)。

生成器解析式是有很多用途的,在不少地方替代列表,是一个不错的选择。特别是针对大量值的时候。列表占内存较多,迭代器(生成器是迭代器)的优势就在于少占内存,因此无需将生成器(或者说是迭代器)实例化为一个列表,直接对其进行操作,方显示出其迭代的优势。

3.2 生成器定义

通过生成器解析式得到的生成器,掩盖了生成器的一些细节,并且适用领域也有限。下面就要剖析生成器的内部,深入理解这个魔法工具。

yield 这个词在汉语中有“生产、出产”之意,在 Python 中,它作为一个关键词(你在变量、函数、类的名称中就不能用这个了),是生成器的标志。

>>> def g():
...     yield 0
...     yield 1
...     yield 2
... 
>>> g
<function g at 0xb71f3b8c>

建立了一个非常简单的函数,跟以往看到的函数唯一不同的地方是用了三个 yield 语句。然后进行下面的操作:

>>> ge = g()
>>> ge
<generator object g at 0xb7200edc>
>>> type(ge)
<type 'generator'>

上面建立的函数返回值是一个生成器(generator)类型的对象。

>>> dir(ge)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']

在这里看到了__iter__() 和 next(),说明它是迭代器。既然如此,当然可以:

>>> ge.next()
0
>>> ge.next()
1
>>> ge.next()
2
>>> ge.next()
Traceback (most recent call last):File "<stdin>", line 1, in <module>
StopIteration

从这个简单例子中可以看出,那个含有 yield 关键词的函数返回值是一个生成器类型的对象,这个生成器对象就是迭代器。

我们把含有 yield 语句的函数称作生成器。生成器是一种用普通函数语法定义的迭代器。通过上面的例子可以看出,这个生成器(也是迭代器),在定义过程中并没有像上节迭代器那样写__inter__() 和 next(),而是只要用了 yield 语句,那个普通函数就神奇般地成为了生成器,也就具备了迭代器的功能特性。

yield 语句的作用,就是在调用的时候返回相应的值。详细剖析一下上面的运行过程:

  1. ge = g():除了返回生成器之外,什么也没有操作,任何值也没有被返回。
  2. ge.next():直到这时候,生成器才开始执行,遇到了第一个 yield 语句,将值返回,并暂停执行(有的称之为挂起)。
  3. ge.next():从上次暂停的位置开始,继续向下执行,遇到 yield 语句,将值返回,又暂停。
  4. ge.next():重复上面的操作。
  5. ge.next():从上面的挂起位置开始,但是后面没有可执行的了,于是 next() 发出异常。

从上面的执行过程中,发现 yield 除了作为生成器的标志之外,还有一个功能就是返回值。那么它跟 return 这个返回值有什么区别呢?

3.3 yield

为了弄清楚 yield 和 return 的区别,我们写两个没有什么用途的函数:

>>> def r_return(n):
...     print("进入了函数.")
...     while n > 0:
...         print("返回内容之前")
...         return n
...         n -= 1
...         print("返回内容之后")
... 
>>> rr = r_return(3)
进入了函数.
返回内容之前
>>> rr
3

从函数被调用的过程可以清晰看出,rr = r_return(3),函数体内的语句就开始执行了,遇到 return,将值返回,然后就结束函数体内的执行。所以 return 后面的语句根本没有执行。

下面将 return 改为 yield:

>>> def y_yield(n):
...     print("进入了函数.")
...     while n > 0:
...         print("返回内容之前")
...         yield n
...         n -= 1
...         print("返回内容之后")
... 
>>> yy = y_yield(3)    #没有执行函数体内语句
>>> yy.next()          #开始执行
进入了函数.
返回内容之前
3                      #遇到 yield,返回值,并暂停
>>> yy.next()          #从上次暂停位置开始继续执行
返回内容之后
返回内容之前
2                      #又遇到 yield,返回值,并暂停
>>> yy.next()          #重复上述过程
返回内容之后
返回内容之前
1
>>> yy.next()
返回内容之后            #没有满足条件的值,抛出异常
Traceback (most recent call last):File "<stdin>", line 1, in <module>
StopIteration

结合注释和前面对执行过程的分析,读者一定能理解 yield 的特点了,也深知与 return 的区别了。

一般的函数,都是止于 return。作为生成器的函数,由于有了 yield,则会遇到它挂起,如果还有 return,遇到它就直接抛出 SoptIteration 异常而中止迭代。

经过上面的各种例子,已经明确,一个函数中,只要包含了 yield 语句,它就是生成器,也是迭代器。这种方式显然比前面写迭代器的类要简便多了。但,并不意味着上节的就被抛弃。是生成器还是迭代器,都是根据具体的使用情景而定。

3.4 生成器接受参数

def simple_coroutine():print('生成器启动')n = yieldprint('生成器接收到的参数为 %s' % n)my_coro = simple_coroutine()
next(my_coro)
my_coro.send("hello")

当执行到 my_coro.next() 的时候,生成器开始执行,在内部遇到了 yield 挂起。注意在生成器函数中,n = yield 中的 n = yield 是一个表达式,并将接收到的结果赋值给 n。

当执行 my_coro.send("hello") 的时候,原来已经被挂起的生成器(函数)又被唤醒,开始执行 n = yield,也就是讲 send() 方法发送的值返回。这就是在运行后能够为生成器提供值的含义。

如果接下来再执行 next(my_coro) 会怎样?

什么也没有,其实就是返回了 None。按照前面的叙述,读者可以看到,这次执行 r.next(),由于没有传入任何值,yield 返回的就只能是 None.

最后一句,你在编程中,不用生成器也可以。

生成器只会记录规则,不会记录值

案例:协程计算平均值

def averager():total = 0.0count = 0average = Nonewhile True:term = yield averagetotal += termcount += 1average = total / countcoro_avg = averager()
next(coro_avg)
print(coro_avg.send(10))
print(coro_avg.send(30))
print(coro_avg.send(5))

3.5 总结

生成器与普通函数的区别:生成器返回的是记录与规则,普通函数返回结果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/466968.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mac m1 docker本地部署canal 监听mysql的binglog日志

mac m1 docker本地部署canal监听mysql的binglog日志(虚拟机同理) 根据黑马视频部署 1.docker 部署mysql 1.docker拉取mysql 镜像 因为m1是arm架构.需要多加一条信息 正常拉取 docker pull mysql:tagm1拉取 5.7的版本. tag需要自己指定版本 docker pull --platform linux/x…

[linux]docker基础

常见命令 Docker最常见的命令就是操作镜像、容器的命令&#xff0c;详见官方文档: Docker Docs 案例: 查看DockerHub&#xff0c;拉取Nginx镜像&#xff0c;创建并运行Nginx容器 在DockerHub中搜索Nginx镜像 拉取Nginx镜像 查看本地镜像列表 把镜像保持到本地 查看保持命令的…

C++builder中的人工智能(10)神经网络中的Sigmoid函数

在这篇文章中&#xff0c;我们将探讨最受欢迎的激活函数之一——Sigmoid函数。我们将解释什么是Logistic函数&#xff0c;以及它与Sigmoid函数的区别&#xff0c;并展示如何在C应用中使用这些函数。 目录 人工神经网络&#xff08;ANN&#xff09;中的激活函数是什么&#xff…

cursor:如何注销帐号和使用流量

点击右上角的设定图标 点击管理 在弹出的网页点登入 点”continue" 点SETING 了解最新信息请扫码关注&#xff1a;

如何选择适合小团队的项目管理工具?免费与开源软件推荐

目录 一、小团队项目管理工具的重要性 二、热门项目管理工具介绍 &#xff08;一&#xff09;禅道 &#xff08;二&#xff09;Trello &#xff08;三&#xff09;Asana &#xff08;四&#xff09;JIRA 三、免费项目管理软件推荐 &#xff08;一&#xff09;ES 管理器 …

Scaffold-ETH 2:颠覆传统开发的区块链神器,快速构建你的去中心化应用!

目录 引言一、Scaffold-eth框架二、前期准备三、搭建Scaffold-ETH 2&#xff08;一&#xff09;使用npx create-ethlatest进行设置&#xff08;二&#xff09;使用git clone进行设置1、克隆仓库&#xff1a;2、进入到此目录3、安装依赖项 四、配置Scaffold ETH-2的开发环境&…

kafka+zookeeper的搭建

kafka从2.8版本开始&#xff0c;就可以不用配置zookeeper了&#xff0c;但是也可以继续配置。我目前使用的kafka版本是kafka_2.12-3.0.0.tgz&#xff0c;其中前面的2.12表示是使用该版本的scala语言进行编写的&#xff0c;而后面的3.00才是kafka当前的版本。 通过百度网盘分享…

恢复rm -rf删除的数据

注&#xff1a;本文演示的是ext4文件系统格式数据恢复 系统版本&#xff1a;ubuntu16.04 恢复数据目录&#xff1a;数据盘&#xff08;非根&#xff09;目录 恢复工具&#xff1a;extundelete 0.2.4 恢复所有被删除数据 ext4magic 恢复指定目录数据 一、注意事项&#xff1a; …

Elasticsearch(三):Elasticvue使用及DSL执行新增、查询操作

Elasticvue使用及DSL执行CURD 1 概述2 什么是Elasticsearch DSL3 基本结构4 客户端工具介绍4.1 索引介绍4.2 创建简单索引4.3 创建相对完整的索引4.4 插入数据4.4.1 基本插入操作4.4.2 批量插入操作 5 常用的DSL查询类型5.1 match查询5.1.1 match工作原理5.1.2 operator 参数5.…

静态库、动态库、framework、xcframework、use_frameworks!的作用、关联核心SDK工程和测试(主)工程、设备CPU架构

1.1库的概念 库&#xff1a;程序代码的集合&#xff0c;编译好的二进制文件加上头文件供使用&#xff0c;共享程序代码的一种方式。 1.2库的分类 根据开源情况分为&#xff1a;开源库&#xff08;能看到具体实现&#xff09;、闭源库&#xff08;只公开调用的的接口&#xf…

C++【string类,模拟实现string类】

&#x1f31f;个人主页&#xff1a;落叶 &#x1f31f;当前专栏: C专栏 目录 为什么学习string类 C语言中的字符串 标准库中的string类 auto和范围for auto关键字 迭代器 范围for string类的常用接口说明和使用 1. string类对象的常见构造 2.string类对象的容量操作 3…

Me-LLaMA——用于医疗领域的新型开源大规模语言模型

摘要 大规模语言模型的出现是提高病人护理质量和临床操作效率的一个重大突破。大规模语言模型拥有数百亿个参数&#xff0c;通过海量文本数据训练而成&#xff0c;能够生成类似人类的反应并执行复杂的任务。这在改进临床文档、提高诊断准确性和管理病人护理方面显示出巨大的潜…

关于在VS中使用Qt不同版本报错的问题

最开始需要配置的地方 首先看一下我的Qt有关的环境变量&#xff1a; Path环境变量里&#xff1a; 这里就是把对应Qt编译器环境下的bin目录放进来&#xff1a;比如你使用的是msvc2017_64或者MinGW QMAKESPEC环境变量&#xff1a; 这个就选择Qt对应的编译器目录下的\mkspecs\w…

Windows Server 怎么关闭IE增强安全配置(关闭IE弹窗)

首先第一步打开IE浏览器&#xff0c;根据下图所示&#xff0c;访问网页时会弹出警告窗口。 打开【控制面板】图标。查看方式改为小图标&#xff0c;打开【管理工具】 第五步进入【管理工具】页面后&#xff0c;找到并双击【服务器管理器】选项。 第六步在弹出的窗口中&#…

中肿团队提出的“免疫三明治”(放疗+化疗+免疫治疗),成功登上柳叶刀肿瘤|顶刊精析·24-11-08

小罗碎碎念 该研究首次发现在同期放化疗基础上增加特瑞普利单抗&#xff08;PD-1抗体&#xff09;新辅助和辅助治疗显著提高了高危局部晚期鼻咽癌患者生存率。 如果大家看完这篇推送以后&#xff0c;有什么好的医工交叉点子&#xff0c;欢迎和我一起探讨&#xff01;&#xff0…

Golang--协程和管道

1、概念 程序&#xff1a; 是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。(程序是静态) 进程&#xff1a; 是程序的一次执行过程。正在运行的一个程序&#xff0c;进程作为资源分配的单位&#xff0c;在内存中会为每个进程分配不同的内存区域&#xff0…

动力商城-02 环境搭建

1.父工程必须满足&#xff1a;1.1删除src目录 1.2pom 2.依赖继承 //里面的依赖&#xff0c;后代无条件继承<dependencies></dependencies>//里面的依赖&#xff0c;后代想要继承&#xff0c;得自己声明需要使用&#xff0c;可以不写版本号&#xff0c;自动继承&l…

JavaWeb开发9

ResponseBody 类型&#xff1a;方法注解、类注解 位置&#xff1a;Controller方法上/类上 作用&#xff1a;将方法返回值直接响应&#xff0c;如果返回值类型是实体对象/集合&#xff0c;将会转换为JSON格式响应 说明&#xff1a;RestControllerControllerResponseBody; 统…

夜天之书 #103 开源嘉年华纪实

上周在北京参与了开源社主办的 2024 中国开源年会。其实相比于有点明显班味的“年会”&#xff0c;我的参会体验更像是经历了一场中国开源的年度嘉年华。这也是在会场和其他参会朋友交流时共同的体验&#xff1a;在开源社的 COSCon 活动上&#xff0c;能够最大限度地一次性见到…

06 Oracle性能优化秘籍:AWR、ASH、SQL trace与实时监控的实战指南

文章目录 Oracle性能优化秘籍&#xff1a;AWR、ASH、SQL trace与实时监控的实战指南一、AWR&#xff08;Automatic Workload Repository&#xff09;1.1 理论部分1.2 实践部分1.2.1 使用方式1.2.2 分析方式 二、ASH&#xff08;Active Session History&#xff09;2.1 理论部分…