装饰器听了N次也没印象,读完这篇你就懂了

简介: 装饰器听了N次也没印象,读完这篇你就懂了

装饰器其实一直是我的一个"老大难"。这个知识点就放在那,但是拖延症。。。


其实在平常写写脚本的过程中,这个知识点你可能用到不多


但在面试的时候,这可是一个高频问题。


一、什么是装饰器


所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。


这一句话理解起来可能没那么轻松,那先来看一个"傻瓜"函数。


放心,绝对不是"Hello World"!


def hello():
    print("你好,装饰器")


肿么样,木骗你吧? 哈哈,这个函数不用运行相信大家都知道输出结果:"你好,装饰器"


那如果我想让hello()函数再实现个其他功能,比如多打印一句话。


那么,可以这样"增强"一下:


def my_decorator(func):
    def wrapper():
        print("这是装饰后具有的新输出")
        func()
    return wrapper
def hello():
    print("你好,装饰器")
hello = my_decorator(hello)
hello()


运行结果:


这是装饰后具有的新输出
你好,装饰器
[Finished in 0.1s]


很显然,这个"增强"没啥作用,但是可以帮助理解装饰器。


当运行最后的hello()函数时,调用过程是这样的:


  1. hello = my_decorator(hello)中,变量hello指向的是my_decorator()
  2. my_decorator(func)中传参是hello,返回的wrapper,因此又会调用到原函数hello()
  3. 于是乎,先打印出了wrapper()函数里的,然后才打印出hello()函数里的


那上述代码里的my_decorator()就是一个装饰器。


它改变了hello()的行为,但是并没有去真正的改变hello()函数的内部实现。

但是,python一直以"优雅"被人追捧,而上述的代码显然不够优雅。


二、优雅的装饰器


所以,想让上述装饰器变得优雅,可以这样写:


def my_decorator(func):
    def wrapper():
        print("这是装饰后具有的新输出")
        func()
    return wrapper
@my_decorator
def hello():
    print("你好,装饰器")
hello()


这里的@my_decorator就相当于旧代码的hello = my_decorator(hello)@符号称为语法糖。


那如果还有其他函数也需要加上类似的装饰,直接在函数的上方加上@my_decorator就可以,大大提高函数


的重复利用与可读性。


def my_decorator(func):
    def wrapper():
        print("这是装饰后具有的新输出")
        func()
    return wrapper
@my_decorator
def hello():
    print("你好,装饰器")
@my_decorator
def hello2():
    print("你好,装饰器2")
hello2()


输出:


这是装饰后具有的新输出
你好,装饰器2
[Finished in 0.1s]


三、带参数的装饰器


1. 单个参数


上面的只是一个非常简单的装饰器,但是实际场景中,很多函数都是要带有参数的,比如hello(people_name)。


其实也很简单,要什么我们就给什么呗,直接在对应装饰器的wrapper()上,加上对应的参数:


def my_decorator(func):
    def wrapper(people_name):
        print("这是装饰后具有的新输出")
        func(people_name)
    return wrapper
@my_decorator
def hello(people_name):
    print("你好,{}".format(people_name))
hello("张三")


输出:


这是装饰后具有的新输出
你好,张三
[Finished in 0.1s]


2. 多个参数


但是还没完,这样虽然简单,但是随之而来另一个问题:因为并不是所有函数参数都是一样的,


当其他要使用装饰器的函数参数不止这个一个肿么办?比如:


@my_decorator
def hello3(speaker, listener):
    print("{}对{}说你好!".format(speaker, listener))


没关系,在python里,*args**kwargs表示接受任意数量和类型的参数,所以我们可以这样


写装饰器里的wrapper()函数:


def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("这是装饰后具有的新输出")
        func(*args, **kwargs)
    return wrapper
@my_decorator
def hello(people_name):
    print("你好,{}".format(people_name))
@my_decorator
def hello3(speaker, listener):
    print("{}对{}说你好!".format(speaker, listener))
hello("老王")
print("------------------------")
hello3("张三", "李四")


同时运行下hello("老王"),和hello3("张三", "李四"),看结果:


这是装饰后具有的新输出
你好,老王
------------------------
这是装饰后具有的新输出
张三对李四说你好!
[Finished in 0.1s]


3. 自定义参数


上面2种,装饰器都是接收外来的参数,其实装饰器还可以接收自己的参数。


比如,我加个参数来控制下装饰器中打印信息的次数:


def count(num):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(num):
                print("这是装饰后具有的新输出")
                func(*args, **kwargs)
        return wrapper
    return my_decorator
@count(3)
def hello(people_name):
    print("你好,{}".format(people_name))
hello("老王")


注意,这里count装饰函数中的2个return.


运行下,应该会出现3次:


这是装饰后具有的新输出
你好,老王
这是装饰后具有的新输出
你好,老王
这是装饰后具有的新输出
你好,老王
[Finished in 0.1s]


4. 内置装饰器@functools.wrap


现在多做一步探索,我们来打印下下面例子中的hello()函数的元信息:


def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("这是装饰后具有的新输出")
        func(*args, **kwargs)
    return wrapper
@my_decorator
def hello(people_name):
    print("你好,{}".format(people_name))
print(hello.__name__) #看下hello函数的元信息


输出:


wrapper


这说明了,它不再是以前的那个 hello() 函数,而是被 wrapper() 函数取代了。


如果我们需要用到元函数信息,那怎么保留它呢?这时候可以用内置装饰器@functools.wrap


import functools
def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("这是装饰后具有的新输出")
        func(*args, **kwargs)
    return wrapper
@my_decorator
def hello(people_name):
    print("你好,{}".format(people_name))
print(hello.__name__)


运行下:


hello
[Finished in 0.1s]


四、类装饰器


装饰器除了是函数之外,也可以是类。


但是类作为装饰器的话,需要依赖一个函数__call__(),当调用这个类的实例时,函数__call__()

会被执行。


来改造下之前的例子,把函数装饰器改成类装饰器:


class MyDecorator():
    def __init__(self, func):
        self.func = func
    def __call__(self, *args, **kwargs):
        print("这是装饰后具有的新输出")
        return self.func(*args, **kwargs)
# def my_decorator(func):
#     def wrapper():
#         print("这是装饰后具有的新输出")
#         func()
#     return wrapper
@MyDecorator
def hello():
    print("你好,装饰器")
hello()


运行:


这是装饰后具有的新输出
你好,装饰器
[Finished in 0.1s]


跟函数装饰器一样,实现一样的功能。


五、装饰器的嵌套


既然装饰器可以增强函数的功能,那如果有多个装饰器,我都想要怎么办?


其实,只要把需要用的装饰器都加上去就好了:


@decorator1
@decorator2
@decorator3
def hello():
    ...


但是要注意这里的执行顺序,会从上到下去执行,可以来看下:


def my_decorator(func):
    def wrapper():
        print("这是装饰后具有的新输出")
        func()
    return wrapper
def my_decorator2(func):
    def wrapper():
        print("这是装饰后具有的新输出2")
        func()
    return wrapper
def my_decorator3(func):
    def wrapper():
        print("这是装饰后具有的新输出3")
        func()
    return wrapper
@my_decorator
@my_decorator2
@my_decorator3
def hello():
    print("你好,装饰器")
hello()


运行


这是装饰后具有的新输出
这是装饰后具有的新输出2
这是装饰后具有的新输出3
你好,装饰器
[Finished in 0.1s]


好记性不如烂笔头,写一下理解一下会好很多。

相关文章
|
8月前
|
设计模式 前端开发 JavaScript
当面试官问你什么是观察者模式的时候,用这篇文章去回答他!
当面试官问你什么是观察者模式的时候,用这篇文章去回答他!
|
6月前
|
安全 Java 容器
第一篇:并发容器学习开篇介绍
第一篇:并发容器学习开篇介绍
50 4
|
8月前
|
机器学习/深度学习 人工智能 自然语言处理
技术小白能看懂的ChatGPT原理介绍
网上有关 ChatGPT 的原理介绍文章一大堆,要么是从 NLP 的历史开始讲起,要么是上数 GPT 3 代,内容都相对冗长和复杂。其实 ChatGPT 的原理并不难理解,我将以最通俗易懂的方式为技术小白解读,帮助大家更好地了解这一技术
635 1
技术小白能看懂的ChatGPT原理介绍
|
安全 算法 Java
Java并发编程73道面试题及答案 —— 这也太棒了(三)
Java并发编程73道面试题及答案 —— 这也太棒了
|
存储 安全 算法
Java并发编程73道面试题及答案 —— 这也太棒了(二)
Java并发编程73道面试题及答案 —— 这也太棒了
|
存储 缓存 安全
Java并发编程73道面试题及答案 —— 这也太棒了(一)
Java并发编程73道面试题及答案 —— 这也太棒了
|
机器学习/深度学习 移动开发 前端开发
想加入大厂?看这篇文章也许会帮助到你
相信加入互联网大厂是每个程序员梦寐以求的事情,无论是从工作环境、员工福利,或者说是技术氛围以及接触到的人所给你带来的一些好的机遇,都是值得我们去追求的,因此程序员可以在职业生涯初期、或者在整个职业生涯中加入过大厂,无论对自己的履历还是阅历都是很有帮助的一件事。
109 0
想加入大厂?看这篇文章也许会帮助到你
想学python函数,这篇笔记你学废了吗?
执行特定任务和完成特定功能的一段代码。
想学python函数,这篇笔记你学废了吗?
|
开发者 Python
一日一技:8行炫技代码,知识点多得不得了
一日一技:8行炫技代码,知识点多得不得了
296 0
一日一技:8行炫技代码,知识点多得不得了