Python的程序结构[7] -> 生成器/Generator -> 生成器浅析

本文深入探讨Python中的生成器概念,包括生成器与迭代器的区别、生成器的创建方法及其内部工作机制。通过具体示例介绍了如何使用yield表达式、生成器函数及生成器对象的常用方法如send()、close()和throw()。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

生成器 / Generator


目录

  1. 关于生成器
  2. 生成器与迭代器
  3. 生成器的建立
  4. 通过迭代生成器获取值
  5. 生成器的 close 方法
  6. 生成器的 send 方法
  7. 生成器的 throw 方法
  8. 空生成器的检测方法
 
1 关于生成器

从计算机科学角度上看,生成器是一种类协程半协程(Semi-coroutine),生成器提供了一种可以通过特定语句或方法来使生成器的执行对象(Execution)暂停,而这语句一般都是 yield。通过  yield 语句将每一次的结果切出执行对象,并带到主线程上来。yield 可以将一个值带出协程,而主线程也可以通过生成器对象的方法将一个值带回生成器的执行对象中去。

在 Python 中生成器常是一个由生成器函数产生的对象,可以使用 nex t函数调用其内部参数,直到所有参数均被生成返回耗尽,则返回一个 StopIteration 异常。在生成器中,返回参数值使用的不是 return 而是 yield,也正是这个 yield 使其所在的函数变成了一个生成器。

对于生成器来说,它不会把结果保存在特定序列中,而是将生成器的状态进行保存。每一次 yield 后都会保存生成器的上下文(包括内部状态及变量值),当下次迭代到来时,则从 yield 处继续生成器的后续函数,且内部状态均为上次 yield 前的状态。

 

2 生成器与迭代器

而对于生成器与迭代器的关系,可以说每一个生成器都是一个迭代器,反之不亦然。也可以认为,生成器是一个带有迭代器特性,并且具有一个或多个 yield 表达式对象

通过源码可以查看到,generator 的基类为 iterator。

 

3 生成器的建立

生成器的建立主要有以下两种:

1. 通过函数中的 yield 表达式,挂起函数;

2. 对于可迭代对象,使用[]推导时会遍历可迭代对象产生一个列表,而使用()推导时,会返回可迭代的对象作为一个生成器

关于生成器的生成,可以使用以下两种方式,即分别使用()推导和 yield 进行生成

 1 # Use () to replace [], make it into a generator
 2 gen = (x**2 for x in range(7))
 3 lis = [x**2 for x in range(7)]
 4 print(type(gen), type(lis))
 5 
 6 # Define function to create generator by using yield
 7 # Return will cause StopIertation and return value will be present as an explaination for StopIteration
 8 def gener(m):
 9     n = 1
10     while n < m:
11         yield n
12         n += 1
13         if n == 3:
14             return 'Stop at 3'
15 
16 # gener is function, while gener(7) is generator
17 print(type(gener), type(gener(7)))

输出结果为

<class 'generator'> <class 'list'>  
<class 'function'> <class 'generator'>  

通过输出的第一行结果可以看到,通过[]推导得到的是一个 list ,而通过 ()推导得到的是一个生成器类,即返回的是生成器对象。

而对于 yield 方式生成的生成器,可以看到,生成生成器的 gener 是一个函数,而调用函数之后返回的结果 gener(m) 则是一个生成器。

同时,还可以通过 help 查看生成器内部的一些信息,

1 help(gener(10))  

输出结果

Help on generator object:

gener = class generator(object)
 |  Methods defined here:
 |  
 |  __del__(...)
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __next__(self, /)
 |      Implement next(self).
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  close(...)
 |      close() -> raise GeneratorExit inside generator.
 |  
 |  send(...)
 |      send(arg) -> send 'arg' into generator,
 |      return next yielded value or raise StopIteration.
 |  
 |  throw(...)
 |      throw(typ[,val[,tb]]) -> raise exception in generator,
 |      return next yielded value or raise StopIteration.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  gi_code
 |  
 |  gi_frame
 |  
 |  gi_running
View Code

对于生成器来说,内部包含了许多内置方法,包括 iter() 会调用的 __iter__ 函数,next() 会调用的 __next__ 函数,这使得生成器遵循迭代器协议,以及3个可以直接调用的方法 close(),  send(),  throw()。

 

4 通过迭代生成器获取值

对于生成器的迭代可以由两种方式,第一种是使用 for 循环迭代,另一种是使用 next 获取下一个生成值,

 1 # Iterate the generator
 2 # Use "for"
 3 for i in gener(7):
 4     print(i)
 5 g = gener(7)
 6 # Use "next"
 7 while g:
 8     try:
 9         print(next(g))
10     except StopIteration as e:
11         print(e)
12         break

输出结果

1  
2  
1  
2  
Stop at 3  

此处结果可以看出,两种方式均成功让生成器运作,且在迭代到 3 时,生成器内部的 return 语句会抛出一个 StopIteration 异常,并且将返回值作为对异常的说明,可以通过异常捕获进行查看。

 

5 生成器的 close 方法

使用 close 方法可以将生成器进行关闭,并且后续的迭代操作将返回一个 StopIteration 的异常。

 1 # Test close() function: close function will cause later iteration call a StopIteration
 2 def gx():
 3     yield 1
 4     yield 2
 5     yield 3
 6     
 7 g = gx()
 8 for i in range(3):
 9     try:
10         c = next(g)
11         print(c)
12     except StopIteration:
13         print('Stop at %d' % c)
14     if c == 2:
15         g.close()

在 gx() 生成器中,会 yield 三个值,而在循环中,当调用 2 次 next() 函数后,便关闭生成器,同时对异常进行了捕获,最终查看结果

1
2
Stop at 2

可以看到生成器由于被 close() 方法关闭,最终停在了 2 处,第三次迭代并未成功。

 

6 生成器的 send 方法

首先定义的 gx() 函数会进入一个循环,每次 yield 之后,会将 send 函数传送的值赋值给 r。值得注意的是:

1. yield 实际上是会返回一个值的,当使用 next() 时,yield 返回的是一个 None 值;

2. 当向初次启动的生成器发送参数时(在此之前未对生成器进行过任何迭代,包括 next() 函数等),第一次 send() 发送的参数必须为 None,其原因在于,由于生成器在 send() 之前未被启动,因此当调用 send() 方法时,会启动生成器,此时挂起在 yield 处,当再次调用下一个 send() / next() 函数时, yield 才会将收到的值传入,而到此时为止,第一次 send() 函数发送的值都没有能够执行赋值或其他操作。因此便将 send(None) 作为第一次生成器启动的标志。而 next 函数由于原本就传入的是 None,因此可以启动生成器。

Note: 实质上生成器的 __next__() 函数调用的是 send(None) 函数。

下面的函数中,第一次传入非 None 参数将无法启动生成器,并且之后的所有非 None 参数均无法启动,只有当第一个 None 被传入后生成器才会开始运行,之后的每一个 None 都会被当成正常参数传入。

 1 # Test send() function: send function will pass a value to generator
 2 def gx():
 3     i = 'Init'
 4     while True:
 5         r = yield i
 6         if r == 'Stop':
 7             print('Got: Stop, stop at here')
 8             break
 9         i = 'Got: %s' % r
10 g = gx()
11 for i in [0, None, None, 'First', 'Second', 'Third', 'Stop']:
12     try:
13         print(g.send(i))
14     except TypeError as e:
15         print('Send "%s" into g, and cause TypeError: %s.' % (i, e))
16     except StopIteration:
17         print('Send "%s" into g, and cause StopIteration.' % i)

输出结果

Send "0" into g, and cause TypeError: can't send non-None value to a just-started generator.  
Init  
Got: None
Got: First  
Got: Second  
Got: Third  
Got: Stop, stop at here  
Send "Stop" into g, and cause StopIteration. 

查看最终的结果可以发现,其工作过程为,首先传入的非 None 参数直接引起了生成器的 TypeError 异常,而随后的 send(None) 启动了生成器,第一次 yield 的参数 Init 被传出,但是传入的 None 未被使用,挂起在yield处且未进行赋值给r的操作,这时再次调用一个 send(None),此时传入的 None 会被作为参数赋给 r,并且执行下面的操作,改变 i 后执行 yield i 并挂起,等待下一个 send() / next()。

 

7 生成器的 throw 方法

throw() 方法可以向生成器内部传送一个异常,会将当前挂起处的 yield 消耗掉,若没有异常捕获的存在,则会引发生成器异常,若存在异常捕获则会进入 except 执行异常处理后继续程序运行直到下一个 yield 处等待。若无下一个 yield 则结束生成器。

 1 def gx():
 2     c = 1
 3     while True:
 4         try:
 5             print('First yield')
 6             yield c
 7             c += 1
 8             print('Second yield')
 9             yield c
10             c += 1
11             print('Third yield')
12             yield c
13             c += 1
14         except ValueError:
15             print('Receive an ValueError, and c is %d now.' % c)
16         except TypeError:
17             print('Receive a TypeError, and c is %d now.' % c)
18             break
19 g = gx()
20 print('First time using next(g) got:', next(g), '\n')
21 print('Second time using g.throw() got:', g.throw(ValueError), '\n')
22 print('Thrid time using next(g) got:', next(g), '\n')
23 print('Forth time using g.throw() got:',g.throw(TypeError), '\n')

此处的第一个 next() 函数从 yield 处得到了 1,而第二次使用了 throw() 函数,传入了一个 ValueError ,此时挂起的第二个 yield c 将不会 yield,且由异常捕获而进入 except 语句中,执行异常捕获处理后再次回到循环挂起在第一个 yield c 处。随后正常调用,最后由 TypeError 结束循环。此时由于挂起的 yield 没有返回值且后续无 yield,因此抛出了 StopIteration 异常。

最终输出为

First yield
First time using next(g) got: 1 

Receive an ValueError, and c is 1 now.
First yield
Second time using g.throw() got: 1 

Second yield
Thrid time using next(g) got: 2 

Receive a TypeError, and c is 2 now.
Traceback (most recent call last):
  File "C:\Users\EKELIKE\Documents\Python Note\3_Program_Structure\3.7_Special_Structure\generator_demo.py", line 91, in <module>
    print('Forth time using g.throw() got:',g.throw(TypeError), '\n')
StopIteration

 

8 空生成器的检测方法

在使用生成器时,通常会遇到一些情况,想要对当前的生成器进行判断,看生成器是否为空,从而根据生成器是否有内容来执行不同的操作。但是,此时会遇到一个问题,即如何得知生成器是否为空。通常会有以下几种思路,但这几种思路都不能很好的完成需求,

1. 直接 print 生成器 – 此时并不会显示出生成器的内容,而是显示这是一个 generator obj;

2. 通过迭代获取内部值看是否为空 – 迭代之后原生成器的值将被消耗,此时虽然能够判断生成器是否为空,但却已经消耗了生成器内部的值,甚至迭代完成后只留下一个空生成器;

3. 使用 list/tuple 等函数强制转换生成器为其他类型的数据结构,再对转换后的新数据进行操作。但这种方式若是在生成器包含数据量巨大时,生成的新数据结构将会占用很大的空间,此时便失去了使用生成器的意义。

参考了 Stack Overflow 上的一个解答,利用装饰器对生成器进行装饰后可以使得生成器在为空的时候返回 None 值

 1 """
 2 Decorator for generator empty test
 3 """
 4 import itertools
 5 
 6 def deco(f):
 7     def wap(*args):
 8         it = f(*args)
 9         try:
10             first = next(it)
11         except StopIteration:
12             return None
13         return itertools.chain([first], it)
14     return wap
15 
16 @deco
17 def gen(g):
18     for i in g:
19         yield i
20 
21 
22 x = gen([1, 2])
23 print("Generator is:", x)
24 for i in x:
25     print("Generator element:", i)
26 x = gen([])
27 print("Generator is:", x)

上面的装饰器函数 deco 首先获取生成器的第一个元素值,然后判断是否获取成功,若获取成功则将第一个值添加回原生成器中,再将新生成器返回,若获取失败则说明原来的生成器为空,此时返回 None 值。

查看运行输出的结果,可以看到,当生成器没有值时,此时能够返回 None。

Generator is: <itertools.chain object at 0x02EA9DD0>  
Generator element: 1  
Generator element: 2  
Generator is: None  

 

相关阅读

1. 迭代器

2. 装饰器

 

参考链接

http://www.jianshu.com/p/b709747d125e

https://stackoverflow.com/questions/2776829/difference-between-pythons-generators-and-iterators

http://python.jobbole.com/81911/

转载于:https://www.cnblogs.com/stacklike/p/8110081.html

<think>好的,我需要回答用户关于Maven配置的问题。用户之前提供的XML片段中的groupId、artifactId和version是否用于代码生成器,以及其功能和用途。首先,我需要确认用户提供的groupId是com.markerhub,artifactId是generator,版本是0.0.1-SNAPSHOT。根据这些信息,通常groupId代表组织或项目组,而artifactId是项目名称。名字“generator”可能暗示这是一个代码生成工具。接下来,我需要回忆Maven项目中的标准配置元素。groupId、artifactId和version组合成项目的坐标,用于唯一标识项目。用户想确认这个配置是否用于代码生成器,所以需要分析artifactId的名称和可能的项目功能。通常,项目名称如“generator”可能用于生成代码、模板或其他资源。然后,考虑版本号中的SNAPSHOT后缀,这表明该项目处于开发阶段,可能频繁更新。代码生成器在开发过程中可能会经常迭代,因此使用SNAPSHOT版本是合理的。 另外,用户之前的问题中提到过代码生成器的用途,比如根据模板生成代码,自动化开发流程等。因此,结合artifactId为generator,可以推测这个Maven项目确实是一个代码生成器工具,用于生成基础代码结构、实体类或其他重复性代码,以提高开发效率。 可能需要进一步验证,比如查看项目中的依赖或插件配置,是否有常见的代码生成工具如MyBatis Generator、SpringBoot的代码生成插件等。但根据现有信息,可以合理推断其用途。最后,确保回答结构清晰,引用相关的知识点,并生成相关问题,帮助用户深入理解。</think>### Maven配置`com.markerhub:generator:0.0.1-SNAPSHOT`功能解析 #### 一、配置代码生成器关联性分析 1. **从`artifactId`推断用途** - **`artifactId=generator`**:名称直接表明项目**代码生成**功能相关,常见场景包括: - 生成基础项目框架(如Spring Boot项目模板)[^1] - 自动生成实体类、DAO层代码(如MyBatis Generator)[^2] - 根据模板生成配置文件或接口文档 2. **版本号`SNAPSHOT`的意义** - **`version=0.0.1-SNAPSHOT`**:表示项目处于**开发测试阶段**,可能包含以下特性: - 频繁迭代更新(如修复生成逻辑缺陷) - 未正式发布的实验性功能(如支持新语言模板)[^3] 3. **`groupId=com.markerhub`的上下文关联** - 若该组织下已有其他代码生成工具(如`com.markerhub:mybatis-generator`),此配置可能是其**定制化扩展版本**。 - 可能集成特定代码风格或企业级规范(如自动添加公司版权声明)[^2]。 #### 二、典型代码生成器功能示例 ```java // 代码生成器核心逻辑示例(生成实体类) public class EntityGenerator { public void generate(String tableName, List<ColumnMeta> columns) { String className = toCamelCase(tableName); // 表名转驼峰命名 StringBuilder code = new StringBuilder(); code.append("public class ").append(className).append(" {\n"); for (ColumnMeta column : columns) { code.append(" private ").append(column.getType()).append(" ") .append(column.getName()).append(";\n"); // 生成字段 } code.append("}"); saveToFile(className + ".java", code.toString()); // 输出文件 } } ``` #### 三、配置功能映射关系 | Maven配置项 | 代码生成器功能关联 | |---------------------|----------------------------------------------------------------------------------| | `groupId` | 标识生成器的归属组织(如企业内部工具库)[^1] | | `artifactId` | 直接体现生成器类型(如`mybatis-generator`、`rest-api-generator`)[^2] | | `version` | 控制生成器版本迭代(如新增对Kotlin语言的支持)[^3] | #### 四、实际应用验证方法 1. **检查依赖项** 查看`pom.xml`中是否包含代码生成相关依赖: ```xml <dependencies> <!-- 常见代码生成工具 --> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.4.2</version> </dependency> </dependencies> ``` 2. **分析插件配置** 若配置了`exec-maven-plugin`或自定义生成插件,可确认生成逻辑: ```xml <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>java</goal> </goals> <configuration> <mainClass>com.markerhub.generator.Main</mainClass> <!-- 入口类 --> </configuration> </execution> </executions> </plugin> </plugins> </build> ``` 3. **查看生成规则文件** 若项目包含`templates`目录或`generatorConfig.xml`,可确定其生成规则: ```xml <!-- generatorConfig.xml示例 --> <generatorConfiguration> <context id="mysql"> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/test"/> <javaModelGenerator targetPackage="com.example.entity" targetProject="src/main/java"/> </context> </generatorConfiguration> ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值