前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python 装饰器再探

Python 装饰器再探

作者头像
用户6021899
发布2021-08-20 15:01:39
3530
发布2021-08-20 15:01:39
举报
文章被收录于专栏:Python编程 pyqt matplotlib
  • 参数化装饰器

在实际代码中可能需要使用参数化的装饰器。如果用函数作为装饰器的话,那么解决方法很简单:再增加一层包装。例如:

代码语言:javascript
复制
def repeat(number=3):
    """多次重复执行原始函数,返回最后一次调用的值作为结果"""
    def actual_decorator(function):
        def wrapper(*args, **kwargs):
            result = None
            for i in range(number):
                result = function(*args, **kwargs)
            return result
        return wrapper
    return actual_decorator


@repeat()  # 即使有默认参数,这里的括号也不能省略
def f():
    print("喵")


@repeat(2)
def g(a,b):
    print("g函数执行中")
    return a + b


if __name__ == "__main__":
    f()
    print()
    print(g(1, 2))
  • 保存原始函数的文档字符串和函数名

看下面的例子:

代码语言:javascript
复制
def dumy_dec(function):
    def wrapped(*args, **kwargs):
        """包装函数内部文档"""
        return function(*args, **kwargs)
    return wrapped


@dumy_dec
def function():
    """原始的文档字符串"""
if __name__ == "__main__":
    print(function.__name__)
    print(function.__doc__)

使用装饰器后,我们如果想查看原始函数的函数名或原始函数的文档字符串,返回的却是:

代码语言:javascript
复制
wrapped
包装函数内部文档

解决这个问题的正确办法,是使用functools模块内置的wraps()装饰器。

代码语言:javascript
复制
from functools import wraps


def preserving_dec(function):
    @wraps(function)  # 注意看这里!
    def wrapped(*args, **kwargs):
        """包装函数内部文档"""
        return function(*args, **kwargs)
    return wrapped


@preserving_dec
def function():
    """原始的文档字符串"""
if __name__ == "__main__":
    print(function.__name__)
    print(function.__doc__)

结果如下:

代码语言:javascript
复制
function
原始的文档字符串
  • 用类来实现装饰器

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()方法,那么这个对象就是callable的。由此使用用户自定义的类也可以实现装饰器:

代码语言:javascript
复制
class Decorator:
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        print("Do something here before call The original function")
        print(self.function.__doc__)
        result = self.function(*args, **kwargs)
        print("Do something here after call The original function")
        return result


@Decorator
def add(a, b):
    """原始函数的文档字符串"""
    print("The original function is running")
    return a + b


if __name__ == "__main__":
    add(1, 2)
  • 不能装饰静态方法和类方法
代码语言:javascript
复制
from datetime import datetime


def logging(func):
    def wrapper(*args, **kwargs):
        """print log before a function."""
        print("[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__))
        return func(*args, **kwargs)
    return wrapper


class Car(object):
    def __init__(self, model):
        self.model = model

    @logging  # 装饰实例方法,OK
    def run(self):
        print(f"{self.model} is running!")

    @logging  # 装饰静态方法,Failed
    @staticmethod
    def check_model_for(obj):
        if isinstance(obj, Car):
            print(f"The model of your car is {obj.model}")
        else:
            print(f"{obj} is not a car!")


car = Car("麒麟")
car.run()
Car.check_model_for(car)

会报错:

代码语言:javascript
复制
[DEBUG] 2021-08-05 23:29:02.687998: enter run()
Traceback (most recent call last):
麒麟 is running!
  File "E:/Python36/MyPythonFiles/myPygame/dec7.py", line 31, in <module>
    Car.check_model_for(car)
  File "E:/Python36/MyPythonFiles/myPygame/dec7.py", line 7, in wrapper
    print("[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__))
AttributeError: 'staticmethod' object has no attribute '__name__'

@staticmethod 这个装饰器,其实返回的并不是一个callable对象,而是一个staticmethod 对象,那么它是不符合装饰器要求的(比如传入一个callable对象),你自然不能在它之上再加别的装饰器。要解决这个问题很简单,只要把你的装饰器放在@staticmethod 之前就好了:

代码语言:javascript
复制
@staticmethod
@logging  # 先装饰就没问题
def check_model_for(obj):
    if isinstance(obj, Car):
        print(f"The model of your car is {obj.model}")
    else:
        print(f"{obj} is not a car!")
  • 使用第三方库来优化你的装饰器

decorator.py 是一个非常简单的装饰器加强包。你可以很直观的先定义包装函数wrapper(),再使用decorate(func, wrapper)方法就可以完成一个装饰器。

代码语言:javascript
复制
from decorator import decorate
from datetime import datetime


def wrapper(func, *args, **kwargs):
    """print log before a function."""
    print("[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__))
    return func(*args, **kwargs)


def logging(func):
    return decorate(func, wrapper)  # 用wrapper装饰func
@logging
def f(a, b):
    return a + b


f(1,2)

你也可以使用它自带的@decorator装饰器来完成你的装饰器。

代码语言:javascript
复制
from decorator import decorator
from datetime import datetime


@decorator
def logging(func, *args, **kwargs):
    print(f"[DEBUG] {datetime.now()}: enter {func.__name__}()")
    return func(*args, **kwargs)

@logging
def f(a, b):
    return a + b


f(1,2)

decorator.py实现的装饰器能完整保留原函数的name,doc和args。

也可以使用第三方库wrapt:

代码语言:javascript
复制
import wrapt


@wrapt.decorator  # without argument in decorator
def logging(wrapped, instance, args, kwargs):  # instance is must
    print(f"[DEBUG]: enter {wrapped.__name__}()")
    return wrapped(*args, **kwargs)


@logging
def say(something):
    print(f"is saying {something}")


say("喵")

使用wrapt你只需要定义一个装饰器函数,但是函数签名是固定的,必须是(wrapped, instance, args, kwargs),注意第二个参数instance是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据instance的值你能够更加灵活的调整你的装饰器。另外,args和kwargs也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。

如果你需要使用wrapt写一个带参数的装饰器,可以这样写:

代码语言:javascript
复制
def logging(level):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print("[{}]: enter {}()".format(level, wrapped.__name__))
        return wrapped(*args, **kwargs)
    return wrapper


@logging(level="INFO")
def say(something):

print(f"is saying {something}")



say("喵")
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python可视化编程机器学习OpenCV 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档