Python-装饰器及高级用法

本文详细介绍了Python装饰器的用法,包括基本语法、标准写法、带参装饰器、类装饰器和类型转换。装饰器通过增强函数或类的功能,如日志记录、用户认证,实现代码复用。文章还探讨了如何使用functools.wraps保持原始函数属性,并展示了如何编写带参装饰器和类装饰器。最后,通过示例展示了装饰器如何用于类型转换,以修改或增强现有类的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    在Python中装饰器的作用毋庸置疑,他能有效解决代码复用等问题,最重要的是他的方式非常类似面向切面编程。装饰器要解决的问题是如日志记录、用户认证等,当然更不仅限于此。

    在讲装饰器前,我们要理解下Python语言的一个特点,那就是在Python里所有的都是对象,包括函数和类。只有在函数和类是对象的前提下,我们才能使用装饰器装饰函数和类。

1. 装饰器语法

下面我们编写一个简单的装饰器:

def decorator(method):
    method.__doc__ += '\n这是个被装饰器装饰过的函数!'
    return method

这是一个简单的装饰器, 它的作用仅仅是给被装饰函数的文档加上一句话,我们先用它装饰一个简单的函数:

@decorator
def foo(x, y):
    """ 这是个foo函数 """
    return x + y

装饰器的语法类似java中的注解,在函数上方写上"@decorator"。foo函数被装饰后,我们看看它的文档_doc_有什么变化:

print(foo.__doc__)

 这是个foo函数 
这是个被装饰器装饰过的函数!

一个函数或类可以被多个装饰器装饰,顺序是由下向上装饰。如下面的函数被装饰顺序依次@decorator_a-->@decorator_b-->@decorator_c。

@decorator_c
@decorator_b
@decorator_a
def foo():
    pass

2. 装饰器的标准写法

有人也许看出来,装饰器有点像是代理方法。确实,它的一些方面确实和代理方法很像,比如装饰器会改变原有函数的属性和方法,返回的可能是变样的函数。看下面的例子:

def base_decorator(method):
    def inner(*args, **kwargs):
        # 判断传入的参数是否全为int类型
        args_list = list(args) + [i for i in kwargs.values()]
        for arg in args_list:
            if not isinstance(arg, int):
                raise ValueError('传入参数不全为int类型')
        return method(*args, **kwargs)
    return inner

@base_decorator
def foo(x, y, z):
    return x + y + z

help(foo)

输出:inner(*args, **kwargs)

 

我们发现help(foo)打印出的是inner(*args, **kwargs),而不是foo(x, y, z),被装饰的函数像是变成了另一个函数,有时候这并不是我们想要要的,要解决这个问题,python官方给出了一个解决方案,使用functools库中的wraps装饰器,使用方法如下:

 

from functools import wraps

def base_decorator(method):
    @wraps(method)
    def inner(*args, **kwargs):
        # 判断传入的参数是否全为int类型
        args_list = list(args) + [i for i in kwargs.values()]
        for arg in args_list:
            if not isinstance(arg, int):
                raise ValueError('传入参数不全为int类型')
        return method(*args, **kwargs)
    return inner

@base_decorator
def foo(x, y, z):
    return x + y + z

help(foo)

输出:foo(x, y, z)

我们发现,wraps函数本身也是一个带参装饰器(后面会讲带参),参数是被装饰的函数,具体wraps做了什么工作,有兴趣的朋友可以自行查找资料看看。

从上面的装饰器我们可以看出它的强大,它可以对被装饰函数的参数进行操作。仔细理解上面的装饰器的写法,这是我们日常比较常用的一种写法。

3. 带参装饰器

上面我们注意到wraps装饰器是一个带参的装饰器,那怎么写一个根据传入的参数来动态装饰函数的装饰器呢?答案很简单,我们看以下的例子:

from functools import wraps
from json import dumps

def json_output(indent=None, sort_keys=False):
    """根据需要对被装饰函数的结果进行json化输出"""
    def decorator(method):
        @wraps(method)
        def inner(*args, **kwargs):
            result = method(*args, **kwargs)
            return dumps(result, indent=indent, sort_keys=sort_keys)
        return inner

    return decorator

@json_output(indent=4)
def do_something():
    return {
        'code': 'success'
    }

print(do_something())

输出:{
    "code": "success"
}

这个带参装饰器的作用是可以根据传入的参数来动态输出json的不同格式。

细心的同学可能已经发现,这个装饰器其实只是在原有的装饰器上再套一个函数,确实如此,这里涉及到一个Python解析代码的方式。Python在解析被装饰函数的时候,会先执行 json_output(indent=4) 这个函数,它会返回一个装饰器 decorator ,再用decorator装饰被装饰的函数。

为了向后兼容,也为了有时候可能不需要参数,不传参时上面的写法会报错。将上面的json_output装饰器修改如下:

def json_output(method=None, indent=None, sort_keys=False):
    # 判断传入参数是否正确,方法和其他参数传入时报错
    if method and (indent or sort_keys):
        raise RuntimeError('传入参数错误!')

    # 以下为普通装饰器
    def decorator(func):
        @wraps(func)
        def inner(*args, **kwargs):
            result = method(*args, **kwargs)
            return dumps(result, indent=indent, sort_keys=sort_keys)

        return inner

    # 此处根据传入的参数有没有method判断是带参装饰器还是普通装饰器
    if method:
        return decorator(method)
    else:
        return decorator

上面这个装饰器,即可以传参也可不传,这里有点复杂,可以只理解写法。

4. 类装饰器

装饰器不仅可以装饰函数,也可以装饰类,也就是类装饰器。

类装饰器的写法如下:

def class_decorator(cls):
    """
    类装饰器,可以用于改变类的基础属性和装饰类方法
    """
    # 保存cls的原有init方法
    original_init = cls.__init__

    # 装饰cls的init方法
    @wraps(original_init)
    def decorate_init_(self, *args, **kwargs):
        # 给类对象加一个创建时间属性
        self.create_time = time()
        original_init(self, *args, **kwargs)

    cls.__init__ = decorate_init_

    return cls


# test
@class_decorator
class DecorateCls:
    pass


d = DecorateCls()
print(d.create_time)

1524624846.831774

class_decorator装饰器只是简单的修改被装饰类的_init_方法,在它创建实例的时候为实例加上一个create_time的属性。

类装饰器的作用可以是添加属性,装属性参数化,或是修改一个类的API,从而使类被声明的方式与实例被使用的方式不同。

5. 类型转换

装饰器的一个更高级的用法是装饰一个函数,运行这个被装饰函数后返回一个类。

想象一个场景,我们已经有一个较完备的类,可我们要修改这个类的一个方法,当然我们可以用继承来实现,可有时候我们也可以用装饰器来实现,看如下的例子:

class Task:
    def run(self, *args, **kwargs):
        raise NotImplementedError('未实现run方法')

    def identify(self):
        return '我是一个Task类'


def task_decorator(method):

    class TaskSubclass(Task):
        def run(self, *args, **kwargs):
            return method(*args, **kwargs)

    return TaskSubclass

task_decorator装饰器返回的是个Task的子类,这个子类实现了run方法,而实际上run方法执行的就是被装饰函数,我们可以简单的只写一个函数却返回一个完整的实现特定方法的类。

上面的写法存在一个问题,看下面示例:

@task_decorator
def foo():
    print('i am a foo')

我们给foo函数上加装饰器后,执行foo()时返回的是一TaskSubclass实例,它并不会马上执行print('i am a foo')这句代码,只我们执行foo().run()时,才会执行foo函数里的代码,这与我们的直觉或是惯用方法有点不符。

但我们也有解决方案,只是相对写得复杂一点,将装饰器修改如下:

class Task:
    def __call__(self, *args, **kwargs):
        return self.run(*args, **kwargs)

    def run(self, *args, **kwargs):
        raise NotImplementedError('未实现run方法')

    def identify(self):
        return '我是一个Task类'


def task_decorator(method):

    class TaskSubclass(Task):
        def run(self, *args, **kwargs):
            return method(*args, **kwargs)

    return TaskSubclass()

 

注意看修改后与修改前的区别,类Task加入了一个_call_方法,加入该方法后,输入Task()()时就会执行_call_方法。而我们的task_decorator装饰器则直接返回TaskSubclass()实例。这时我们再用执行foo()时,就会正常执行foo函数里的代码。

 

对于一些朋友来说,装饰器的语法可能有些不合常理,比如装饰器往往返回一个内构的函数的名称,而不是执行这个内构函数后的结果,有时候返回的是一个类,而不是类的实例,这中间的问题就在于,在Python里,所有的这一切都是对象,在理解了这句话后,再去看之前的装饰器,可能会有不同的体验。

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值