深入理解python装饰器

前言

你学习python的时候一定也遇到了装饰器,但是很多学习教程对装饰器的解释都是绕来绕去的,把初学者都给绕的云里雾里,我想读者你一定有类似的经历对吧?不过不要着急,只要你仔细用心的观看我这篇文章之后,你对python装饰器就完全理解了。相信我,我是想将复杂的问题简单的讲解出来,分享给更多的人,如果还有什么问题,可以留言告诉我,我们一起进步。

本文主要能够回答以下问题。

  1. 什么是装饰器
  2. 装饰器的深入理解
  3. 装饰器的作用
  4. 装饰器的使用场景

基础知识

装饰器,其实就是将一个函数作为参数传递给另外一个函数,先来看一段代码

def before_hi(func):
    print("before executing hi()")
    func()

def hi():
    print("hi changpzh!")

before_hi(hi)

#outputs:
# before executing hi()
# hi changpzh!

解释:

其实这段代码对大多数人来说应该都是能轻易的看的懂的。 

1. 首先定义了两个函数,一个是hi,一个是before_hi,before_hi接受一个函数作为参数。

2. 所以执行before_hi(hi),意思就是将hi这个函数作为参数传递给了before_hi

3. 因此打印会是先before hi(),然后是hi

但是,他和我们的装饰器有什么关系呢?别慌,听我慢慢道来

我先把上面的函数做简单的改造, before_hi函数做了一层封装,此时返回的就是一个封装后的函数wrap_function了。

def before_hi(a_func):
    def wrap_function():
        print("before executing hi()")
        a_func()
    return wrap_function


def hi():
    print("hi changpzh!")

hi = before_hi(hi)

hi()
# outputs:
# before executing hi()
# hi changpzh!

解释:

1. 首先当hi = before_hi(hi)执行的时候,实际上是将hi这个函数作为参数传递给了before_hi,然后在before_hi中运行,before_hi此时返回一个wrap_function回去。

2. hi()相当于在运行wrap_function()了。然后进行了对应的打印

当然此时看起来还是和我们的装饰器没有一点关系,不过再看我对上面代码进行一些变化,以便让你真正的理解装饰器。我用装饰器来代替其中一行代码用`@before_hi 代替 hi = before_hi(hi)`,看以下代码

def before_hi(a_func):
    def wrap_function():
        print("before executing hi()")
        a_func()
    return wrap_function

@before_hi            # 注意位置 等价于 hi = before_hi(hi)
def hi():
    print("hi changpzh!")


hi()
# outputs:
# before executing hi()
# hi changpzh!

解释:

这里代码和之前代码只有很小的区别,就是 @before_hi 代替 hi = before_hi(hi)。但是执行之后得到的效果却是一样的。

说明装饰器@的作用,就是将函数作为参数传递给装饰器函数。

也就是说在这里 @before_hi 等价 hi = before_hi(hi),函数hi就被wrap_function代替了。

什么是装饰器?

就是将一个函数作为参数传递给另一个函数,就是装饰器。

装饰器深入理解

我们再来进一步了解装饰器,通过hi.__name__可以看到输出结果为wrap_function,参看下图,这里的hi函数名,被wrap_function代替了,这不是我们想要的,我们想要的是hi.__name__输出为hi。

那么我们怎么办呢? 不慌,好在python提供了一个简单的函数来解决这个问题,那就是functiontools.wraps,我们修改上面例子,通过使用functiontools.wraps,代码如下

from functools import wraps

def before_hi(a_func):
    @wraps(a_func)
    def wrap_function():
        print("before executing hi()")
        a_func()
    return wrap_function

@before_hi
def hi():
    print("hi changpzh!")


# hi()
print(hi.__name__)
# outputs:
# hi

此时的hi.__name__就是hi了,看下图调试结果。

当然如果不用functiontools.wraps,还会改变函数的注释文档,所以建议用装饰器时,使用functiontools.wraps

装饰器的作用

在不改变原函数的情况下,可以对原函数的功能进行扩展。比如授权,日志等。

举例,我想在每次需要打印的时候,都能像下面一样在打印的字体外面包装两行*号。

 代码

from functools import wraps


def print_star(func):
    @wraps(func)
    def star_print(*args, **kwargs):
        print("*" * 50)
        func(*args, **kwargs)
        print("*" * 50)

    return star_print


@print_star
def hello(name):
    print(f"{name},欢迎来到装饰件世界")

hello('changpzh')

装饰器的使用场景

授权

装饰器能有助于检查某个⼈是否被授权去使⽤⼀个web应⽤的端点(endpoint)。它们被⼤量
使⽤于Flask和Django web框架中。这⾥是⼀个例⼦来使⽤基于装饰器的授权

from functools import wraps
def requires_auth(func):
    @wraps(func)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return func(*args, **kwargs)
    return decorated

 日志

⽇志是装饰器运⽤的另⼀个亮点,举例

from functools import wraps

def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called" + "args is" + str(args) + " kwargs is " + str(kwargs)
            with open(logfile, mode='a') as opened_file:
                opened_file.write(log_string + '\n')

        return wrapped_function
    return logging_decorator


@logit()
def func1():
    print("test")

@logit('func2.log')
def func2():
    pass

func1("first", 2, age=30, name="changpzh")
func2()

解释:

注意这里的装饰器使用,和之前的有很大的一个差异,在于,之前使用是@logit,但是这里是@logit(),这里多了一个括号,意味着代码运行到这里的时候,实际上是执行了一次函数logit的。

所以:代码运行过程中

1. 当运行到@logit()时,首先执行logit函数,返回了logging_decorator这个装饰器函数

2. 因此,在运行接下来的函数def func1()时,执行了logging_decorator(func),并且返回wrapped_function给func1. 此时func1就等价为wrapped_function了。

3. 运行func1时,就是运行wrapped_function,传入的参数就会给到*args, **kwargs。结果如下所示。

4. 同理运行func2

装饰器类实现

from datetime import datetime


class Logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile

    def __call__(self, func):  # 在函数定义的时候调用
        # 打印日志
        self.print_log(func)
        return func

    def print_log(self, func):
        log_string = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f ") + func.__name__ + " was called"
        print(log_string)
        # 打开logfile并写⼊
        with open(self.logfile, 'a') as opened_file:
            # 现在将⽇志打到指定的⽂件
            opened_file.write(log_string + '\n')



@Logit()    # 这里会调用 __init__
def func1():  # 这里会调用 __call__
    print(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f ")} func1 was really called')
    pass


@Logit('func2.log')
def func2():
    print(f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f ")} func2 was really called')
    pass


func1()
func2()

运行结果如下所示:

好了,今天的装饰器就讲解到这里,相信你看完这篇文章之后,对装饰的运行原理应该很清楚了。再次遇到装饰器时,你也有足够的信心相信自己可以征服她了。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木瓜~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值