py15.装饰器、迭代器、生成器

一、装饰器

1.什么是装饰器
器:工具
装饰:为被装饰对象添加新功能

装饰器:装饰的工具

被装饰对象--->>需要添加功能 的函数
装饰器--->>函数

装饰器的作用:在不修改被装饰对象源代码与调用方式的前提下,为其加上新的功能
装饰器必须要遵循的原则:开放封闭原则

为什么要使用装饰器:可以解决代码冗余问题,提高代码的可扩展性

开放封闭原则

开放:对函数功能的添加是开放的。
封闭:对函数功能修改是封闭的。

总结原则如下:
不修改被装饰对象源代码
不修改被修饰对象调用方式

目的:在遵循1和2原则的基础上扩展新功能

2.装饰器的实现

函数装饰器分为:'无参装饰器’和’有参装饰器’两种,二者的实现原理一样,都是’函数嵌套+闭包+函数对象’的组合使用的产物
无参装饰器: 装饰在被装饰对象时,没有传参数的装饰器。
有参装饰器: 本质上就是在无参装饰器上套了一个外层函数,无参装饰器可以引用外层函数的名字

2.1 无参装饰器
如果想为下述函数添加统计其执行时间的功能
​

import time
​
def index():
    time.sleep(3)
    print('Welcome to the index page’)
    return 200
​
index() #函数执行

 


          
遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样:
​

strat_time = time.time()
index() # 函数执行
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(其他函数)

 


========================================================================================
​
但这就违反了不能修改被装饰对象调用方式的原则,于是我们换一种为函数体传值的方式,即将值包给函数,如下:

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的组合,于是 ---> 最终版装饰器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来装饰带参数或不带参数的函数了,但是为了简洁而优雅地使用装饰器
python提供了专门的装饰器语法来取代index = timer(index)的形式
        # 需要在被装饰对象的正上方单独一行添加@timer
当解释器解释到@timer时就会调用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)
如果我们有多个装饰器,可以叠加多个
​
@deco3
@deco2
@deco1
def index():
    pass
叠加多个装饰器也无特殊之处,上述代码语义如下:
​
index=deco3(deco2(deco1(index)))    

2.2 有参装饰器
在了解了无参装饰器的实现原理之后,我们可以再实现一个用来为被装饰对象添加认证功能的装饰器,实现的基本形式如下:
​

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专门用来帮我们实现这件事,用法如下

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.3 叠加装饰器
叠加装饰器:
    在同一个被装饰对象中,添加多个装饰器,并执行。
    @装饰1
    @装饰2
    @装饰3
    def 被装饰对象():
        pass

    注意: 装饰器在调用被装饰对象时才会执行添加的功能。

    - 叠加装饰器:
        - 装饰的顺序: 由下到上装饰
        - 执行的顺序: 由上往下

 注意: 无论inner中出现任何判断,最后都要返回“调用后的被装饰对象” func(*args, **kwargs)

3.装饰器模板
def wrapper(func):
    def inner(*args, **kwargs):
    
        为被装饰对象添加新功能
        
        res = func(*args, **kwargs)  # 调用被装饰对象,得到返回值
        
        为被装饰对象添加新功能
        
        return res    
    return inner

def func1():
    pass

func1 = wrapper(func1)
func1()  # inner()

4.装饰器的语法糖

装饰器语法糖,是属于装饰器的。

@:装饰器的的语法糖
注意: 在使用装饰器语法糖时,装饰器必须定义在被装饰对象之上。

import time

# 统计函数执行时间装饰器

def wrapper(func):  # 被装饰对象
    def inner(*args, **kwargs):  # 被装饰对象的参数
        start_time = time.time()          # 调用前增加新功能
        res = func(*args, **kwargs)         # 调用被装饰对象,并接收返回值
        end_time = time.time()           # 调用后添加新功能
        print(end_time - start_time)

        return res
    return inner
    
@wrapper  # download= wrapper(download_m)
def download_m()
     print('开始下载电影....')
     time.sleep(3)
     print('电影下载完成....')
download_m()

二、迭代器

定义:迭代取值的工具, 它可以迭代取值。
迭代:是指重复迭代,每一次迭代的结果都是基于上一次的结果而来的
-可迭代对象:所有序列类型:list、tuple、dict、str、set、f(文件)

#依赖于索引取值
goods=['mac','lenovo','acer','dell','sony']

index=0
while index < len(goods):
    print(goods[index])
    index+=1

dict1 = {'name':'bob','age':18,'sex':'male'}
iter_dict1 = dict1.__iter__()    #iter_dict1是一个迭代器对象 
print(iter_dict1.__next__())
print(iter_dict1.__next__())
print(iter_dict1.__next__())
>>>name
>>>age
>>>male
# 抛出stopiteration的异常,代表无值可取,迭代结束
补充:
list1 = ['tank', 'jason鸡哥', 'sean', '饼哥']
iter_list1 = list1.__iter__()
while True:
    # 补充: try:获取异常
    try:
        print(iter_list1.__next__())   #报错
    #立即触发此代码 StopIteration
    except StopIteration:      #捕捉异常终止循环
        break

凡是内部有.__iter__()方法的都是可迭代对象。

例如:
str='hello'
str1.__iter__()

-获取迭代器:
	通过可迭代对象.__iter__(),得到的返回值就是"迭代器对象

-如何迭代取值:
	迭代器对象.__next__(),每次执行,都会从迭代器对象中取出一个值
	
-迭代器对象的优点:
	 1. 不依赖于索引迭代取值
	 2. 节省内存空间。
 缺点:
     1.取指定某个值麻烦
     2.每次取值都要从第一个值开始,无法同过索引取值
     3.无法通过len()计算长度


        - 迭代器本质上是一个可迭代对象

        - 文件本质上既是迭代器对象,也是可迭代对象。

        - 可迭代对象不一定是迭代器对象


for循环原理:

for i in 可迭代对象:
	- in: 会将可迭代对象自动调用.__iter__()变成迭代器对象
	 
	- for循环内置捕获异常机制


set1 = '1, 2, 3, 4'
iter_set1 = set1.__iter__()    #iter_set1 迭代器
print(iter_set1.__iter__() is iter_set1)  #True

list1 = [1, 2, 3, 4]
iter_list1 = list1.__iter__()
print(iter_list1 is list1)   #False

三、生成器

生成的工具
生成器是一个“自定义”的迭代器,本质上就是一个迭代器

1.如何实现生成器

但凡在函数内部包含关键字yield,调用函数时,函数体代码不会执行,但会返回一个结果,该结果就是一个生成器

-yield:
	只能在函数内部定义
	每次yield都会往生成器对象中添加一个值,
	yield可以保存函数的暂停状态


yieldreturn:
    相同点:
        返回值的个数都是无限制的。

    不同点:
        return只能返回一次值,yield可以返回多次值

# 自定义的迭代器:
def func():
    print('form func')
    yield 1
res = func()  #res是一个生成器
print(res)  #<generator object func at 0x0000016D5A0DEF48>


#当我们通过.__next__取值时,才会执行函数体代码。
def func():
    print('from func')
    yield 1
res = func()
print(res.__next__())


def func():
    print('开始准备下蛋')
    print('一个鸡蛋')
    yield '鸡蛋1'
    print('第二个鸡蛋')
    yield '鸡蛋2'
    print('第三个鸡蛋')
    yield '鸡蛋3'
    print('取最后一个鸡蛋,查看是否还有')
res = func()   #res是迭代器对象
# print(next(res))
# print(next(res))
# print(next(res))
# print(next(res)) #StopIteration 报错

迭代器对象.__next__() ==next(迭代器对象)
print(res.__next__())   #当我们通过.__next__取值时,才会执行函数体代码
print(res.__next__())
print(res.__next__())
print(res.__next__())   #StopIteration 报错

# 循环10次
for i in range(1,11):
    print(i)
  #  python2:range(1,5)--->[1,2,3,4]
  # python3:range(1,5) --->range对象 --->生成器 --->迭代器

#自定义range功能,创建一个自定义的生成器
def my_range(start,end,move):
    while start<end:
        yield start
        start +=move

g_range = my_range(1,5,2)   #g_range 是生成器
print(g_range)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

琴声浮或沉__听懂只一人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值