day12-无参装饰器,迭代器和生成器

一.装饰器

装饰器的伪装一步步实现(重点)

昨天我们已经实现了对闭包函数的运用,写了一个简单的装饰器

那么今天我们就来具体的讲诉一下装饰器该怎么应用

装饰器其实可以分为俩种,一种是无参的装饰器,也就是我们昨天写的,只有俩层的函数.一种是有参装饰器,它就是在我们俩层的基础再套一层函数.

所以我们现在就先来介绍一下无参装饰器的应用

先不着急补充,我们先复习一下昨天的玩法.

还是那个例子.请你写出一个时间计时的装饰器

def index():
    time.sleep(1)
    print("form index")


def outer(func):
    def wrapper():
        start = time.time()
        func()
        stop = time.time()
        print(f"run time is {stop - start}")
    return wrapper


index = outer(index)
index()

这时候,大家对这个应该就不陌生了吧.

那现在我又有一个需求:让这个计时的装饰器可以为不同的函数运用,即不管函数本身是否有参数还是无参数.

即我们来一个实际的例子,我现在又有一个函数,home, home的 函数体代码如下

def home(name):  # 被装饰对象home的内存地址
    time.sleep(2)
    print(f"welcome {name} to my home...")

我们正常调用即:home('jkey')

现在我们试试上面的装饰器

home = outer(home)
home("jkey")  

会报错: 说 你这个 home是一个 不需要参数的,而你现在给他传了一个参数.是不是就没有满足,它的本意.

别着急,现在我们看看问题出在哪!

你将 home = outer(home)是给它换了一个内存地址 即 wrapper, 但是你不难发现,你现在的wrapper就是一个无参的函数,所以它并不能在下方直接'home("jkey")'这样直接调用.

现在我们来解决这个问题的根本,它是不是因为 wrapper函数不能传参???

那我们现在就为其加上参数!

改后长这样:

def outer(func):  # func ==> 被装饰对象函数的内存地址
    def wrapper(name):
        start = time.time()
        func(name) 
        stop = time.time()
        print(f"run time is {stop - start}")
    return wrapper 

这时候你再使用装饰器:home = outer(home),之后的home("jkey") 就可以正常运行了.

但是你现在又将wrapper这个函数写死了,即它只能装饰在home函数上了.你的index函数又不可以调用装饰器了.

因为你调用的就是wrapper这个函数,它现在变成要传一个参数的了!

所以现在就又有一个问题了?那就是我怎么"写活".

这时候我们的* 和 ** 的作用就来了.还记得之前将的 *和**的作用嘛?

当*和**被当作形参的时候会接收溢出的位置实参和关键字实参,回合成一个元组和字典,而被当作实参的时候又会将后面接的可迭代对象打散成位置形参和默认形参.即我们可以发现下面这种搭配

def func(*args,**kwargs):
    index(*args,**kwargs)
    
func(1,1,3,3)

你会发现你执行func实际上就是将实参原封不动的传给了index

这时候我们又回到我们的装饰器需求上来.

于是就有了一个新的方案,如下:

def outer(func):  # func ==> 被装饰对象函数的内存地址
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs) 
        stop = time.time()
        print(f"run time is {stop - start}")
    return wrapper  

这时候,你再调用home还是index就都可以和原来一样了!

哈哈哈.真是一个不错的解决方案,但难免,还是有点问题?

什么问题呢?那就是我们的返回值大哥了.

来个实际例子:现在我们的home功能又要我们加上返回值的需求了

即:

def home(name):  # 被装饰对象home的内存地址
    time.sleep(2)
    print(f"welcome {name} to my home...")
    return name

res = home('jkey')

print(res) # 即这个res应该是一个 jkey 字符串

我们现在直接拿我们的装饰器装饰之后,调用,查看一下返回值

home = outer(home)
res2 = home("jkey")
print(res2)  

此时你会发现你的res2打印出来居然是一个None!

why ?????

其实原因还是出在了 wrapper , 哈哈哈 所以wrapper才是装饰器的核心.

同学你看看,我们现在的wrapper是不是还没有返回值呢??没有返回值它当然给你返回None啦

于是我们可以这样解决

def outer(func):  # func ==> 被装饰对象函数的内存地址
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)  # 被装饰对象函数的返回值
        stop = time.time()
        print(f"run time is {stop - start}")
        return res
    return wrapper  # 千万别加()

这时候我们再拿我们的装饰器装饰之后,调用,查看一下返回值

home = outer(home)
res3 = home("jkey")
print(res3)  

这时候是不是就和原来一样了.打印出的就是jkey了

这时候我们的装饰器才是一个完整的装饰器!

语法糖

到这!其实我们python在内部已经帮我们做好了home = outer(home)的优化方案,即装饰器的语法糖

在python中我们的装饰器调用时可以直接用@装饰器名称就代替了home = outer(home)

即具体例子:

方案1:index = auth(index) 的方案

def index():
    print('hi jkey')

def auth(func):
    def wrapper(*args, **kwargs):
        user = input('>>>:').strip()
        if user == 'jkey':
            print('就是该用户')
        res = func(*args, **kwargs)
        return res
    return wrapper

index = auth(index)
index()

方案2:@auth

def auth(func):
    def wrapper(*args, **kwargs):
        user = input('>>>:').strip()
        if user == 'jkey':
            print(f'hi {user}')
        res = func(*args, **kwargs)
        return res

    return wrapper


@auth
def index():
    print('index is run')
index()

俩种方式是等同于的,推荐使用语法糖@装饰器名

了解:functools中的wraps模块

在 python中 每个函数其实都有各自的内置属性,这个我给大家列举俩个给大家看看

.__name__返回的是函数的名字

.__doc__返回的是函数的注释

我们要想让我们的装饰器伪装的更像一点,我们可以调用python自带的模块

from functools import wraps

这个wraps就是让我们的装饰器不仅能有功能和返回值和原函数一样,连属性都变的一样了

使用方法:

def outer(func):  # func ==> 被装饰对象函数的内存地址
	from functools import wraps
	@wraps(func)  # 这一步就是讲wrapper完全伪装成和被装饰的对象函数一样
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)  # 被装饰对象函数的返回值
        stop = time.time()
        print(f"run time is {stop - start}")
        return res
    return wrapper  # 千万别加()

了解2:当我们的被装饰函数被多个装饰器装饰的时候,代码执行的顺序

这个大家了解一下就好

提示:先看被装饰的函数的地方的注释,再看装饰器(才下往上)

def deco1(func1):  # func1 = wrapper2的内存地址
    def wrapper1(*args, **kwargs):
        print('=====>wrapper1')
        res1 = func1(*args, **kwargs)
        print('=====>end1')
        return res1

    return wrapper1


def deco2(func2):  # func2 = wrapper3的内存地址
    def wrapper2(*args, **kwargs):
        print('=====>wrapper2')
        res2 = func2(*args, **kwargs)
        print('=====>end2')
        return res2

    return wrapper2


def deco3(func3):  # func3 = wrapper4的内存地址
    def wrapper3(*args, **kwargs):
        print('=====>wrapper3')
        res3 = func3(*args, **kwargs)
        print('=====>end3')
        return res3

    return wrapper3


def deco4(func4):  # func4 = wrapper5的内存地址
    def wrapper4(*args, **kwargs):
        print('=====>wrapper4')
        res4 = func4(*args, **kwargs)
        print('=====>end4')
        return res4

    return wrapper4


def deco5(func5):  # func5 = 被装饰的index的内存地址
    def wrapper5(*args, **kwargs):
        print('=====>wrapper5')
        res5 = func5(*args, **kwargs)  #
        print('=====>end5')
        return res5

    return wrapper5


# -------index = wrapper1的内存地址
@deco1  # deco1(被装饰的wrapper2的内存地址) =====> wrapper1的内存地址
@deco2  # deco2(被装饰的wrapper3的内存地址) =====> wrapper2的内存地址
@deco3  # deco3(被装饰的wrapper4的内存地址) =====> wrapper3的内存地址
@deco4  # deco4(被装饰的wrapper5的内存地址) =====> wrapper4的内存地址
@deco5  # deco5(被装饰的index的内存地址) =====> wrapper5的内存地址
def index():
    print('from index')


index()

"""
返回结果:
        =====>wrapper1
        =====>wrapper2
        =====>wrapper3
        =====>wrapper4
        =====>wrapper5
        from index
        =====>end5
        =====>end4
        =====>end3
        =====>end2
        =====>end1
"""

即我理解的概念为:当一个函数被多个装饰器装饰的时候,执行的顺序为
1:在装饰器中的wrapper的func在执行之前的顺序是按照 装饰器的位置从上往下的顺序执行,执行完了,
2.则执行被装饰的对象的代码体,
3.之后再执行func执行之后的,顺序则是从下往上执行.

练习

下方是我写的的装饰器的一个登录功能的小练习

def login(func):
    def wrapper(*args, **kwargs):
        user = input('user>>:').strip()
        pwd = input("pwd>>:").strip()
        if user == 'jkey' and pwd == "123":
            print('成功')
            res = func(*args, **kwargs)
            return res
        else:
            print('用户名或者密码错误')

    return wrapper


@login
def index():
    print('from index')


index()

无参装饰器模板

其实无参装饰器还有一个模板,即

def outer(func):
    from functools import wraps
    @wraps(func)
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res

    return wrapper

有参装饰器我们留着明天讲!

迭代器

接下来我们要讲的是迭代器

1、什么是迭代器

​ 迭代器指的是迭代取值的工具

什么是迭代?

​ 迭代就是一个重复的过程,但是每一次重复都是在上一次的基础之上进行的

2、为何要用迭代器

​ 1、迭代器提供了一种不依赖于索引的、通用的迭代取值方案
​ 2、节省内存

案例:使用while循环将有索引的数据类型取值

# nums = [111, 222, 333]
nums = "hello"


def get(l):
    i = 0
    while i < len(l):
        print(l[i])
        i += 1

get(nums)

我们发现可以将字符串,列表..这种有索引的数据类型的值取出来,但是当是一个字典对象?一个文件对象?是不是就不能通过上面的while来实现了?所以就要用到我们的迭代器

3、如何用迭代器

在如何用迭代器之前,我们就必须了解可迭代的对象和迭代器对象

可迭代的对象

​ 内置有__iter__方法的对象都叫可迭代的对象

例如:之前我们所学的数据类型,除了数字(bull其实也是数字类型),其他的都是可迭代对象,注意,文件是一个迭代器对象.

"abc"
[1,2,3]
(1,2,3)
{'k1':111}
{1,2,3}
------以上都是可迭代对象
f = open('a.txt',mode='wt')  # 文件对象本身就是迭代器对象

迭代器对象

​ 1、内置有__next__方法
​ 2、内置有__iter__方法

调用迭代器对象的__iter__方法得到的它自己,就跟没调用一样
调用迭代器对象的__next__方法返回下一个值,不依赖索引
可以一直调用__next__直到取干净,则抛出异常StopIteration

所以我们可以得到的结论是,迭代器对象一定是可迭代的对象,但可迭代的对象不一定是迭代器对象.

可迭代对象和迭代器对象的一个内存区别就是,可迭代对象就相当于一个个的鸡蛋,即内存中存放的,而迭代器对象是一个可以产生鸡蛋的老母鸡,即需要蛋时就产生,即一个是一开始将所有元素都放到内存中,而一个是放一个可以产生元素的对象.内存上会有一个很大的优化.但在可迭代对象转成迭代器的时候,其实是又有鸡蛋又有老母鸡,哈哈哈这就很尴尬了.但其实除了文件,一般的数据类型也不会去存那么多数据信息,所有除了文件是一开始的迭代器对象,其他的都是可迭代对象.

其实.调用可迭代对象的__iter__方法,返回的是它的迭代器

什么意思???即一个可迭代对象,在调用了iter方法之后,就由可迭代对象变成了迭代器对象.

迭代器推演过程

那我们迭代器的最终目的就是可以不通过索引去迭代取值,下方为推演过程

1.将一个可迭代对象内的值不通过索引全取出来

方式1_iter__和__next_

nums = [111, 222, 3333]
nums_iter = nums.__iter__()  # 这一步就是将可迭代对象变成迭代器对象
print(nums_iter)
print(nums_iter)  # <list_iterator object at 0x000001CF3CDCBA08>
# 即可以调用迭代器对象下面的next方法,来迭代取值
print(nums_iter.__next__())  # 111
print(nums_iter.__next__())  # 222 
print(nums_iter.__next__())  # 333
print(nums_iter.__next__())  # 抛异常:StopIteration,即已经没有可迭代的值了

方式二:iter()和next();等同于上面

x = iter(nums)
print(next(x))
print(next(x))
print(next(x))
print(next(x))

那现在我们的问题是如何解决这种取多了,或者取少了的问题,取少了还行,就是没取干净,去多了,可就会抛异常了

因为会用到一个没用到的知识点:try: except 捕捉异常的操作

try的简单使用.因为我们遇到的问题是 StopIteration 所以我们可以在 except 为StopIteration 时做一个处理

即搭配到我们的迭代器中:

nums = [111, 222, 3333]
nums_iter = iter(nums)
while True:
    try:  # try内的子代码为一定会遇到异常的代码
        res = next(nums_iter)
        print(res)
    except StopIteration:  # except 后接的是异常代码返回的错误类型
        break

这上面的操作就是我们for循环的使用原理

for循环使用:

nums = [111, 222, 3333]
for res in nums:
    print(res)

1、先调用in后那个对象的__iter__方法,拿到迭代器对象
2、 res = next(迭代器对象),执行一次循环体代码
3、循环往复步骤2,直到值取干净抛出异常StopIteration,for会捕捉异常结束循环

所以迭代器就是一个让我们了解for循环的强大一个知识点.

生成器

概念:生成器其实就是一个自定义的迭代器

yield

在介绍生成器之前,我们要知道另外一个知识点:yield关键字

之前我们学习了函数的一种返回值的方式,return

其实yield也是一种返回函数的返回值的方式.它于return的异同:

相同点:返回值层面用法一样
不同点:return只能返回值一次,而yield可以返回多次

下面是yield使用的一个案例

def func():
    print('xxx')
    yield 111
    print('yyy')
    yield 222
    print('zzz')
    yield 333
    print('mmmm')

当函数内出现yield关键字,再调用函数并不会触发函数体代码的运行,会返回一个生成器

g = func() # 这里返回的就是一个生成器(generator)

print(g) # <generator object func at 0x000001A1450411C8>

生成器就是一种自定义的迭代器

那么我们就可以使用迭代器取值的方法来取生成器了

res = next(g)  # xxx
print(res)  # 111
#
res = next(g)  # yyy
print(res)  # 222
#
res = next(g)  # zzz
print(res)  # 333

我们根据迭代器的特性可以发现,函数在每次被next之后就会将函数体代码暂停运行,并暂停的位置是对应次数的yield返回值的后面.

当我们在进行上面的三次next()取值之后,再next一次,而函数内没有对应的yield对应,这时候又会抛异StopIteration

next(g)  # StopIteration

所以我们就可以根据生成器满足一个需求?那就是当我有一个很大很大的数据内容需要时!我们就可以用到我们自定义的迭代器了,也就是生成器.

即需求:我现在要一个1-10000000000元素值?我们难道要存放一个1-10000000000的列表来存,来取值?

所以我们就可以使用生成器来帮助我们实现

def func():
    res = 1
    while True:
        yield res
        res += 1

g = func()
# 我就可以通过next一次一次取值往内存中放
print(next(g))
print(next(g))
print(next(g))

那生成器这么好,为什么我不就使用这个,还要使用可迭代对象呢???其实,迭代器对象有一个很大的不足,就是它取值,一次只能取一个,并且不能往回取,这相对于可迭代对象来说就是low的一批了,毕竟可迭代对象可以根据索引的,根据key的..,可以实现指哪打哪,而迭代器对象不行.所以都有好有弊.

我们现在出一个案例,让你看看你掌没掌握迭代器

nums = [111, 22, 33]

x = iter(nums)
for res in x:  
    print(res)
print('='*50)

for res in x:  
    print(res)

还有一个案例:

nums = [111, 22, 33]

for res in nums:  # nums.__iter__()
    print(res)
print('=' * 50)
for res in nums:  # nums.__iter__()
    print(res)

你觉得打印出的res的值是分别什么????为什么??

那大家可以自己去试试

本天总结:

1.有参装饰器

功能一大推,最后可以获取一个有参装饰器的模板

def outer(func):
    from functools import wraps
    @wraps(func)
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res

    return wrapper

特别要注意的是func(*args, **kwargs)这个没执行之前,是表示你的被装饰对象的函数体代码没被执行,而执行之后,是在你的被装饰对象的函数体代码执行过后了,所以要添加的功能是要在没执行之前还是之后,看功能需求了.

2.迭代器

迭代器有俩个重点:就是知道什么是可迭代对象,什么是迭代器对象.

可迭代对象在调用了iter方法之后,就变成了迭代器对象.

然后就是迭代器对象的怎么取值了.就是next()方法的

3.生成器

生成器其实就是一个自定义的迭代器.一般可以搭配函数内的yield关键字使用,next()一次,函数执行一次,碰到yield就等下一次next...依此类推

原文地址:https://www.cnblogs.com/jkeykey/p/14212249.html