python生成器有几种写法,python生成器函数例子

news2024/12/23 3:18:02

大家好,小编来为大家解答以下问题,python生成器有几种写法,python生成器函数例子,今天让我们一起来看看吧!

本文部分参考:Python迭代器,生成器–精华中的精华 https://www.cnblogs.com/deeper/p/7565571.html

一 迭代器和可迭代对象

迭代器是访问集合元素的一种方式。火车头采集器AI伪原创。迭代器只能往前不会后退。迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素,仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件。

特点:

a)访问者不需要关心迭代器内部的结构,仅需通过next()方法或不断去取下一个内容

b)不能随机访问集合中的某个值 ,只能从头到尾依次访问

c)访问到一半时不能往回退

d)便于循环比较大的数据集合,节省内存

e)也不能复制一个迭代器。如果要再次(或者同时)迭代同一个对象,只能去创建另一个迭代器对象。

enumerate()的返回值就是一个迭代器,我们以enumerate为例:

a = enumerate(['a','b'])

for i in range(2):    #迭代两次enumerate对象
    for x, y in a:
        print(x,y)
    print(''.center(50,'-'))

结果:

0 a
1 b
-----------------------分割线------------------------
-----------------------分割线------------------------

可以看到再次迭代enumerate对象时,没有返回值。

我们可以用linux的文件处理命令vim和cat来理解一下:

a) 读取很大的文件时,vim需要很久,cat是毫秒级;因为vim是一次性把文件全部加载到内存中读取;而cat是加载一行显示一行

b) vim读写文件时可以前进,后退,可以跳转到任意一行;而cat只能向下翻页,不能倒退,不能直接跳转到文件的某一页(因为读取的时候这个“某一页“可能还没有加载到内存中)。

正式进入python迭代器之前,我们先要区分两个容易混淆的概念:可迭代对象(Iterable)和迭代器(Iterator)

1.1可迭代对象

定义:迭代器是一个对象,不是一个函数。只要它定义了可以返回一个迭代器的__iter__方法,或者定义了可以支持下标索引的__getitem__方法,那么它就是一个可迭代对象。

注意: 集合数据类型,如list、tuple、dict、set、str都是可迭代对象Iterable,却不是迭代器Iterator。

如何判断一个对象是可迭代对象呢?可以通过collections模块的Iterable类型判断:

from collections import Iterable
print(isinstance([], Iterable))
print(isinstance({}, Iterable))
print(isinstance('abc', Iterable))
print(isinstance({'name':'join','age':23},Iterable))
print(isinstance(set([2,3]),Iterable))

结果为:

True
True
True
True
True

再看:

from collections import Iterator
print(isinstance([], Iterator))
print(isinstance({}, Iterator))
print(isinstance('abc', Iterator))
print(isinstance({'name':'join','age':23},Iterator))
print(isinstance(set([2,3]),Iterator))

结果为:

False
False
False
False
False

1.2迭代器

定义:任何实现了__iter__()和__next__()(python2中实现next())方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值。

这里我们来看一下迭代器和可迭代对象的使用方法。

问题:不适用for,while以及下表索引,怎么遍历一个list?

l1=[2,3,5]
iter_l1 = l1.__iter__() 
# 或者iter(iter_l1)
# iter(iter_l1)是python的内置函数,
# l1.__iter__()调用的是l1对象的__iter__()方法。
# 下面的next()函数和__iter__()函数类似。
print(next(iter_l1)) # 或print(iter_l1.__next__()
print(next(iter_l1))
print(next(iter_l1))
print(next(iter_l1))

结果为:

2
3
5
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-126-eb58865cb644> in <module>
      4 print(next(iter_l1))
      5 print(next(iter_l1))
----> 6 print(next(iter_l1))

结论:思路就是,先得到当前对象(当前对象是可迭代对象)的迭代器,然后每次执行next(iter_l1)就可以得到当前对象的一个值。

修改一下:

l1=[2,3,5]
iter_l1 = l1.__iter__() 
for i in iter_l1:
    print(i,end=',')

结果为:

2,3,5,

再次执行for语句如下:

for i in iter_l1:
    print(i,end=',')

此次返回结果为空,可以看到迭代器只能遍历一次对象,不能重复遍历。由于for语句可以自动处理StopIteration异常,所以这里没有报出StopIteration,而是没有任何结果。

看一个例子。我们想得到Fibonacci数列,思路是定义一个可迭代对象类Fib;然后在定义一个迭代器类FibIterator,使用方式是:
① 实例化一个可迭代对象: fib = Fib()
② 得到fib的一个迭代器: fib_iter = iter(fib)
③ 然后每次调用next(fib_iter)可得到fibonacci数列中的一个数。

class FibIterator():
    '''
    定义迭代器类
    '''
    def __init__(self,num,a,b,current):
        self.num = num
        self.a = a
        self.b = b
        self.current = current
    def __iter__(self):
        return self
    def __next__(self):
        if(self.num-1>=0):
            self.num = self.num-1
            self.current = self.a
            self.a = self.b
            self.b = self.b+self.current   #以上两步赋值操作可省略中间变量直接写为self.a,self.b = self.b,self.a+self,b 
            return self.current
        else: raise StopIteration
         
class Fib:
    '''
    定义可迭代对象所属类
    '''
    def __init__(self,num): #num表示该数列的长度
        self.a = 1
        self.b = 2
        self.current=self.a
        self.num = num
    def __iter__(self):
        return FibIterator(self.num,self.a,self.b,self.current)
        
fib = Fib(20)
fib_iter = iter(fib)
for i in range(20):
    print(next(fib_iter),end=',')

结果为:

1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,

这里定义的Fibonacci类的__init__函数有一个参数num,表示一共得到几个fibonacci数。

由于可迭代对象是实现了__iter__()方法的,迭代器对象是实现了__iter__()和__next__()方法的,能否只定义一个迭代器类呢?

试一下。

class Fib:
    def __init__(self,num):
        self.num = num
        self.a = 1
        self.b = 2
        self.current = self.a
    def __iter__(self):
        return self
    def __next__(self):
        if(self.num-1>=0):
            self.num = self.num-1
            self.current = self.a
            self.a = self.b
            self.b = self.b+self.current   #以上两步赋值操作可省略中间变量直接写为self.a,self.b = self.b,self.a+self,b 
            return self.current
        else: raise StopIteration
        
fib = Fib(10)
for i in fib:
    print(i,end=',')

结果为:

1,2,3,5,8,13,21,34,55,89,

当我们再次执行for语句时,

for i in fib:
    print(i,end=',')

同上面的分析一样,结果为空。如果想再次得到fibonacci数列的前10个数,就必须重新实例化Fib对象。

结论:为什么不只保留Iterator的接口而还需要设计Iterable呢?

因为迭代器迭代一次以后就空了,那么如果list,dict也是一个迭代器,迭代一次就不能再继续被迭代了,这显然是反人类的;所以通过__iter__每次返回一个独立的迭代器,就可以保证不同的迭代过程不会互相影响。

另外,迭代器是惰性的,只有在需要返回下一个数据时它才会计算。所以,Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

下面的例子得到全体自然数(下面我们用生成器可以得到更简单的写法)。

class Natural:
    def __init__(self):
        pass
    def __iter__(self):
        return NaturalIterator()
class NaturalIterator:
    def __init__(self):
        self.beg=0
        self.current=self.beg
    def __iter__(self):
        return self
    def __next__(self):
        self.current += 1
        return self.current   

# 显示前20个自然数
n1=Natural()
n1_iter = iter(n1)
for i in range(20):
    print(next(n1_iter),end=',')

结果为:

1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,

作业:使用迭代器和可迭代对象实现得到全体fibonacci数。

二 生成器

2.1 生成器的定义和使用

定义:生成器其实是一种特殊的迭代器,它不需要再像上面的类一样写__iter__()和__next__()方法了,只需要一个yiled关键字。

生成器一定是迭代器(反之不成立)。

Python有两种不同的方式提供生成器:

  1. 生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。 yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
  2. 生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象, 而不是一次构建一个结果列表

我们先用一个例子说明一下:

def generate_even():
    i=0
    while True:
        if i%2==0:
            yield i
        i += 1

g=generate_even()
dir(g) # 可以看到里面有__iter__()方法和__next__()方法,所以生成器也是迭代器。

for i in range(10):
    print(g.__next__(),end=',')

结果为:

0,2,4,6,8,10,12,14,16,18,

现在解释一下上面的代码:

我们知道,一个函数只能返回一次,即return以后,这次函数调用就结束了;

但是生成器函数可以暂停执行,并且通过yield返回一个中间值,当生成器对象的__next__()方法再次被调用的时候,生成器函数可以从上一次暂停的地方继续执行,直到下一次遇到yield语句(此时会返回yield后面的值,如果有的话)或者触发一个StopIteration。

了解协同程序:
a) 生成器的另外一个方面是协同程序的概念。协同程序是可以运行的独立函数调用,可以暂停或者挂起,并从程序离开的地方继续或者重新开始。

b) 可以在调用者和被调用的之间协同程序通信。

c) 在程序暂停时可以传参:举例来说,当协同程序暂停时,我们仍可以从其中获得一个中间的返回值,当调用回到程序中时,能够传入额外或者改变了的参数,但是仍然能够从我们上次离开的地方继续,并且所有状态完整。

生成器表达式类似于列表推导式,只是把[]换成(),这样就创建了一个生成器。

gen = (x for x in range(10))

下面我们用生成器来实现前面的fibonacci数列和全体自然数。

# 生成前n个fibonacci数
def fib(n):
    a, b = 0, 1
    count=0
    while True:
        if(count>n):
            break
        count += 1
        yield b
        a, b = b, a+b

f = fib(20)
for item in f:
    print(item,end=',')

结果为:

1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,
def Natural():
    n=0
    while True:
        yield n
        n += 1
g_n1=Natural()
for i in range(20):
    print(next(g_n1),end=',')

结果为:

0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,

2.2 send方法

生成器函数最大的特点是可以接受外部传入的一个变量,并根据变量内容计算结果后返回。这是生成器函数最难理解的地方,也是最重要的地方,协程的实现就全靠它了。

看一个小猫吃鱼的例子:

def cat():
    print('我是一只hello kitty')
    while True:
        food = yield
        if food == '鱼肉':
            yield '好开心'
        else:
            yield '不开心,人家要吃鱼肉啦'

中间有个赋值语句food = yield,可以通过send方法来传参数给food,试一下:

情况1)

miao = cat()    #只是用于返回一个生成器对象,cat函数不会执行
print(''.center(50,'-'))
print(miao.send('鱼肉'))

结果:

Traceback (most recent call last):
--------------------------------------------------
  File "C:/Users//Desktop/Python/cnblogs/subModule.py", line 67, in <module>
    print(miao.send('鱼肉'))
TypeError: can't send non-None value to a just-started generator

看到了两个信息:

a)miao = cat() ,只是用于返回一个生成器对象,cat函数不会执行

b)can’t send non-None value to a just-started generator;不能给一个刚创建的生成器对象直接send值

改一下,情况2)

miao = cat()
miao.__next__()
print(miao.send('鱼肉'))

那么到底send()做了什么呢?send()的帮助文档写的很清楚,’’‘Resumes the generator and “sends” a value that becomes the result of the current yield-expression.’’’;可以看到send依次做了两件事:

a)回到生成器挂起的位置,继续执行

b)并将send(arg)中的参数赋值给对应的变量,如果没有变量接收值,那么就只是回到生成器挂起的位置

但是,我认为send还做了第三件事:

c)兼顾__next__()作用,挂起程序并返回值,所以我们在print(miao.send(‘鱼肉’))时,才会看到’好开心’;其实__next__()等价于send(None)

所以当我们尝试这样做的时候:

def cat():
    print('我是一只hello kitty')
    while True:
        food = yield
        if food == '鱼肉':
            yield '好开心'
        else:
            yield '不开心,人家要吃鱼肉啦'

miao = cat()
print(miao.__next__())
print(miao.send('鱼肉'))
print(miao.send('骨头'))
print(miao.send('鸡肉'))

就会得到这个结果:

我是一只hello kitty
None
好开心
None
不开心,人家要吃鱼肉啦

我们按步骤分析一下:

a)执行到print(miao.next()),执行cat()函数,print了”我是一只hello kitty”,然后在food = yield挂起,并返回了None,打印None

b)接着执行print(miao.send(‘鱼肉’)),回到food = yield,并将’鱼肉’赋值给food,生成器函数恢复执行;直到运行到yield ‘好开心’,程序挂起,返回’好开心’,并print’好开心’

c)接着执行print(miao.send(‘骨头’)),回到yield ‘好开心’,这时没有变量接收参数’骨头’,生成器函数恢复执行;直到food = yield,程序挂起,返回None,并print None

d)接着执行print(miao.send(‘鸡肉’)),回到food = yield,并将’鸡肉’赋值给food,生成器函数恢复执行;直到运行到yield’不开心,人家要吃鱼肉啦’,程序挂起,返回’不开心,人家要吃鱼肉啦’,并print ‘不开心,人家要吃鱼肉啦’

大功告成,那我们优化一下代码:

def cat():
    msg = '我是一只hello kitty'
    while True:
        food = yield msg
        if food == '鱼肉':
            msg = '好开心'
        else:
            msg = '不开心,人家要吃鱼啦'

miao = cat()
print(miao.__next__())
print(miao.send('鱼肉'))
print(miao.send('鸡肉'))

我们再看一个更实用的例子,一个计数器。

def counter(start_at = 0):
    count = start_at
    while True:
        val = yield count
        if val is not None:
            count = val
        else:
            count += 1

count = counter(5)
print(count.__next__())
print(count.send(0))

结果为5,0而不是5,6的原因:

①执行print(count.next()),程序运行到val = yield count(第一个yield语句)后挂起,然后返回yield后面的值,所以结果为5。

②执行print(count.send(0)),程序恢复到挂起点val = yield count,将send的参数0赋值给接受变量val,然后继续执行下面的语句,由于val=0,所以if val is not None条件为真,count = val,接着又来到yield语句(val = yield count),此时程序挂起,返回yield后面的值count=0。

综上:当执行next()方法时,程序会恢复到挂起点,依次执行yield语句下面的语句,最后再返回yield后面的值;而不是先返回yield后面的值,再执行yield后面的语句。

最后给出一张图说明Iterable,Iterator,Generator的关系:
gen2

补充几个小例子:

a)使用生成器创建一个range

def range(n):
    count = 0
    while count < n:
        yield count
        count += 1

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#接化发:串口通信

文章目录 框架准备串口准备接、化、发测试 源码地址&#xff1a;C# 串口通信测试软件 框架准备 出于简单考虑&#xff0c;首先创建一个Winform项目&#xff0c;本文项目名称为portTest。 串口通信&#xff0c;至少有两个串口才能通信&#xff0c;所以拖动两个GroupBox&#…

Python 3 使用Hive 总结

启动HiveServer2 服务 HiveServer2 是一种可选的 Hive 内置服务&#xff0c;可以允许远程客户端使用不同编程语言向 Hive 提交请求并返回结果。 Thrift服务配置 假设我们已经成功安装了 Hive&#xff0c;如果没有安装&#xff0c;请参考&#xff1a;Hive 一文读懂 。在启动 H…

SAP后台表SE16和SE16N修改后台表数据方法

SAP后台表SE16和SE16N修改后台表数据方法 SAP中直接修改表、视图的Tcode有SE16N和SM30。 一 . SE16N界面输入&SAP_EDIT 其中使用SE16N修改表需要先输入命令&SAP_EDIT,回车左下角显示激活SAP编辑功能后&#xff0c;就可以对相应的表进行新增、删除、修改的操作。 有…

剑指offer44.数字序列中某一位的数字

最后一道题&#xff0c;我一定要自己做出来&#xff0c;想了不到一个小时想法差不多成熟了&#xff0c;但是有一个小细节出问题了&#xff0c;这个问题我在idea上debug都没debug出来。我先讲我的题解然后再讲我这个小问题出在哪里吧。以下是我的代码&#xff1a; class Soluti…

基于.Net Core开发的医疗信息LIS系统源码

SaaS模式.Net Core版云LIS系统源码 医疗信息LIS系统是专为医院检验科设计的一套实验室信息管理系统&#xff0c;能将实验仪器与计算机组成网络&#xff0c;使病人样品登录、实验数据存取、报告审核、打印分发&#xff0c;实验数据统计分析等繁杂的操作过程实现了智能化、自动化…

Mac m1芯片基于parallesls desktop安装Ubuntu

1.Ubuntu镜像下载 访问Ubuntu官网自行选择版本进行下载&#xff0c;可进行以下操作进行版本选择 对于Mac系统&#xff0c;不论VM/PD都需要用arm架构镜像&#xff0c;所以点击arm架构的镜像进行下载 2.准备PD PD安装包&#xff1a; 链接&#xff1a;https://pan.quark.cn/s/46…

深入了解Maven

目录 一.Maven介绍与功能 二.依赖管理 1.依赖的配置 2.依赖的传递性 3.排除依赖 4.依赖的作用范围 一.Maven介绍与功能 maven是一个项目管理和构建工具&#xff0c;是基于对象模型POM实现。 Maven的作用&#xff1a; 便捷的依赖管理&#xff1a;使用Maven可以简化依赖管…

FPGA GTP全网最细讲解,aurora 8b/10b协议,HDMI板对板视频传输,提供2组4套工程源码和技术支持

目录 1、前言免责声明 2、我这里已有的 GT 高速接口解决方案3、GTP 全网最细解读GTP 基本结构GTP 发送和接收处理流程GTP 的参考时钟GTP 发送接口GTP 接收接口GTP IP核调用和使用 4、设计思路框架HDMI输入视频配置及采集视频数据组包GTP aurora 8b/10b数据对齐视频数据解包图像…

四层和七层负载均衡的区别

一、四层负载均衡 四层就是ISO参考模型中的第四层。四层负载均衡器也称为四层交换机&#xff0c;它主要时通过分析IP层和TCP/UDP层的流量实现的基于“IP端口”的负载均衡。常见的基于四层的负载均衡器有LVS、F5等。 以常见的TCP应用为例&#xff0c;负载均衡器在接收到第一个来…

《HeadFirst设计模式(第二版)》第九章代码——组合模式

上一章链接&#xff1a; 《HeadFirst设计模式(第二版)》第九章代码——迭代器模式_轩下小酌的博客-CSDN博客 前面说到&#xff0c;当一个菜单里面出现了子菜单的时候&#xff0c;前面的迭代器模式得换成组合模式。 组合模式&#xff1a; 允许将对象组合成树形结构来表现部分-整…

PyTorch训练深度卷积生成对抗网络DCGAN

文章目录 DCGAN介绍代码结果参考 DCGAN介绍 将CNN和GAN结合起来&#xff0c;把监督学习和无监督学习结合起来。具体解释可以参见 深度卷积对抗生成网络(DCGAN) DCGAN的生成器结构&#xff1a; 图片来源&#xff1a;https://arxiv.org/abs/1511.06434 代码 model.py impor…

跨境电商平台风控揭秘:如何应对刷单风险?

跨境电商平台内部对比被举报的买家信息时&#xff0c;会进行一系列分析来确认是否存在刷评行为。系统会追溯买家的购买记录和留评记录&#xff0c;根据留评率等信息来判断是否存在刷评的行为。如果系统确认买家存在刷评行为&#xff0c;那么该买家曾经留下的所有评价都有可能被…

我国出租车行业的发展伪历史(依赖倒置)

一、前言 既然是“伪历史”&#xff0c;大家就暂且不要纠结故事的真实性了&#xff0c;因为我们今天主要讲的并非是中国出租车的发展史&#xff0c;而是希望通过这个伪历史的例子来用日常生活中的例子&#xff0c;来深入理解一下什么叫依赖倒置。 还是按照惯例&#xff0c;我…

【从零开始学习Linux】常用命令及操作

哈喽&#xff0c;哈喽&#xff0c;大家好~ 我是你们的老朋友&#xff1a;保护小周ღ 本期给大家带来的是 Linux 常用命令及操作&#xff0c;主要有三个分类&#xff1a;文件操作&#xff0c;目录操作&#xff0c;网络操作&#xff0c;创建文件 touch , 创建目录 mkdir , 删除…

【了解一下常见的设计模式】

文章目录 了解一下常用的设计模式(工厂、包装、关系)导语设计模式辨析系列 工厂篇工厂什么是工厂简单工厂「模式」&#xff08;Simple Factory「Pattern」&#xff09;简单工厂代码示例&#xff1a;简单计算器优点&#xff1a;缺点&#xff1a; 静态工厂模式特点&#xff1a; 工…

基于Spring Boot的社区诊所就医管理系统的设计与实现(Java+spring boot+MySQL)

获取源码或者论文请私信博主 演示视频&#xff1a; 基于Spring Boot的社区诊所就医管理系统的设计与实现&#xff08;Javaspring bootMySQL&#xff09; 使用技术&#xff1a; 前端&#xff1a;html css javascript jQuery ajax thymeleaf 微信小程序 后端&#xff1a;Java …

改进YOLO系列:2.添加ShuffleAttention注意力机制

添加ShuffleAttention注意力机制 1. ShuffleAttention注意力机制论文2. ShuffleAttention注意力机制原理3. ShuffleAttention注意力机制的配置3.1common.py配置3.2yolo.py配置3.3yaml文件配置1. ShuffleAttention注意力机制论文 论文题目:SA-NET: SHUFFLE ATTENTION …

教育行业选择CRM的四大要求

随着互联网教育的发展和变迁&#xff0c;越来越多的教育机构开始意识到管理客户关系的重要性。然而&#xff0c;对于教育行业来说&#xff0c;选择一款适合自己的CRM系统也不轻松。下面就来说说&#xff0c;教育行业crm要如何来选择&#xff1f; 一、明确使用需求 在进行CRM选…

如何使用 ChatGPT 将文本转换为 PowerPoint 演示文稿

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可二次编辑的3D应用场景 步骤 1&#xff1a;将文本转换为幻灯片演示文稿 第一步涉及指示 ChatGPT 根据给定的文本生成具有特定数量幻灯片的演示文稿。首先&#xff0c;您必须向 ChatGPT 提供要转换的文本。 使用以下提示指示…

Gitlab服务部署及应用

目录 Gitlab简介 Gitlab工作原理 Gitlab服务构成 Gitlab环境部署 安装依赖包 启动postfix&#xff0c;并设置开机自启 设置防火墙 下载安装gitlab rpm包 修改配置文件/etc/gitlab/gitlab.rb&#xff0c;生产环境下可以根据需求修改 重新加载配置文件 浏览器登录Gitlab输…