Python装饰器的使用详解

1. 装饰器的定义

Python装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。即Python装饰器的作用是使函数包装与方法包装(一个函数,接受函数并返回其增强函数)变得更容易阅读和理解。

装饰器的使用符合了面向对象编程的开放封闭原则。开放封闭原则主要体现在两个方面:

  1. 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  2. 对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。

2. 装饰器最初的使用场景

最初的使用场景是在方法定义的开头能够将其定义为类方法静态方法
如果不用装饰器语法的话,定义可能会非常稀疏,并且不断重复:

class WithoutDecorators:
  def some_static_method():
    print("this is static method")
  some_static_method = staticmethod(some_static_method)

  def some_class_method(cls):
    print("this is class method")
  some_class_method = classmethod(some_class_method)

如果用装饰器语法重写的话,代码会更简短,也更容易理解:

class WithDecorators:
  @staticmethod
  def some_static_method():
    print("this is static method")
  @classmethod
  def some_class_method(cls):
    print("this is class method")

3. 闭包

在python中,实现装饰器的方式叫做闭包。闭包是指函数中定义了一个内函数,内函数运用了外函数的临时变量,并且外函数的返回值是内函数的引用。

关于闭包的实现,代码示例:

def outer_func():
    a = 1
    def inner_func(b):
        nonlocal a    # 注意这一行
        a += b
        return a
    return inner_func

func = outer_func()
print(func(100))
print(func(1000))

运行结果,如下:

101
1101

在上述代码的示例中,需要引用到nonlocal关键字,nonlocal关键字只能用于嵌套函数中
nonlocal关键字修饰变量后标识该变量是上一级函数中的局部变量,如果上一级函数不存在该局部变量,nonlocal位置会发生错误

闭包的重点

  1. 外函数的内部定义了一个内函数,
  2. 内函数使用了外函数的临时变量,
  3. 外函数的返回值是内函数的引用。

4. 一般语法和可能的实现

装饰器通常是一个命名的对象(不允许使用lambda表达式),在被(装饰函数)调用时接受单一参数,并返回另一个可调用对象(包括函数在内)。这里用的是“可调用(callable)”,而不是之前以为的函数。
事实上,任何可调用对象(任何实现了__call__方法的对象都是可调用的)都可以用作装饰器,它们返回的对象往往也不是简单的函数,而是实现了自己的__call__方法的更复杂的类的实例

4.1 作为一个函数

编写自定义装饰器有许多方法,最简单的方法就是编写一个函数,返回包装原始函数调用的一个子函数。

通用模式如下:

def mydecorator(function):
    def wrapped(*args, **kargs):
        # 在调用原始函数之前,做点什么
        result = function(*args, **kargs)
        # 在函数调用之后,做点什么
        # 并返回结果
        return result
    # 返回wrapper作为装饰函数
    return wrapped

举个例子,在原始函数上增加打印信息,代码示例:

def addlog_decorator(function):
    def wrapped(*args, **kargs):
        print("======== start ========")
        result = function(*args, **kargs)
        print("========  end  ========")
        return result
    return wrapped


@addlog_decorator     # 未携带参数
def foo():
    print("这是原始函数!")

foo()

运行结果,如下:

======== start ========
这是原始函数!
========  end  ========

4.2 作为一个类

虽然装饰器几乎总是可以用函数实现,但在某些情况下,使用用户自定义类可能更好。如果装饰器需要复杂的参数化或者依赖于特定状态,那么这种说法往往是对的。
非参数化装饰器用作类的通用模式,如下:

class DecoratorAsClass:
    def __init__(self,function):
        self.function = function
    def __call__(self, *args, **kargs):
        # 在调用原始函数之前,做点什么
        result = self.function(*args, **kargs)
        # 在函数调用之后,做点什么
        # 并返回结果
        return result

还是拿上面的例子,在原始函数上增加打印信息,代码实现如下:

class DecoratorAsClass():
    def __init__(self, function):
        self.function = function
    def __call__(self, *args, **kargs):
        print("======== start ========")
        result = self.function(*args, **kargs)
        print("========  end  ========")
        return result

@DecoratorAsClass
def foo():
    print("这是原始函数!")

foo()

运行结果,如下:

======== start ========
这是原始函数!
========  end  ========

4.3 参数化装饰器

在实际代码中,通常需要使用参数化的装饰器。如果用函数作为装饰器的话,那么解决方法很简单:需要用到第二层包装。

举个例子,实现重复打印原始函数的功能,代码示例:

def repeat(number=5):
    def mydecorator(function):    # 参数化装饰器
        def wrapper(*args, **kargs):
            result = None
            for _ in range(number):
                result = function(*args, **kargs)
            return result
        return wrapper
    return mydecorator

@repeat(3)
def foo(n):
    '''
    功能描述:重复执行该函数
    '''
    n += 1
    print('打印该函数')
    print('查看n的变化:%d'%n)

foo(6)

这样定义的装饰器可以接受参数
运行结果,如下:

打印该函数
查看n的变化:7
打印该函数
查看n的变化:7
打印该函数
查看n的变化:7

4.4 保存内省的装饰器

使用装饰器的常见错误是在使用装饰器时,不保存函数元数据(主要是文档字符串和原始函数名)。在前面所有的示例都存在这个问题。

装饰器组合创建了一个新函数,并返回一个新对象,但却完全没有考虑原始函数的标识。这将会使得调用这样装饰过的函数更加困难,也会破坏可能用到的大多数自动生成文档的工具,因为无法访问原始的文档字符串函数签名

假设我们有一个虚设的(dummy)装饰器,仅有装饰作用,还有其他一些被装饰的函数:

def dummy_decorator(function):
    def wrapped(*args, **kargs):
        """包装函数内部文档"""
        return function(*args, **kargs)
    return wrapped

@dummy_decorator
def important_docstring():
    """这是我们想要保存的重要文档字符串"""
    pass

print(important_docstring.__name__)
print(important_docstring.__doc__)

从上述的示例中,我们可以看到原始函数important_docstring()的文档字符串和函数签名已经被更改了

为解决这个问题,需要使用functools模块内置的wraps()装饰器
代码示例:

from functools import wraps

def preserving_decorator(function):
    @wraps(function)     # 此处调用wraps()装饰器
    def wrapped(*args, **kargs):
        """包装函数内部文档"""
        return function(*args, **kargs)
    return wrapped

@preserving_decorator
def important_docstring():
    """这是我们想要保存的重要文档字符串"""
    pass

print(important_docstring.__name__)
print(important_docstring.__doc__)

运行结果,如下:

important_docstring
这是我们想要保存的重要文档字符串

调用functolls模块的wraps()装饰器用来装饰wrapped()函数后,即可保留原始函数important_docstring()的文档字符串和函数签名。

有关迭代器、生成器、上下文管理器,以及其他部分语法元素的知识点,欢迎大家阅读下面的链接哦
python高级编程实例(中篇)
请大家多多点赞和支持,感谢!!

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

种树的牧羊人Z

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

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

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

打赏作者

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

抵扣说明:

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

余额充值