简单易懂的讲一下Python中的高级专题装饰器

废话不说。。。
首先,装饰器是干什么的,自然是装饰用的。

这是很多课程里面都会讲的一句话,这句话理解的人自然懂,但是我们这种小白,就云里雾里了。

其实说白了就是给函数在不更改源代码及其调用方式的情况下,增加函数的一些功能。

那装饰器到底是个啥?
装饰器就是个不一样的函数。

那么在理解装饰器是啥函数之前,我们要先看懂三句话。
1、函数就是变量
这一点在用了很长时间Python人上都不一定会知道,其实函数是个object类型的变量,那么自然也是可以赋值等操作的,也自然可以当做参数进行传递。
2、高阶函数
何为高阶函数? 就是以函数当做变量,或者返回值是函数的函数。
3、嵌套函数
就是函数里面定义函数

懂了这三个东西了之后,其实
装饰器 = 高阶函数 + 嵌套函数

干讲,大家也是不会太仔细去看的,那我们举个栗子。

import time

def f():
	print('嘤嘤嘤')
	time.sleep(2)

f()

这样我们就定义好了一个超简单的函数,就是输出一句话然后在有个延迟。
那么比如说我们现在有了一个需求:要测试函数运行的时间
那么普通方法也是可以实现的。

def new_f():
	start_time = time.time()
	f()
	end_time = time.time()
	print("Consuming time: {:.4f}s".format(end_time - start_time))

new_f()

这样定义一个新函数是完全可以的,但如果你有10个函数呢,或者成百上千上万个呢?定义1万个新函数?
显然这是不可能的。
那么装饰器就是这样一个作用。

import time

def time_counter(func):
    start_time = time.time()
    func()
    end_time = time.time()
    print("Consuming time: {:.4f}s".format(end_time - start_time))

def f():
    print('嘤嘤嘤')
    time.sleep(2)

time_counter(f)  # f是不带括号的,才能作为一个object类型变量

这样我们已经写了半个装饰器了,它与之前的不同,就是我们可以传入函数,使它能够稍微泛化一点。
那么我们之前说了,装饰器是 高阶函数 + 嵌套函数,那么我们想,在time_counter这个函数里,是不是那段计算时间的功能可以封装在一个函数里呢?自然可以。

import time

def time_counter(func):
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print("Consuming time: {:.4f}s".format(end_time - start_time))
    return wrapper

def f():
    print('嘤嘤嘤')
    time.sleep(2)

f = time_counter(f)  # 将time_counter(f)的返回结果返回给f, 实际上就是wrapper --> f
f() 

好了,那么这个装饰器,我们就搞完了,但是大家会发现,在最后重新赋值的方法好像不太顶啊。。
这么搞,一样是很恶心人的(;´д`)ゞ。这个时候就要用到我们Python自有的一个功能:语法糖

import time

def time_counter(func):
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print("Consuming time: {:.4f}s".format(end_time - start_time))
    return wrapper

@time_counter  # 就相当于是f = time_counter(f)
def f():
    print('嘤嘤嘤')
    time.sleep(2)

f()

那么简单的装饰器我们就学会了,但是,如果我们有好几个需求呢?

多重装饰
比如说,现在,产品经理,emmm又来了,说,你这个,啊,我想输出时间,我又想让它输出日志,你再改改,先输出日志,再输出时间。
(╯°Д°)╯︵┻━┻ (不,我不想)

import time

def log(func):
    def wrapper(*args, **kwargs):
        print("Log: Begin call", func.__name__)
        temp = func(*args, **kwargs)
        return temp
    return wrapper

def time_counter(func):
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print("Consuming time: {:.4f}s".format(end_time - start_time))
    return wrapper

@log
@time_counter
def f():
    print('嘤嘤嘤')
    time.sleep(2)

f()

那么其实就是再搞另一个装饰器,二次包装,从下而上进行包装,这个可以想象成是包装纸,最外面的也就是你最先看到的包装纸。

那我们在想想 如果函数有返回值的时候,会出现什么状况。

import time

def time_counter(func):
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print("Consuming time: {:.4f}s".format(end_time - start_time))
    return wrapper

@time_counter
def f():
    time.sleep(2)
    return "嘤嘤嘤"

print("result: ", f())
Consuming time: 2.0001s
result: None

这时候大家会发现,诶嘿,咋输出不太对劲呢。
这一块,大家注意,这时候输出的不是f()的返回值了,而是wrapper的,所以要让wrapper返回才可以。

import time

def time_counter(func):
    def wrapper():
        start_time = time.time()
        temp = func()
        end_time = time.time()
        print("Consuming time: {:.4f}s".format(end_time - start_time))
        return temp
    return wrapper

@time_counter
def f():
    time.sleep(2)
    return "嘤嘤嘤"

print(f())

那如果f()有参数呢?

import time

def time_counter(func):
    def wrapper():
        start_time = time.time()
        temp = func()
        end_time = time.time()
        print("Consuming time: {:.4f}s".format(end_time - start_time))
        return temp
    return wrapper

@time_counter
def f(x):
    time.sleep(2)
    return x

print(f('嘤嘤嘤'))

这时候会报错

TypeError: wrapper() takes 0 positional arguments but 1 was given

诶嘿,又出问题了,这是因为 f 我们要传进去参数,但是wrapper没有参数。
所以切记,下面有参数的时候,上面wrapper也需要修改,wrapper和 f 传的参数不必一样。

import time

def time_counter(func):
    def wrapper(a):   # 在这个地方
        start_time = time.time()
        temp = func(a)  # 还有这个地方,两个地方,需要将参数传进去
        end_time = time.time()
        print("Consuming time: {:.4f}s".format(end_time - start_time))
        return temp
    return wrapper

@time_counter
def f(x):
    time.sleep(2)
    return x

print(f('嘤嘤嘤'))

那。。我们有两个参数呢。。。
同样的wrapper需要给它两个参数,在原先的两个地方加进去就可以了,包括*args和**kwargs也是一样的,不管有几个参数,统一这样写,是绝对没问题的。

import time

def time_counter(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        temp = func(*args, **kwargs)
        end_time = time.time()
        print("Consuming time: {:.4f}s".format(end_time - start_time))
        return temp
    return wrapper

@time_counter
def f(x, y):
    time.sleep(2)
    return x

print(f('嘤嘤嘤', 1))

那么我们在来看,如果说,产品经理又来了。。。。

 
又改需求
他说,这个日志,他想自定义。。他想动态显示。。
那我们再改。。

def log(text):  # 传入一个文本数据
    def decorator(func):  # 然后嵌套一个装饰器
        def wrapper(*args, **kwargs):
            print(text)
            temp = func(*args, **kwargs)
            return temp
        return wrapper
    return decorator

@log("日志")  # 这个很关键,我们需要输入内容了,就是text的内容
def f(x, y):
    return x

print(f("嘤嘤嘤", 1))

嵌套里面再嵌套,这时候只要去修改“日志”这个里面的内容就可以,改变其日志显示了。
禁止套娃
那我们的装饰器就写完了,有什么不会的话,欢迎评论或私聊。(`・ω・´)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值