前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python迭代器、生成器和修饰器-你会用yield吗?

Python迭代器、生成器和修饰器-你会用yield吗?

作者头像
唔仄lo咚锵
发布2021-09-14 11:18:48
5010
发布2021-09-14 11:18:48
举报
文章被收录于专栏:blog(为什么会重名,真的醉了)

文章目录

在这里插入图片描述
在这里插入图片描述

yield 英 [jiːld] 美 [jiːld] v.出产(作物);产生(收益、效益等);提供;屈服;让步;放弃;缴出 n.产量;产出;利润 上面路牌是「让」的意思

在这里插入图片描述
在这里插入图片描述

迭代器

概念


迭代器是什么?学习过C/C++的朋友可能熟悉,是在STL中用来对特定的容器进行访问,能够遍历STL容器中的元素。

迭代器提供一些基本操作符:*、++、==|!+、=等,这些操作和C/C++操作array元素时的指针接口基本一致,不同的是迭代器具有遍历复杂数据结构的能力,即所谓的智能指针。

比如对列表和元组做for...in遍历操作时,Python实际上时通过列表和元组的迭代对象来实现的,而不是列表和元组本身:

在这里插入图片描述
在这里插入图片描述

Python中,迭代器还拥有迭代用户自定义类的能力。迭代器对象需要支持__iter__()next()两个方法,前者返回迭代器本身,后者返回下一个元素。

迭代自定义类举例:

代码语言:javascript
复制
class example(object):
    def __init__(self,num):
        self.num=num
    def __iter__(self):
        return self
    def __next__(self):
        if self.num <= 0:
            raise StopIteration
        tmp = self.num
        self.num -= 1
        return tmp
    
a = example(3)
print(a.__next__())
print(a.__next__())
print(a.__next__())
print(a.__next__())

类example是一个迭代对象,每次执行next()操作时会判断self.num属性,如果<=0则抛出异常表示迭代结束。

在这里插入图片描述
在这里插入图片描述

生成器

概念


当类线性遍历操作时,可以通过迭代器的__iter()____next__()方法来实现,但是不够灵活方便,比如对函数来说没有属性变量来存放状态,即不支持函数的线性遍历。

那这怎么解决?Python3.X支持使用yield生成器的方法来进行线性遍历。yield语句仅用于定义生成器函数,且只能出现在生成器函数内,当生成器函数被调用时返回一个生成器。

那生成器又是什么?生成器的概念源于协同工作的程序。比如消费者和生产者模型,Python生成器就是其中生产者的角色(数据提供者),每次生成器程序就等在那里,一旦消费者/用户调用next()方法,生成就继续执行下一步,然后把当前遇到的内部数据的Node放到下一个消费者用户能够看到的公用缓冲区里,然后停下来等待wait,最后消费者/用户从缓冲区里获得Node

例如实现一个递增的生成器:

代码语言:javascript
复制
def add():
    num = 0
    while True:
        yield num
        num += 1

a = add()
print(next(a))
print(next(a))
print(next(a))

定义生成器函数add(),只有在用户调用next()方法时,才将内部数据的Node提供出来然后等待,而不是陷入无限循环。

在这里插入图片描述
在这里插入图片描述

从上看出yield表达式的功能可以分成两点:

  1. 返回数据num
  2. 进入wait_and_get状态,可以理解为程序在这个位置暂停,当消费者再次调用`next()方法时,程序才会这个位置激活。

迭代器VS生成器: 都是用户通过next()方法来获取数据,不过迭代器是通过自己实现的next()方法来逐步返回数据,而生成器是使用yield自动提供数据并让程序暂停wait状态,等待用户进一步操作。即生成器更加灵活方便。

yield语法


一、yield是表达式 在Python3.X中,yield成为表达式,不再是语句。但是必须放在函数内部,如果写成语句的形式会报错(实际上返回值被扔掉了),例如:

代码语言:javascript
复制
yield n
x=yield n

既然yield是表达式,所以可以和其他表达式组合使用,例如:

代码语言:javascript
复制
x=y+z*(yield 2)
a=b+c+yield d

二、next()方法 当用户调用next()方法执行到yield表达式时,先返回n,然后程序进入wait状态,只有当下一次执行next()方法时才会从此处恢复并继续执行下面的代码,一直执行到下一个yield表达式。如果没有下一个yield代码,就抛出StopIteration异常。

在这里插入图片描述
在这里插入图片描述

三、send(msg)方法 执行一个send(msg)会恢复生成器的运行,然后发送的值msg将成为当前yield表达式的返回值。程序恢复运行之后,会继续执行下面的代码,也是一直执行到下一个yield代码,如果没有下一个则抛出StopIteration异常。 当使用send(msg)发送消息给生成器时,wait_and_get会检测到这个新型,然后唤醒生成器,同时该方法获取msg并复制给x,如下代码所示:

在这里插入图片描述
在这里插入图片描述

上述函数等价于:

代码语言:javascript
复制
def test():
    print('记得一键三连')
    put(1)
    x = wait_and_get()
    print('x=', x)
    put(2)
    y = wait_and_get()
    print('y=', y)
    print('下面无yield了,会抛Stop异常')

当第一次调用next()方法执行到第一个wait_and_get处时生成器进入wait状态并打印‘记得一键三连’和’1’; 接着调用send(666),从第一个wait_and_get处启动生成器,并把参数666赋值给变量x,然后继续执行到第二个wait_and_get处,生成器又进入wait状态; 接着调用send(888),生成器从第二个wait_and_get处启动,并把参数888赋值给变量y,后面因为没有yield表达式了,所以生成器抛出StopIteration异常。

特别注意的是,第一个调用要么使用next(),要么使用send(None),不能使用send()来发送一个非None值,原因是非None值是发给wait_and_get的。一开始程序并没有停在wait_and_get代码处,只有先使用next()或者send(None)方法后才会停止wait_and_get处,这时才能使用send发送一个非None值。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、throw()方法 生成器提供throw()方法从生成器内部来引发异常,从而控制生成器的执行。

在这里插入图片描述
在这里插入图片描述

GeneratorExit的作用是让生成器有机会执行一些退出时的清理工作。

五、关闭生成器 生成器提供了一个close()方法来关闭生成器。当使用close()方法时,生成器会直接从当前状态退出,再使用next()时会得到StopIteration异常。

在这里插入图片描述
在这里插入图片描述

实际上,close()方法也是通过throw()方法来发送GenerationExit异常来关闭生成器的,其实现相当于如下代码:

代码语言:javascript
复制
def close(self):
    try:
        self.throw(GeneratorExit)
    except(GeneratorExit,StopIteration):
        pass
    else:
        raise RuntimeError("generator ignored GeneratorExit")

插播反爬信息 )博主CSDN地址:https://wzlodq.blog.csdn.net/

用途


一、节省内存 相对于列表、元组,生成器更节省内存。生成器一次产生一个数据项,直到没有为止,在for循环中可以对它进行循环处理,占用内存更少。但是需要记住当前的状态,以便返回下一个数据项。生成器是一个有next()方法的对象,序列类型则保存了所有的数据项,通过索引来进行访问。

比如求闰年:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、线性遍历 生成器可以将非线性话的处理转换为线性的方式,典型的例子就是对二叉树的访问。 传统做法是用递归:

代码语言:javascript
复制
def deal_tree(node):
    if not node:
        return
    if node.leftnode:
        deal_tree(node.leftnode)
    process(node)
    if node.rightnode:
        deal_tree(node.rightnode)

递归做法中为了处理每一个节点,需要将处理方法process()放到访问的过程中,极容易出错,也不清晰。比较好的方法是先将树的节点访问转换成线性,用生成器实现:

代码语言:javascript
复制
def deal_tree(node):
    if not node:
        return
    if node.leftnode:
        for i in walk_tree(node.leftnode):
            yield i
    yield node
    if node.rightnode:
        for i in walk_tree(node.rightnode):
            yield i

修饰器

修饰器模式


修饰器是用于扩展原来函数功能的一种函数,这个函数的特殊之处在于返回值是一个函数。

修饰器(Decorator)的概念来自于设计模式,也叫装饰者模式。具体细节可见设计模式系列博客,举个例子,如游戏英雄的战力提升,可以通过升级装备、加技能、使用道具等等,实现时自然不可能把每一种组合的情况都创建出来以备调用。修饰器则发挥作用了:升级装备时使用升级装备的修饰器;使用道具时用道具的修饰器。目的是为了运行时动态的改变对象的状态而不是编译期,使用组合的方式来增减Decorator而不是修改原有的代码来满足业务的需要,以利于程序的扩展。

修饰器模式是针对Java语言的,为了灵活使用组合的方式来增减Decorator,Java语言需要使用较为复杂的类对象结构才能达到效果。Python从语法层次上实现了使用组合的方式来增减Decorator的功能。

Python修饰器


Python从语法层次上支持Decorator模式的灵活调用,主要有以下两种方式: 一、不带参数的Decorator 语法形式如下:

代码语言:javascript
复制
@A
def f():

相当于在函数f上加一个修饰器A,Python会处理为f = A(f)。 修饰器的添加时不受限制的,可以多层次使用:

代码语言:javascript
复制
@A
@B
@C
def f():

Python会处理为f=A(B(C(f)))

二、带参数的Decorator 语法形式如下:

代码语言:javascript
复制
@A(args)
def f():

Python会先执行A(args)得到一个decorator()函数,处理为:

代码语言:javascript
复制
def f():...
_deco = A(args)
f = _deco(f)

定义


每一个Decorator都对应有相应的函数,要对后面的函数进行处理,要么返回原来的函数对象,要么返回一个新的函数对象。特别注意Decorator只能用来处理函数和类方法。

根据上述修饰器两种调用方法,修饰器函数定义也对应两种方法: 一、不带参数 对func处理后返回原函数对象,语法形式如下:

代码语言:javascript
复制
def A(func):
	#处理func
	return func
	
@A
def f(args):pass

返回一个新的函数对象,注意neew_func的定义形式要与待处理函数相同(可以用不定参数),语法形式如下:

代码语言:javascript
复制
def A(func):
	def new_func(*args,**argkw):
		#做一些额外工作
		return func(*args,**argkw) #调用原函数继续进行处理
	return new_func
	
@A
def f(args):pass

上述代码在A中定义了新的函数,然后A返回这个新函数。在新函数中可以先处理一些事情再调用原始函数进行处理,如果想在调用函数之后再进一步处理,可以通过函数返回值来实现:

代码语言:javascript
复制
def A(args):
	def new_func(*args,**argkw):
		#一些调用前处理工作
		result = func(*args,**argkw)
		if result:
			#进一步调用后工作
			return new_result
		else:
			return result
	return new_func

@A
def f(args):pass

二、带参数 因为decorator()函数使用了参数进行调用,所以要返回一个新的decorator()函数,这样就与第一种形式一致了。

代码语言:javascript
复制
def A(arg):
	def _A(func):
		def new_func(args):
			#做一些工作
			return func(args)
		return new_func
	return _A

@A(arg)
def f(args):pass

应用


使用Decorator可以增加程序的灵活性,减少耦合度,适合用于用户登录检查、日志处理等。这种通过函数之间相互结合的方式更符合搭积木的要求,可以把函数功能进一步分解,使得功能足够简单单一,然后再通过Decorator的机制灵活把函数串起来。

应用举例:一个多用户使用的程序会有很多功能和权限相关,传统方法是建立权限角色类,然后每个用户继承权限角色,但这种方法不但容易出错,而且对管理、修改也很麻烦。采用Decorator来处理,通过decorator()函数的调用来处理用户权限的逻辑,可以先定义权限管理的类:

代码语言:javascript
复制
class Permission:
    #普通用户
    def userPermission(self,function):
        def new_func(*args,**kwargs):
            obj = args[0]
            if obj.user.hasUserPermission(): #判断拥有对应权限
                ret = function(*args,**kwargs)
            else:
                ret = 'No User Permission'
            return ret
        return new_func
    #管理员
    def adminPermission(self,function):
        def new_func(*args,**kwargs):
            obj = args[0]
            if obj.user.hasAdminpermission(): #判断拥有对应权限
                ret = function(*args,**kwargs)
            else:
                ret ='No Admin Permission'
            return ret
        return new_func

然后处理实际业务代码是,只要为需要的功能加上实际权限限制Decorator就可以了:

代码语言:javascript
复制
class Action:
    def __init__(self,name):
        self.user = UserService.newUser(name)
    @Permission.userPermission #需要用户权限
    def listAllpoints(self):
        return 'do real list all points'
    @Permission.adminPermission #需要管理员权限
    def setup(self):
        return 'do real setup'

最后就是业务方法的调用了:

代码语言:javascript
复制
if __name__ == "main":
    action = Action('user')
    print(action.listAllpoints())#执行真正的业务代码
    print(action.setup)

相对于传统方法,Decorator使用起来优势很大,可以将用户权限检查这些琐碎的工作和业务调用代码相剥离,并且能够检测函数方便地修饰到业务逻辑代码之上。 Python系列博客持续更新中

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/02/24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 迭代器
    • 概念
    • 生成器
      • 概念
        • yield语法
          • 用途
          • 修饰器
            • 修饰器模式
              • Python修饰器
                • 定义
                  • 应用
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档