yield 英 [jiːld] 美 [jiːld] v.出产(作物);产生(收益、效益等);提供;屈服;让步;放弃;缴出 n.产量;产出;利润 上面路牌是「让」的意思
迭代器是什么?学习过C/C++的朋友可能熟悉,是在STL中用来对特定的容器进行访问,能够遍历STL容器中的元素。
迭代器提供一些基本操作符:*、++、==|!+、=等,这些操作和C/C++操作array元素时的指针接口基本一致,不同的是迭代器具有遍历复杂数据结构的能力,即所谓的智能指针。
比如对列表和元组做for...in
遍历操作时,Python实际上时通过列表和元组的迭代对象来实现的,而不是列表和元组本身:
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则抛出异常表示迭代结束。
当类线性遍历操作时,可以通过迭代器的__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
提供出来然后等待,而不是陷入无限循环。
从上看出yield
表达式的功能可以分成两点:
迭代器VS生成器:
都是用户通过next()
方法来获取数据,不过迭代器是通过自己实现的next()
方法来逐步返回数据,而生成器是使用yield
自动提供数据并让程序暂停wait状态,等待用户进一步操作。即生成器更加灵活方便。
一、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
异常。
三、send(msg)方法
执行一个send(msg)
会恢复生成器的运行,然后发送的值msg
将成为当前yield
表达式的返回值。程序恢复运行之后,会继续执行下面的代码,也是一直执行到下一个yield
代码,如果没有下一个则抛出StopIteration
异常。
当使用send(msg)
发送消息给生成器时,wait_and_get会检测到这个新型,然后唤醒生成器,同时该方法获取msg
并复制给x,如下代码所示:
上述函数等价于:
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
异常来关闭生成器的,其实现相当于如下代码:
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()
方法的对象,序列类型则保存了所有的数据项,通过索引来进行访问。
比如求闰年:
二、线性遍历 生成器可以将非线性话的处理转换为线性的方式,典型的例子就是对二叉树的访问。 传统做法是用递归:
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从语法层次上支持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系列博客持续更新中