python 装饰器@

装饰器概述

装饰器就是在特定条件下为某些函数在不改动函数体的时候为函数新添加一些功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用,装饰器的使用符合了面向对象编程的开放封闭原则,即对扩展开放、对修改封闭。

装饰器基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内置函数),执行函数时再在内层函数中执行闭包中的原函数。

装饰器使用

定义一个函数add,实现了两个数的相加。

def add():
    x = 1
    y = 2
    z = x + y
    print(z)

如果需要计算这些代码的执行时间,应该怎么办呢?一个可行的方法就是在函数体内部加上计算执行时间的代码。

import time


def add():
    start = time.time()
    x = 1
    y = 2
    z = x + y
    print(z)
    end = time.time()
    print("total time:" + str(end - start))

add()

输出结果为:

3
total time:0.0

这样已经违背了开闭原则,如果不想在修改add函数的基础上,来计算该函数的运行时间,又该怎么办呢?我们知道,函数的参数传递的值可以为一个函数,基于这个特性,将源代码修改如下:

import time


def add():
    x = 1
    y = 2
    z = x + y
    print(z)


def count_time(function):
    def wrap():
        start = time.time()
        function()
        end = time.time()
        print("total time:" + str(end - start))

    return wrap


add = count_time(add)
add()

输出结果为:

3
total time:0.0

首先向count_time函数传递一个参数,这个参数的值是一个add函数;然后该count_time函数的返回值是一个wrap函数,这个wrap函数的函数体内包含了add函数,并在此基础上增加了计算时间的代码。这样,我们就在不修改add函数的基础上,实现了计算函数add的运行时间的功能。

但是这样又出现了一个问题,我们每次在调用add函数前,都需要调用一次count_time函数。这时候应该如何解决呢?

如果看过其他python项目里面的代码里,难免会看到@符号,这个@符号就是装饰器的语法糖。因此上面简单的装饰器还是可以通过语法糖来实现的,这样就可以省去add = count_time(add)这一行代码。

import time


def count_time(function):
    def wrap():
        start = time.time()
        function()
        end = time.time()
        print("total time:" + str(end - start))

    return wrap


@count_time
def add():
    x = 1
    y = 2
    z = x + y
    print(z)


add()

输出结果为:

3
total time:0.0

上面我们用函数来实现了一个装饰器,如果用类来实现一个同样功能的装饰器应该怎么做呢?可以通过类的_ _ init _ _ 方法来传入一个add函数,然后通过 _ _ call _ _ 函数来添加相应的功能。

import time


class CountTime:
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        start = time.time()
        self.function(*args, **kwargs)
        end = time.time()
        print("total time:" + str(end - start))


@CountTime
def add():
    x = 1
    y = 2
    z = x + y
    print(z)


add()

输出结果为:

3
total time:0.0

装饰器传参

上面实现了一个简单的装饰器,这个装饰器其实就是一个函数,既然装饰器是一个函数,那么肯定就可以有参数。如何实现一个带有参数的装饰器呢?为了实现传参,我们可以在原函数的基础上,再加入一层函数。

def count_time_args(arg1):
    def count_time(function):
        def wrap():
            start = time.time()
            function()
            end = time.time()
            print("total time:" + str(end - start))
            print(arg1)
        return wrap
    return count_time


@count_time_args(arg1="end")
def add():
    x = 1
    y = 2
    z = x + y


add()

输出结果为:

3
total time:0.0
end

既然函数形式的装饰器能传参,那么类装饰器肯定也能传参,如何向类装饰器传入参数呢?如果带有参数的化,我们不能像函数那么在函数的外面再包装一层,而是将装饰器的参数传入_ _ init _ _ (),而将函数传入_ _ call _ _()。

import time


class CountTime:
    def __init__(self, arg1):
        self.arg1 = arg1

    def __call__(self, function, *args, **kwargs):
        def wrap():
            start = time.time()
            function(*args, **kwargs)
            end = time.time()
            print("total time:" + str(end - start))
            print(self.arg1)
        return wrap


@CountTime(arg1="end")
def add():
    x = 1
    y = 2
    z = x + y
    print(z)


add()

输出结果为:

3
total time:0.0
end

装饰器顺序

如果一个函数同时被多个装饰器所修饰时,这些装饰器的执行顺序是什么样的呢?

def decorator_1(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print('我是装饰器1')

    return wrapper


def decorator_2(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print('我是装饰器2')

    return wrapper


def decorator_3(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print('我是装饰器3')

    return wrapper


@decorator_1
@decorator_2
@decorator_3
def main():
    print("this is a text")


main()

结果为

this is a text
我是装饰器3
我是装饰器2
我是装饰器1

从结果中可以看出,如果一个函数被多个装饰器修饰,那么多个装饰器会按照由内向外的顺序生效。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不负韶华ღ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值