python三大器:装饰器,迭代器,生成器

2021/8/9 11:35:52

本文主要是介绍python三大器:装饰器,迭代器,生成器,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

此文与其他文章不同,仅为个人学习笔记使用,编写过程中难免有错误,望各位读者提出,共同进步。
装饰器,迭代器,生成器是python开发过程中重要的工具,装饰器相较于其他两个来说较难以理解。

1.装饰器

通俗的来说是用来为其他区函数增加其他功能的,同时遵循一个基本原则:开放封闭原则:开放指对拓展功能开放,封闭指对修改源代码是封闭的。
1.1
简简单单先定义一个函数:

def index(x, y):
    print("index %s,%s" % (x, y))
index(111, 222)

不难看出结果应该是:

index 111,222

当我们想为这段代码添加一个运行时间的代码,但又不想破坏其本身的完整型
想到利用time方法可以实现:

import time    
def index(x,y):
    start = time.time()
    time.sleep(3)
    print("index %s,%s" %(x,y))
    stop = time.time()
    print(stop - start)
index(111,222)

输出:

index 111,222
3.0005669593811035

1.2
不出意外,运行时间是能统计出来,但是我们破坏了代码的完整性。这算是个失败案例,但是我们想到可以在函数内调用其他函数去实现:定义一个新的函数去接收index;运行1.1函数实际上就是在调用index(111,222),那我们想到在调用阶段为其添加统计时间功能

def index(x,y):
    print("index %s,%s" % (x, y))

start = time.time()
time.sleep(3)
index(111,222)
stop = time.time()
print(stop - start)

输出:

index 111,222
3.000377655029297

结果也是合情合理,但是我们想到在开发过程中 应该会重复调用多次1.1的函数,所以为了代码的简洁性,我们把调用阶段包装成一个函数:

import time
def index(x,y):
    print("index %s,%s" % (x, y))
def timee(x,y):
    start = time.time()
    time.sleep(3)
    index(x,y)
    stop = time.time()
    print(stop - start)
timee(1,2)

输出:

index 1,2
3.0007386207580566

此时不管有多少需求 我只需要运用time(x,y)函数来掉用。
1.3这时候,我们将index参数写活,用*args,**kwargs进行定义,这样可以优化需求:

import time
def timee(*args,**kwargs):
    start = time.time()
    index(*args,**kwargs)
    stop = time.time()
    print(stop - start)

def index(x, y, z):
    time.sleep(3)
    print("index %s,%s,%s" % (x, y, z))
timee(1,2,3)

输出:

index 1,2,3
3.0002880096435547

定义阶段:timee(*args,**kwargs):中timee如何调用 就怎么原封不动的index。这是看上去很完美,实际上还有很大的缺陷:timee函数只能为index函数做装饰,如果有其他函数还需要另外重写,不符合代码的简洁性,我们想到可以用嵌套函数在外层把函数装饰参数,

import time   
def index(x,y,z):
    time.sleep(3)
    print("index %s,%s,%s" % (x,y,z))
def outter(func):

    def timee(*args,**kwargs):
        start = time.time()
        func (*args,**kwargs)
        stop = time.time()
        print(stop - start)
    return timee

index= outter(index) 
index(1,2,3)

1.4当我们相想为多个函数添加运行时间功能时,那么另外定义一个就好了:

import time
def index(x,y,z):
    time.sleep(3)
    print("index %s,%s,%s" % (x,y,z))
    return 456
def home (name):
    time.sleep(2)
    print("welcome %s  come to home page"%name)
    return 123
def outter(func):
    # func = index  # 的内存地址
    def timee(*args,**kwargs): # timee怎么定义的,怎么给index调用,原封不动
        start = time.time()
        func (*args,**kwargs)
        stop = time.time()
        print(stop - start)
        #return 123 #此时才算有timee的返回值
    return timee # 是属于outter的返回值
home = outter(home)
index= outter(index)
r = index(1,2,3)
res = home('egon')
print('返回值--》',res)
print('返回值--》',r)

输出:

index 1,2,3
3.01408314704895
welcome egon  come to home page
2.0104966163635254
返回值--》 None
返回值--》 None

我们可以看出想要为home添加功能,不需要改变原有的整体,只用添加相关代码即可,
但是如果我们想为成千上万个函数增加统计运行时间,俺么每次一次代码就需要定义一行,造成了代码冗余,那么@outter这个语法糖可以解决这个问题
1.5@装饰器

import time
def outter(func):
    # func = index  # 的内存地址
    def timee(*args,**kwargs): # timee怎么定义的,怎么给index调用,原封不动
        start = time.time()
        func (*args,**kwargs)
        stop = time.time()
        print(stop - start)
        # return 123 #此时才算有timee的返回值
    return timee # 是属于outter的返回值
@outter #index= outter(index)   # home = wrapper的内存地址
def index(x,y,z):
    time.sleep(3)
    print("index %s,%s,%s" % (x,y,z))
    return 456
@outter #home = outter(home)  # f = 当初那个timee函数的内存地址
def home (name):
    time.sleep(2)
    print("welcome %s  come to home page"%name)
    return 123
r = index(1,2,3)
res = home('egon')
print('返回值--》',res)
print('返回值--》',r)
print(home)

输出:

index 1,2,3
3.007416009902954
welcome egon  come to home page
2.0053913593292236
返回值--》 None
返回值--》 None
<function outter.<locals>.timee at 0x00907808>

1.6
我们发现打印home时,home的属性已经被指向timee,这肯定不是我们所想要的,functools功能帮我们解决了这个问题:

from functools import wraps
def outter(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        return res
    wrapper.__name__ = func.__name__# 手动将原函数的属性赋值给wrapper函数
    return wrapper
@outter# index= outter(index)
def index(x,y):
    '''这是主页功能'''
    print(x,y)
index.__name__ = 'index'
print(index.__name__)
print(help(index))

这里我们把index.__name__指向了index 因此帮助文档展示的就是index的注释
输出:

index
Help on function index in module __main__:
index(x, y)
    这是主页功能
None

1.7
有参装饰器:如果装饰器本身需要参数,那么就编写一个返回装饰器的高阶函数:

import functools
def log(text):
    def decorator(func):
        @functools.wraps(func)#在定义wrapper()的前面加上@functools.wraps(func)即可。
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)#返回now函数的打印值
        return wrapper
    return decorator
@log('execute')
def now():
    print('2013-12-25')
    return 1
print(now())#wrapper

此时,now() = log('execute')now()
输出:

execute now():
2013-12-25
1

1.8叠加多个装饰器的加载,运行分析:

def deco1(func1):
    def wrapper1(*args, **kwagrs):
        res1 = func1(*args, **kwagrs)
        print("第一个装饰器")
        return res1
    return wrapper1
def deco2(func2):
    def wrapper2(*args, **kwagrs):
        print("第二个装饰器")
        res2 = func2(*args, **kwagrs)
        return res2
    return wrapper2
def deco3(x):
    def outer3(func3):
        def wrapper3(*args, **kwagrs):
            print("第三个装饰器")
            res3 = func3(*args, **kwagrs)
        return wrapper3
    return outer3
@deco1 
@deco2 
@deco3(111)
def index(x, y):
    print('from index %s,%s' % (x, y))
print(index(1, 2)) 

加载顺序应该是自下而上:outer3-->index = outer3(index) -->index = wrapper3的内存地址
index = deco2(wrapper3的内存地址)-->index = wrapper2的内存地址
index = deco1(wrapper2的内存地址)-->index = wrapper1的内存地址
此时打印index(1, 2)返回的应该是 wrapper1的内存地址
执行顺序与加载顺序相反
输出:

第二个装饰器
第三个装饰器
from index 1,2
第一个装饰器
None

2迭代器

迭代器是迭代取值的工具,迭代是一个重复的工具,每次重复都是基于上一次的结果而继续的,单纯的重复并不是迭代。能把多个值循环取出来的类型有:列表,字符串,元组,字典,集合,文件。
先简单列出一个while循环取值:

count  =['e','q','f']
q = 0
while q<len(count):
    print(count[q])
    q+=1

上述取值的当时只是适用于有索引的数据类型。
2.1可迭代对象:
但凡内置有__iter__方法的都称之为可迭代的对象,那么我们考察一下上述类型是否为可迭代对象。

s1 =''
print(s1.__iter__())
l = []
print(l.__iter__())
d = {'a':1}
print(d.__iter__())
t = (1,)
print(t.__iter__())
set1 = {1,2,3}
print(set1.__iter__())
with open ("C:/Users/wxl/Desktop.111.txt",mode = "w") as f:
    pass
print(f.__iter__())

输出:列表,字符串,元组,字典,集合,文件均是可迭代对象

<str_iterator object at 0x01DEE6E8>
<list_iterator object at 0x01DEE6E8>
<dict_keyiterator object at 0x01E048C0>
<tuple_iterator object at 0x01DEE6E8>
<set_iterator object at 0x01E0D4C8>
Traceback (most recent call last):
  File "C:/Users/wxl/Desktop/笔记.py", line 2260, in <module>
    print(f.__iter__())
ValueError: I/O operation on closed file.

由此可见:
2.3迭代器对象:调用可迭代对象下的.__iter__方。法会将其转换成迭代器对象
以列表为例:

d = {'a':1,'b':2,"c":3}#调用可迭代对象下的.__iter__方。法会将其转换成迭代器对象
res = d.__iter__()
while True:
    try:
        print(res.__next__())
    except StopIteration:
        break

输出:

a
b
c

利用迭代器取值:

d = {'a':1,'b':2,"c":3}#调用可迭代对象下的.__iter__方。法会将其转换成迭代器对象
c = d.__iter__()
print(c.__next__())
print(c.__next__())

这里的c是一个迭代器对象,迭代器的内置方法:next:得到迭代器下一个值
输出:

a
b

可迭代对象与迭代器对象:
可迭代对象:可以转换为迭代器对象的对象:同时内置有.__iter__方法
迭代器对象:内置有.__next__方法且内置有.__iter__方法
迭代器对象.next:得到迭代器下一个值
迭代器对象.iter:得到迭代器本身
2.4for循环工作原理:
先写一个简单的for循环:

d = [12,2,3]
for i in d:
    print(i)

我们在用迭代器实现一下:

d = [12,2,3]
c = d.__iter__()
print(c.__next__())
print(c.__next__())
print(c.__next__())

二者输出都是:

12
2
3

那么上述代码中在运行一次print(c.next())会产生什么呢?

Traceback (most recent call last):
  File "C:/Users/wxl/Desktop/笔记.py", line 2356, in <module>
    print(c.__next__())
StopIteration

可以看出运行一次就读取一行信息。若把值取干净,若再次调用则会抛出异常。想要再次运行的话重新再定义一次迭代器对象就好了。

接下来解释一下for循环工作原理:
1.d.iter()得到一个迭代器对象
2.迭代器对象.next()会拿到一个返回值,然后将该返回值赋值给i
3.往复循环步骤2,知道抛出异常,佛如循环会捕捉异常然后结束循环
缺点:
1.取值不如索引方便,
2.是一次性用具。

3.生成器:

自定义迭代器:在函数内有yield关键字,调用函数指挥返回一个生成器

def func():
    print("第一次")
    yield 1
    print("第2次")
    yield 2
    print("第3次")
    yield 3
    print("第4次")
res = func()
print(res)

输出:

<generator object func at 0x031A33E0>

看出func是generator(生成器)generator不会把所有保存在内存中,而是一边计算一边调用,

res.__iter__()
q = res.__next__()
print(q)#1
print(q)#1

只调用一次去多次打印迭代器对象得到多次重复的值:

第一次
1
1
1

在python中为了让q = res.next()书写更方便,简化成了next(res)



这篇关于python三大器:装饰器,迭代器,生成器的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程