01-python-装饰器学习

1. 装饰器本质

        装饰器实际上是给函数添加额外功能的函数。

        函数的编写理论上要遵循开放封闭的原则,

        开放是指该函数功能可以扩展,

        封闭是指当函数已经实现预期功能,就不要更改内部代码了,此时要想添加功能,就要使用装饰器。

2. 举个例子

        比如说想要统计函数的运行时间:

import time
def dance():
    time.sleep(3)
    print("-----","我在跳舞","-----")
def sing():
    time.sleep(3)
    print("-----","我在唱歌","-----")
def sleep():
    time.sleep(10)
    print("------","我在睡觉","-----")
def cook():
    time.sleep(5)
    print("------","我在做饭","-----")

统计dance函数的运行时间,一般有两种做法:

# 第一种做法
def dance():
    start_time = time.time()
    time.sleep(3)
    print("-----","我在跳舞","-----")
    stop_time = time.time()
    print("dance函数的运行时间:",stop_time-start_time)
dance()

#这种做法显然不符合函数的开放封闭原则,修改了函数内部的代码
# 第二种方法 
start_time = time.time()
dance()
stop_time = time.time()
print("dance函数的运行时间:",stop_time-start_time)
# ----- 我在跳舞 -----
# dance函数的运行时间: 3.0120158195495605

#这种做法若是要统计个别函数的运行时间倒是可以,但是若有多个函数需要统计,就会显得特别麻烦

使用装饰器的做法

# 该函数就是装饰器,其实就是闭包
def total_time(func):
    def inner():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("dance函数的运行时间:",stop_time-start_time)
    return inner

@total_time
def dance():
    time.sleep(3)
    print("-----","我在跳舞","-----")

dance()
# 运行结果:
# ----- 我在跳舞 -----
# dance函数的运行时间: 3.0112452507019043

 可以发现,给函数添加功能时使用装饰器不仅遵循了开放封闭原则,同时也简化了代码,只要给每个函数之前加上@total_time就可以统计其运行时间了。

思考:不用闭包,这样写为什么不行

def total_time(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print("dance函数的运行时间:",stop_time-start_time)
"""
----- 我在跳舞 -----
dance函数的运行时间: 3.0044522285461426
Traceback (most recent call last):
  File "c:\Users\q1994\Desktop\学习\Pyhton\21-装饰器.py", line 29, in <module>
    dance()
TypeError: 'NoneType' object is not callable
"""

 3. 装饰器具体实现

那上边举的例子进行解析:

1. 需要对函数dance进行功能上的添加,创建了闭包函数total_time

def total_time(func):
    def inner():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("dance函数的运行时间:",stop_time-start_time)
    return inner

2. 执行dance()时,会找到dance函数的定义处,查看上面是否存在@total_time

@total_time
def dance():
    time.sleep(3)
    print("-----","我在跳舞","-----")

3. 存在@total_time,就先执行total_time(func),将dance作为实参传递给total_time函数

total_time(dance)

4. total_time函数返回inner函数,并将返回值赋给变量dance,也就是说此时执行的dance()实际上是执行inner(),原来的dance函数已经被放进了inner函数内部了。这样就实现了功能扩展

def inner():
        start_time = time.time()
        dance()
        stop_time = time.time()
        print("dance函数的运行时间:",stop_time-start_time)

5. 解答一下上面的思考题:可以明显发现最后执行dance()实际上是执行inner(),而原本的dance函数被放在了inner内部,巧妙的将功能扩展,很好的保持了代码的整体一致性。 

4. 再举个例子

实现功能:在原来函数的基础上实现身份验证

应用场景:将每一个函数视为独立的功能,使用这些功能时需要进行身份验证,禁止未被授权的人使用这些功能。

def identity(func):
    user = input("请输入账号:")
    password = input("请输入密码:")
    def inner():
        if "ZJ" != user:
            print("账号错误")
        if "123456" != password:
            print("密码错误")
        else:
            print("登录成功")
            func()
    return inner
@identity
def money():
    print("这是一个转账功能")
money()
"""
请输入账号:ZJ
请输入密码:123456
登录成功
这是一个转账功能
"""

上面代码理解装饰器执行行为可理解

1. money作为实参传递给identity函数,money接收identity的返回值inner

2. 调用money()就相当于调用inner()

3. inner内部的func保存的是原来的money函数对象

5. 装饰器的不同用法 

1. 对无参数的函数进行装饰

上述两个例子都是。

2. 对有参数的函数进行装饰

# 原来功能:对输入的浮点数进行加法运算
# 添加功能:将浮点数先向下取整再运算
import math

def int_computer(func):
    def inner(a,b):
        a = math.floor(a)
        b = math.floor(b)
        func(a,b)
    return inner
@int_computer
def float_computer(a,b):
    c = a+b
    print(c)

float_computer(3.12,4.23)

#运行结果:7

已经知道执行float_computer()实际就是执行inner(),故装饰器内部的inner需要接收参数,在inner中对参数处理完后,再将参数传递给原本的float_computer。

3. 对带有return的函数进行装饰

这个是错误的:

import time
def cur_time(func):
    def inner():
        print(time.time())
        func()
    return inner
@cur_time
def run():
    return "我正在跑步----"
print(run())
# 1696734497.0567214
# None

这个是正确的: 

import time
def cur_time(func):
    def inner():
        print(time.time())
        return func()
    return inner
@cur_time
def run():
    return "我正在跑步----"
print(run())
# 1696734605.7440841
# 我正在跑步----

 两者的区别就在于:

#第一种
func()

#第二种
return func()

原因在于:

# 第一种情况相当于
def inner():
    print(time.time())
    run()
#即
def inner():
    print(time.time())
    "我正在跑步----"

#第二种情况相当于
def inner():
    print(time.time())
    return run()
#即
def inner():
    print(time.time())
    return "我正在跑步----"

4. 多个装饰器对同一个函数进行修饰

# 功能实现:输入名字,补全信息
def name(func):
    def inner():
        return func()+'姓名:IU-----'
    return inner
def age(func):
    def inner():
        return func()+'年龄:30'
    return inner

@age
@name
def info():
    return "打印信息-----"
print(info())

多个装饰器修饰同一个函数的具体实现流程:

1. 首先是执行的@age,进入到age函数的闭包函数inner()内部

2. 在age的inner中碰到func()即原来的info函数,就再找到下一个@name

3. 进入到name函数的闭包函数inner()中,碰到func()即原来的info,没有其它的装饰器了,就到此为止,否则继续往下找

4. 此时,age函数的inner()中的func()实际上是name函数中的inner()

5. 最后,执行name函数中的inner(),返回值返回到age函数中的inner()

6. 总的来说,就是一层一层往下调,返回值时从最深层往外返回

5. 装饰器带参数

# 设置睡觉时间
import time
def set_time(timeout):
    def inner1(func):
        def inner2():
            func()
            time.sleep(timeout)
            print("睡醒了-----")
        return inner2
    return inner1
@set_time(5)
def sleep():
    print("开始睡觉-----")
sleep()

 区别就在于最外层函数是用于接收实参的,第二层才接受函数。

6. 用类作为装饰器

上面的例子都是用闭包函数来作为装饰器的,现在讨论使用类作为装饰器的情况。

类作为装饰器必须要实现__call__方法,这样的类创建的对象是callable对象。

Python中callable对象一般都是函数

class A():
    def __call__(self):
        print("该类创建的对象可以作为函数直接执行")
a = A()
a()
# 运行结果:该类创建的对象可以作为函数直接执行

类作为装饰器的例子:

class Test():
    def __init__(self, func):
        print("---初始化---")
        print("func name is %s" % func.__name__)
        self.__func = func
    def __call__(self):
        print("---装饰器中的功能---")
        self.__func()


#说明:
#1. 当用Test来装作装饰器对test函数进行装饰的时候,首先会创建Test的实例对象
#   并且会把test这个函数名当做参数传递到__init__方法中
#   即在__init__方法中的属性__func指向了test指向的函数
#2. test指向了用Test创建出来的实例对象
#3. 当在使用test()进行调用时,就相当于直接调用实例对象,因此会调用这个对象的__call__方法
#4. 为了能够在__call__方法中调用原来test指向的函数体,所以在__init__方法中就需要一个实例属性来保存这个函数体的引用
#   所以才有了self.__func = func这句代码,从而在调用__call__方法中能够调用到test之前的函数体
@Test
def test():
    print("----test---")


test()  # 如果把这句话注释,重新运行程序,依然会看到"--初始化--"

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值