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

时间:2021-2-26 作者:admin

文章目录

Python迭代器、生成器和修饰器-你会用yield吗?
yield 英 [jiːld] 美 [jiːld]
v.出产(作物);产生(收益、效益等);提供;屈服;让步;放弃;缴出
n.产量;产出;利润
上面路牌是「让」的意思
Python迭代器、生成器和修饰器-你会用yield吗?

迭代器

概念


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

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

比如对列表和元组做for...in遍历操作时,Python实际上时通过列表和元组的迭代对象来实现的,而不是列表和元组本身:
Python迭代器、生成器和修饰器-你会用yield吗?

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

迭代自定义类举例:

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则抛出异常表示迭代结束。
Python迭代器、生成器和修饰器-你会用yield吗?

生成器

概念


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

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

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

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

def add():
    num = 0
    while True:
        yield num
        num += 1

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

定义生成器函数add(),只有在用户调用next()方法时,才将内部数据的Node提供出来然后等待,而不是陷入无限循环。
Python迭代器、生成器和修饰器-你会用yield吗?
从上看出yield表达式的功能可以分成两点:

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

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

yield语法


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

yield n
x=yield n

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

x=y+z*(yield 2)
a=b+c+yield d

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

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

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值。
Python迭代器、生成器和修饰器-你会用yield吗?
Python迭代器、生成器和修饰器-你会用yield吗?

四、throw()方法
生成器提供throw()方法从生成器内部来引发异常,从而控制生成器的执行。
Python迭代器、生成器和修饰器-你会用yield吗?
GeneratorExit的作用是让生成器有机会执行一些退出时的清理工作。

五、关闭生成器
生成器提供了一个close()方法来关闭生成器。当使用close()方法时,生成器会直接从当前状态退出,再使用next()时会得到StopIteration异常。
Python迭代器、生成器和修饰器-你会用yield吗?
实际上,close()方法也是通过throw()方法来发送GenerationExit异常来关闭生成器的,其实现相当于如下代码:

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()方法的对象,序列类型则保存了所有的数据项,通过索引来进行访问。

比如求闰年:
Python迭代器、生成器和修饰器-你会用yield吗?
Python迭代器、生成器和修饰器-你会用yield吗?

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

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()放到访问的过程中,极容易出错,也不清晰。比较好的方法是先将树的节点访问转换成线性,用生成器实现:

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
语法形式如下:

@A
def f():

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

@A
@B
@C
def f():

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

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

@A(args)
def f():

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

def f():...
_deco = A(args)
f = _deco(f)

定义


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

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

def A(func):
	#处理func
	return func
	
@A
def f(args):pass

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

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

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

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()函数,这样就与第一种形式一致了。

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()函数的调用来处理用户权限的逻辑,可以先定义权限管理的类:

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就可以了:

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'

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

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

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

原创不易,请勿转载本不富裕的访问量雪上加霜
博主首页:https://wzlodq.blog.csdn.net/
微信公众号:唔仄lo咚锵
如果文章对你有帮助,记得一键三连❤

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。