Python专家编程系列: 2. 装饰器介绍(Python Decorator)

本文详细介绍了Python装饰器的概念和使用方法,包括简单装饰器、带参数的装饰器、复杂示例、获取函数名称、类装饰器以及带状态的装饰器。通过多个例子展示了如何创建和应用装饰器,帮助读者掌握这一强大的编程技巧。
摘要由CSDN通过智能技术生成

0. 标题

Python专家编程系列: 2. 装饰器介绍(Python Decorator)

读这个文章之前,先看一下我博客里面讲闭包的文章: Python闭包(Python Closures)介绍

作者: quantgalaxy@outlook.com   
欢迎交流   

1. What 什么是装饰器

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

装饰器的特点如下:

  1. 装饰器是闭包的一种应用,是返回值为函数的高阶函数;
  2. 装饰器修饰可调用对象,也可以带有参数和返回值;
  3. 装饰器中可以保持状态。

2. How 如何使用装饰器

2.1 简单的使用

举一个最简单的使用:

def outer(func):
    def inner():
        print('Before func()..')
        func()
        print('After func()..')
    return inner

@outer
def hi():
    print('Hi World')
    
# 装饰器的作用等同于: hi = outer(hi)

执行一下,输出是:

hi()

# 输出:
# Before func()..
# Hi World
# After func()..

通过上面的例子可以看出来,outer函数定义了一个闭包,返回了一个函数。
@outer的方式,是把下面的函数,当成参数,传给outer,实现了原有hi()的一个包装功能,
实际的作用是:hi = outer(hi)

2.2 带参数的使用

def outer(func):
    def inner():
        return func()
    return inner

@outer
def haha(name):
    return 'Haha ' + name

这样使用,会报错,因为传入的函数需要参数:

print(haha('Bob'))

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

你可以给 inner 函数加一个参数,但这样又不能适用无参数的函数了。
正确的解决方案是这样,利用Python 有 *args 和 **kwargs 可以接收任意数量的位置参数和关键字参数:

def outer(func):
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@outer
def haha(name):
    return 'Haha ' + name

@outer
def hehe():
    return 'Hehe'

执行结果:

print(haha('Bob'))
# 输出:
# Haha Bob

print(hehe())
# 输出:
# Hehe

2.3 复杂一些的例子:

def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。
我们要借助Python的@语法,把decorator置于函数的定义处:

@log
def now():
    print('2015-3-25')

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

>>> now()
call now():
2015-3-25

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。
在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

有的时候装饰器本身也需要接收参数,从而配置为不同的状态,比如打印日志时附带当前的text信息。
你要记得,不管怎么变化,装饰器必须返回一个函数。
既然这里的装饰器多了一对括号,那就是多了一层调用,
所以必须在之前无参数的情况下再增加一层的函数嵌套,也就是三层嵌套的函数:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

这个3层嵌套的decorator用法如下:

@log('execute')
def now():
    print('2015-3-25')

执行结果如下:

>>> now()
execute now():
2015-3-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的: >>> now = log(‘execute’)(now)
我们来剖析上面的语句,首先执行log(‘execute’),返回的是decorator函数,
再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

2.4 获取函数名称

Python 具有强大的 自省能力, 即对象在运行时了解自身属性的能力。
比如,函数知道自己的名字:

def foo():
    pass

print(foo.__name__)

# 输出:
# foo

但是如果用装饰器包装后,返回的名字就是内部函数的名字,因此函数的身份就变得混乱了:

def outer(func):
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@outer
def my_func():
    pass

print(my_func.__name__)
# 输出:
# inner

对于要记录原有函数的名字, Python 有内置的解决方案:

import functools

def outer(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@outer
def my_func():
    pass

print(my_func.__name__)

# 输出:
# my_func

甚至解决方案本身就是个 @wraps() 装饰器。
具体实现原理就不在这里解释了,可以具体查看functools.wraps的说明。

作者: quantgalaxy@outlook.com   
欢迎交流   

2.5 类作为装饰器

虽然前面例子里的装饰器都是函数,但是装饰器语法其实并不要求本身是函数,而只要是一个可调用对象即可。
既然如此,那我只要在类里实现了 call() 方法,岂不是类实例也可以做装饰器?

import functools

class Logit():
    def __init__(self, name):
        self.name = name

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            value = func(*args, **kwargs)
            print(f'{self.name} is calling: ' + func.__name__)
            return value
        return wrapper

@Logit(name='Dusai')
def a_func():
    pass

a_func()

# 输出:
# Dusai is calling: a_func

2.6 带状态的装饰器

结合闭包的原理,和装饰器配合使用,就可以实现状态的记录:

import functools

def counter(func):
    count = 0
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        nonlocal count
        count += 1
        print(count)
        return func(*args, **kwargs)
    return wrapper

@counter
def whatever():
    pass

whatever()
whatever()
whatever()

# 输出:
# 1
# 2
# 3

通常闭包可以使用自由变量,但是不能修改其值。
因此这里用 nonlocal 表明 count 不是内层函数的局部变量,并优先在与闭包作用域最近的自由变量中寻找 count 变量。

2.7 类的装饰器

装饰器不仅可以作用于函数,同样也可以作用于类:

import functools

def logit(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('-' * 10)
        print('Calling: ' + func.__name__)
        value = func(*args, **kwargs)
        print('-' * 10)
        return value
    return wrapper

@logit
class Tester():
    def __init__(self):
        print('__init__ ended')

    def a_func(self):
        print('a_func ended')

执行的时候,你会发现,装饰器,只在类实例化时候生效,后面类函数的调用,装饰器是不生效的:

tester = Tester()
tester.a_func()

# 输出
# ----------
# Calling: Tester
# __init__ ended
# ----------
# a_func ended

更多关于类装饰器的学习,参见: 像专家一样使用Python类装饰器(Python Class Decorators)

作者: quantgalaxy@outlook.com   
欢迎交流   

3. One More Thing

3.1 一个经典的装饰器模版

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 原函数运行前
        # Do something
        value = func(*args, **kwargs)
        # 原函数运行后
        # Do something
        return value
    return wrapper

3.2 一个计时的例子

import functools
import time

def time_it(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        #
        value = func(*args, **kwargs)
        #
        end = time.perf_counter()
        duration = end - start
        print(f'Duration: {duration}')
        return value
    return wrapper

@time_it
def another_func():
    time.sleep(1)

another_func()

# 输出:
# Duration: 1.004140400000324

4. 作者信息

作者: quantgalaxy@outlook.com   
欢迎交流   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

rockwood573

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

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

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

打赏作者

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

抵扣说明:

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

余额充值