06-装饰器与迭代器

一、装饰器

  • 参考资料:https://zhuanlan.zhihu.com/p/109078881

1.1、什么是装饰器

  • “装饰”代指为被装饰对象添加新的功能,“器”代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。
  • 装饰器的作用就是在不修改被装饰对象源代码调用方式的前提下为被装饰对象添加额外的功能。
  • 软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。
    • 对扩展开放:意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
    • 对修改封闭:意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。

 

1.2、为何要用装饰器

  • 在不修改被装饰对象源代码调用方式的前提下为被装饰对象添加额外的功能。

1.3、装饰器的实现

函数装饰器分为:无参装饰器和有参装饰两种,二者的实现原理一样,都是’函数嵌套+闭包+函数对象’的组合使用的产物

无参装饰器

  • 需求:在不修改源代码及调用方式的基础上为其添加统计运行时间的新功能
  • 源代码
import time

def index():
    time.sleep(3)
    print('Welcome to the index page’)
    return 200

index() #函数执行
  • 方案一:没有修改被装饰对象的调用方式,但是修改了源代码
import time

def index(x,y):
    start_time = time.time()
    time.sleep(3)
    print('index %s %s' % (x, y))
    stop_time = time.time()
    print('run time is %s' % (stop_time - start_time))

index(111,222) #函数执行
  • 方案二:在遵循不修改被装饰对象源代码的原则,做如下统计逻辑【假设多个地方调用,产生代码冗余
start_time=time.time()
index(111,222) #函数执行
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))

start_time=time.time()
index(111,222) #函数执行
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))


start_time=time.time()
index(111,222) #函数执行
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
  • 方案三:考虑到还有可能要统计其他函数的执行时间,于是我们将其做成一个单独的工具,函数体需要外部传入被装饰的函数从而进行调用,我们可以使用参数的形式传入【解决了代码冗余问题,但修改了调用方式
def wrapper(func): # 通过参数接收外部的值
    start_time=time.time()
    res=func()
    stop_time=time.time()
    print('run time is %s' %(stop_time-start_time))
    return res

#但之后函数的调用方式都需要统一改成
wrapper(index)
wrapper(其他函数)
  • 这违反了不能修改被装饰对象调用方式的原则,于是我们换一种为函数体传值的方式,即将值包给函数,如下
  • 方案三的优化一:防止index被写死
def timer(func):
    def wrapper(): # 引用外部作用域的变量func
        start_time=time.time()
        res=func()
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper
  • 这样我们便可以在不修改被装饰函数源代码和调用方式的前提下为其加上统计时间的功能,只不过需要事先执行一次timer将被装饰的函数传入,返回一个闭包函数wrapper重新赋值给变量名 /函数名index,如下
index=timer(index)  #得到index=wrapper,wrapper携带对外作用域的引用:func=原始的index
index() # 执行的是wrapper(),在wrapper的函数体内再执行最原始的index
  • 至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。但我们忽略了若被装饰的函数修改参数个数的情况,便会抛出异常
def home(name):
    time.sleep(5)
    print('Welcome to the home page',name)

home=timer(home)
home('egon')
#抛出异常
TypeError: wrapper() takes 0 positional arguments but 1 was given
  • 之所以会抛出异常,是因为home(‘egon’)调用的其实是wrapper(‘egon’),而函数wrapper没有参数。wrapper函数接收的参数其实是给最原始的func用的,为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合(见4.3小节),于是修正装饰器timer如下
  • 方案三的优化二:可边长度参数
def timer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper
  • 此时我们就可以用timer来装饰带参数或不带参数的函数了
@timer # index=timer(index)
def index():
    time.sleep(3)
    print('Welcome to the index page')
    return 200
@timer # index=timer(home)
def home(name): time.sleep(5) print('Welcome to the home page’,name)
  •  以下为分析过程:
# 装饰器
# 需求:在不修改源代码及调用方式的基础上为其添加统计运行时间的新功能
# 源代码
import time

def index(x,y):
    time.sleep(3)
    print('index %s %s' %(x,y))

index(111,222)

# 方案一:
# 问题:没有修改被装饰对象的调用方式,但是修改了源代码
def index(x,y):
    start_time = time.time()
    time.sleep(3)
    print('index %s %s' % (x, y))
    stop_time = time.time()
    print('run time is %s' % (stop_time - start_time))

index(111,222) #函数执行


# 方案二:
# 问题:没有修改被装饰对象的源代码及调用方式,但是代码冗余
def index(x,y):
    time.sleep(3)
    print('index %s %s' %(x,y))


start_time = time.time()
index(111,222)
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))


start_time = time.time()
index(111,222)
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))

# 方案三:解决了方案二的代码冗余问题,但带来了新问题:即改变了源代码的调用方式
def index(x,y):
    time.sleep(3)
    print('index %s %s' %(x,y))

def wrapper():
    start_time = time.time()
    index(111,222)
    stop_time = time.time()
    print('run time is %s' % (stop_time - start_time))

wrapper()


# 方案三的优化一:(将index的参数写活了)
# 问题1:如果index加新功能,比如添加参数z,则wrapper也需要跟着修改;则考虑*args+**kwargs接受可边长度参数
# 问题2:wrapper用于装饰index,写死了,若其他函数如home也需要wrapper装饰,则代码冗余,故考虑方案三之优化二(使用闭包函数)

def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))

def wrapper(*args,**kwargs):# a=111,b=222,c=333
    start_time = time.time()
    index(*args,**kwargs) # index(x=111,y=222,z=333)
    stop_time = time.time()
    print('run time is %s' % (stop_time - start_time))

wrapper(111,222)

# 方案三的优化二(使用了闭包功能):
# 问题2:wrapper是用于装饰index,而闭包函数的功能把index写活了
def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))

def home(name):
    time.sleep(3)
    print('welcome %s to home page' %name)

def outter(func):
# 偷梁换柱:home这个名字指向wrapper函数的内存地址
    # func=index # func=index的内存地址
    def wrapper(*args,**kwargs):# a=111,b=222,c=333
        start_time = time.time()
        func(*args,**kwargs) # index内存地址()
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))

    return wrapper

# # index=outter(index)
# f=outter(index) #  f = outter(index内存地址)
# # f = 当初wrapper的内存地址
# f(x=1,y=2,z=3)
home=outter(home)
res=home('egon') # res=wrapper('egon')
print('返回值: ',res)

# 方案三优化三:将wrapper做的和被装饰的函数一模一样,以假乱真
def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))
    return 456

def home(name):
    time.sleep(3)
    print('welcome %s to home page' %name)
    return 1234

def outter(func):
# 偷梁换柱:home这个名字指向wrapper函数的内存地址
    # func=index # func=index的内存地址,最原始的index
    def wrapper(*args,**kwargs):# a=111,b=222,c=333
        start_time = time.time()
        res=func(*args,**kwargs) # index内存地址();最原始的index
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res

    return wrapper

# index=outter(index)
# f=outter(index) #  f = outter(index内存地址)
# # f = 当初wrapper的内存地址
resid=index(x=1,y=2,z=3)
print('resid is:',resid)
home=outter(home) # 偷梁换柱:home这个名字指向wrapper函数的内存地址
res=home('egon') # res=wrapper('egon')
print('返回值: ',res)

 @装饰器:装饰器语法

  • 但是为了简洁而优雅地使用装饰器,Python提供了专门的装饰器语法来取代index=timer(index)的形式:需要在被装饰对象的正上方单独一行添加@timer,当解释器解释到@timer时就会调用timer函数,且把它正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名
# 在被装饰对象的正上方单独一行添加@outter
def outter(func):
# 偷梁换柱:home这个名字指向wrapper函数的内存地址
    # func=index # func=index的内存地址
    def wrapper(*args,**kwargs):# a=111,b=222,c=333
        start_time = time.time()
        res=func(*args,**kwargs) # index内存地址()
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res

    return wrapper

@outter # index=outter(index)
def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))
    return 456

@outter # home=outter(home)
def home(name):
    time.sleep(3)
    print('welcome %s to home page' %name)
    return 1234

index(1,2,3)
home('egon')

 

有参装饰器

  • 参考文档:https://zhuanlan.zhihu.com/p/109078881
  •  实现一个用来为被装饰对象添加认证功能的装饰器,实现的基本形式如下
def deco(func):
    def wrapper(*args,**kwargs):
        编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
    return wrapper
  • 想提供多种不同的认证方式以供选择,单从wrapper函数的实现角度改写如下
def deco(func):
        def wrapper(*args,**kwargs):
            if driver == 'file':
                编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
            elif driver == 'mysql':
                编写基于mysql认证,认证通过则执行res=func(*args,**kwargs),并返回res
        return wrapper
  • 函数wrapper需要一个driver参数,而函数deco与wrapper的参数都有其特定的功能,不能用来接受其他类别的参数,可以在deco的外部再包一层函数auth,用来专门接受额外的参数,这样便保证了在auth函数内无论多少层都可以引用到
def auth(driver):
    def deco(func):
        ……
    return deco
  • 此时我们就实现了一个有参装饰器,使用方式如下
#先调用auth_type(driver='file'),得到@deco,deco是一个闭包函数,
#包含了对外部作用域名字driver的引用,@deco的语法意义与无参装饰器一样
@auth(driver='file') 
def index():     
    pass
@auth(driver='mysql') 
def home():
    pass 
  • 保留原函数的文档与函数属性
    • 可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性,但对于被装饰之后的函数,查看文档注释
@timer
def home(name):
    '''
    home page function
    :param name: str
    :return: None
    '''
    time.sleep(5)
    print('Welcome to the home page',name)

print(help(home))
'''
打印结果:

Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

None
    • 在被装饰之后home=wrapper,查看home.name也可以发现home的函数名确实是wrapper,想要保留原函数的文档和函数名属性,需要修正装饰器
def timer(func):
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    wrapper.__doc__=func.__doc__
    wrapper.__name__=func.__name__
    return wrapper
    • functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用法如下
import time

from
functools import wraps def timer(func): @wraps(func) def wrapper(*args,**kwargs): start_time=time.time() res=func(*args,**kwargs) stop_time=time.time() print('run time is %s' %(stop_time-start_time)) return res return wrapper
# 2、有参数装饰器
import time

def timmer(func):
    # @wraps(func)
    def wrapper(*args,**kwargs):# a=111,b=222,c=333
        start_time = time.time()
        res=func(*args,**kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res
    wrapper.__doc__ = func.__doc__
    wrapper.__name__ = func.__name__
    return wrapper

@timmer
def index(name):
    '''index 函数'''
    time.sleep(3)
    print('welcome %s to index' %name)
    return 1111

# res=index('egon')# res=inner('egon')
# print(res)
print(help(index))
  • 需求:实现用户登录

 

有参数装饰器
import time

def timmer(func):
    # @wraps(func)
    def wrapper(*args,**kwargs):# a=111,b=222,c=333
        start_time = time.time()
        res=func(*args,**kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res
    wrapper.__doc__ = func.__doc__
    wrapper.__name__ = func.__name__
    return wrapper

# 三层函数
def auth2(engine='file'):# engine='file'
    def auth(func):# func=index
        def inner(*args,**kwargs):
            if engine == 'file':
                name=input('name>>:').strip()
                password=input('password>>:').strip()
                if name == 'egon' and password == '123abc':
                    print('login successfully!')
                    # func(*args, **kwargs) #原本func('egon')是写死参数
                    # res=func(*args, **kwargs)
                    # return res
                    return func(*args,**kwargs)
                else:
                    print('login error')
            elif engine == 'mysql':
                print('mysql auth')
            elif engine == 'ldap':
                print('ldap auth')
            else:
                print('engine is not exists')
        return inner
    return auth

# # @timmer #timmer放在auth2函数是上方,是错误装饰:该位置统计的是auth2与index的运行时间
@auth2(engine='file')#@auth #index=auth(index) #index=inner
@timmer
def index(name):
    '''index 函数'''
    time.sleep(3)
    print('welcome %s to index' %name)
    return 1111

res=index('egon')# res=inner('egon')
print(res)
# print(help(index))
  • 函数与文件操作综合作业:

 

 

 

二、迭代器

for循环的工作原理

 

 

三、生成器:yield

 

四 、生成式

列表生成式

 

字典生成式

 

生成器表达式

 

 

 

 

 

posted @ 2022-03-21 20:07  西瓜的春天  阅读(74)  评论(0)    收藏  举报