一文带你读懂python装饰器

前言

我的个人网站:https://www.gentlecp.com

python中有一个很经典的用法就是装饰器,它用于在不修改原始函数的情况下,添加新的功能到原始函数中,但是这章内容比较难以理解,本文就从函数到装饰器以及装饰器在现实生产中的应用举出样例,希望能够帮助大家更好地理解装饰器到底有何用处

附:文章个人网站链接

一、函数

谈装饰器前先对函数要有一个深刻理解。在python中,万物皆对象,函数也不例外,我们创建一个函数将其打印出来,看看结果:

def func(message):
    print('Here is func: {}'.format(message))
print(func)

在这里插入图片描述
可以看到返回值为一个内存地址,说明函数是对象,既然是对象,自然可以进行赋值传递,参数传递操作。

1.1 函数作为对象传递

看下面的例子:

def func(message):
    print('Here is func: {}'.format(message))
    
func_object = func
func_object('Hello world')

在这里插入图片描述
我们将func这个对象直接赋值给func_object,用func_object调用,结果成功输出。说明函数可作为对象传递

1.2 函数作为参数传递

看下面的例子:

def func_a(message):
    print('Here is func_a:{}'.format(message))

def func_b(func,message):
    func(message)

func_b(func_a,'Hello world')

在这里插入图片描述
我们将func_a作为func_b的参数传入func_b中,并在func_b中调用该函数,打印输出结果。说明函数可以作为参数传递。

1.3 函数可嵌套

这个大家应该都用过,就是在函数中有时候我们会重复利用一部分代码,而这部分代码在其他地方又不会用到,就可以将这部分整合成一个函数,然后进行调用,看下面的例子:

def func_root(message):
    def func_node(message):
        print('Here is func_node:{}'.format(message))
    # 将func_node结果返回
    return func_node(message)  
func_root('Hello World')

在这里插入图片描述
我们在func_root中又定义了func_node,并在func_root中返回func_node的处理结果。相当于在函数内部调用了内部的函数,说明函数可嵌套。
举一个形象的例子:
老板(func_root)收到外包商一个项目(‘Hello World’)。
外包商:你帮我打印一下Hello World
老板接收原材料(‘Hello World’)。
老板:我不会啊,那什么,小C你帮我打印一下。
然后将原材料(‘Hello World’)扔给小C(= =)。
小C:好的老板,打印了Hello World,写成了报告。
老板一看:哎哟,不错哦。
然后就将结果return给了外包商。

1.4 返回函数本身

前面是内部函数处理了结果,外部函数将结果返回,因为函数本身是对象,自然也可以作为return返回,看下面这个例子:

def func_root():
    def func_node(message):
        print('Here is func_node:{}'.format(message))
    # 将func_node本身返回
    return func_node
func_object = func_root()
func_object('Hello world')

同样用上面的例子:
外包商(func_object):你帮我打印Hello World
老板(func_root):我不会啊,我把会打印的人叫来给你用吧,小C你过来一下
外包商:小C,你给我打印Hello World
小C(func_node):print(…)
在代码中,func_object通过func_root这个桥梁,获取到了func_node的地址,这时候它就可以通过()的形式调用func_node的功能。

二、装饰器

2.1 基础装饰器

我们先看一个最基础的装饰器:

def my_decorator(func):
    def wrapper():
        print("I'm wrapper in my_decorator")
        func()
    return wrapper

@my_decorator
def hello():
    print('hello world')
hello()

其中@my_decorator称为语法糖,其作用等价于

hello = my_decorator(hello)

在这里插入图片描述
从结果可以看出,hello成功执行了自己的功能(打印hello world),装饰器装饰了hello函数,并在其基础上添加了功能(打印I’m wrapper in my_decorator)。装饰器一般有两层函数,外层my_decorator用于@装饰在其他函数上,它返回内层函数(wrapper)的地址,让被装饰函数可以直接获取到该地址。
前面说过,有了函数地址,自然可以调用函数功能,所以wrapper可以看作被装饰函数的加强版函数,在其内部必然要调用被装饰函数,且添加一些想添加的代码功能,这使得添加的功能和原始功能互不干扰。
举个例子:
小C:我只会打印hello world,别的我不学,哼~
老板:什么?这么菜怎么完成任务?不行,我给你个神器,这个神器可以自动打印I’m wrapper in my_decorator,也不用你学什么了。但是你要把它戴到头上。以后我布置打印任务的时候,我会发命令给这个神器,这个神器再提示你怎么做知道吗?
小C:好的,老板!
老板:神器啊神器,开始打印吧~
神器:打印I’m wrapper in my_decorator完成,小C,打印hello world
小C:好嘞,打印hello world。
有人可能要问了,为什么不直接用一个函数wrapper,将hello传入其中,然后调用呢?
注意!装饰器的初衷是不影响原始函数的代码和功能,也就是说。假设原始函数hello是一个接口,别人一直用hello作为接口调用,如果你用wrapper接收hello,那么接口的名称就要改动成wrapper,外面的人并不知道这个,还是用hello调用,就会导致出错!而用装饰器的形式,你发现没有,hello接口没有变,但是新功能已经添加进去了。

2.2 带参装饰器

被装饰函数难免会有参数传入,如何将这些参数一并传入到装饰器中呢?看下面的代码:

def my_decorator(func):
    def wrapper(*args,**kwargs):
        print("I'm wrapper in my_decorator")
        func(*args,**kwargs)
    return wrapper

@my_decorator
def hello(name):
    print('hello world '+name)
    
hello('CP')

这里用到了args,**kwargs,这样就可以接收任意数量或类型的参数,关于args,**kwargs网上有很多解释的文章,这里不多赘述。可以看到成功地进行了参数传递。

2.3 装饰器自定义参数

前面装饰器一直是接收被装饰函数的参数,那么如果装饰器自己要定义参数呢?例如定义装饰器参数num,用于指定装饰器调用的次数,看下面的代码:

def repeat(num):
    def my_decorator(func):
        def wrapper():
            for i in range(num):
                print("I'm wrapper in my_decorator")
                func()
        return wrapper
    return my_decorator

@repeat(5)
def hello():
    print('hello world')
     
hello()

repeat装饰器传入了参数5,用于将他内部的函数执行5次
在这里插入图片描述

2.4 类装饰器

类也可以作为装饰器使用,它依赖于函数__call__,实际上,每次调用类的实例,函数__call__便执行一次。看下面的代码:

class CountClass:
    def __init__(self, func):
        self.func = func
        self.calls = 0

    def __call__(self, *args, **kwargs):
        self.calls += 1
        print('calls: {}'.format(self.calls))
        return self.func(*args, **kwargs)

@CountClass
def hello():
    print("hello world")

hello()
hello()

在这里插入图片描述
本质上作用与函数装饰器相同,根据自己的需要选择用函数装饰器还是类装饰器

2.5 装饰器嵌套

装饰器说到底还是函数,因此装饰器本身也可以被装饰,看下面的例子:

import functools

def my_decorator1(func):
    def wrapper(*args, **kwargs):
        print('Here is decorator1')
        func(*args, **kwargs)
    return wrapper


def my_decorator2(func):
    def wrapper(*args, **kwargs):
        print('Here is decorator2')
        func(*args, **kwargs)
    return wrapper


@my_decorator1
@my_decorator2
def hello(message):
    print(message)
    
hello('hello world')

在这里插入图片描述
由结果可以看出来,执行的顺序是​decorator1 -> decorator2 -> hello

三、装饰器的应用

装饰器的应用场景有很多,例如登录验证,日志记录,合理性检查等。下面以登录验证为例:假设一个网站有两个功能,登录和评论,而评论功能必须要先检查用户是否登录,登录才能使用否则调用评论会提示需要登录,看下面代码:

IS_LOGIN = False  # 全局变量作为是否登录的标志
USER , PWD = 'CP','123456'  # 假设只有一个用户
def require_login(func):
    def wrapper(*args,**kwargs):
        if IS_LOGIN:
            # 验证通过,可以评论
            func(*args,**kwargs)
        else:
            # 验证不通过
            print('验证失败,您未登录')
            login()
    return wrapper

# 登录        
def login():
    global IS_LOGIN
    while True:
        print('请输入登录用户名:')
        user = input()
        print('请输入密码:')
        pwd = input()  # 这里为了简便没有做密码输入加密处理,实际开发需要做
        if user == USER and pwd == PWD:
            print('登录成功')
            IS_LOGIN = True
            break
        else:
            print('用户名或密码输入错误,请重新输入')
        
# 评论功能
@require_login
def comment():
    print('欢迎使用评论功能,请输入你要评论的内容:')
    com = input()
    print('你的评论是:{}'.format(com))

comment()
comment()

运行结果:
在这里插入图片描述
我们定义两个函数和一个装饰器函数,login用于登录,comment用于评论,comment被require_login装饰器装饰,在require_login装饰器中会判断全局变量IS_LOGIN来检查用户是否已经登录,如果登录则执行comment的功能,如果未登录,则提示用户进行登录。
第一次调用comment()提示验证失败,您未登录,并让用户进行登录操作,登录成功后,进入评论的核心功能,用户可以进行评论。
通过上面的例子我们可以发现,comment还是那个comment,我们并没有对其修改,但是让其使用的时候多了一层验证,而且这种方式在不改变接口的同时降低了代码耦合性,使得程序员维护成本大大降低,所以是一种非常值得学习、掌握的方法。

四、总结

关于装饰器的使用,我大概就总结了以上内容,希望能够帮助到你更好地理解装饰器,如果有任何疑问的地方,欢迎在评论区留言或私信给我,我会尽力解答~
建议: 看完文章后一定要自己动手尝试一下才能更好地掌握,不要想当然以为自己看了就会了,动手才是将东西变成自己的的唯一途径!!!

发布了46 篇原创文章 · 获赞 99 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/GentleCP/article/details/92772255