您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

使用类与函数的Python装饰器最佳实践

使用类与函数的Python装饰器最佳实践

说每种方法是否都具有“优势”是相当主观的。

但是,如果对幕后的事物有一个很好的了解,那么自然就可以为每种场合选择最佳选择。

装饰器(谈论函数装饰器)只是一个函数为输入参数的可调用对象。Python有其相当有趣的设计,它允许除功能以外还可以创建其他类型的可调用对象- 并且可以将其用于创建更多可维护的或更短的代码

装饰器在Python 2.3中作为“语法快捷方式”添加

def a(x):
   ...

a = my_decorator(a)

除此之外,当我们使用这种类型时,我们通常将装饰器称为“装饰器”,而不是“装饰器工厂”。

@my_decorator(param1, param2)
def my_func(...):
   ...

使用param1和param2调用“ my_decorator”-然后返回将再次调用的对象,这次以“ my_func”作为参数。因此,在这种情况下,从技术上讲,“ decorator”是“ my_decorator”返回的值,从而使其成为“ decorator factory”。

现在,如上所述的装饰器或“装饰器工厂”通常必须保留一些内部状态。在第一种情况下,它唯一保留的是对原始函数的引用(f在示例中称为变量)。“装饰器工厂”可能想要注册其他状态变量(在上面的示例中为“ param1”和“ param2”)。

在装饰器编写为函数的情况下,此额外状态保留在封闭函数内的变量中,并由实际包装函数作为“非局部”变量进行访问。如果编写了适当的类,则可以将它们作为实例变量保存在装饰器函数(将被视为“可调用对象”,而不是“函数”)中,并且对它们的访问更加明确并且更具可读性。

因此,在大多数情况下,是否选择一种方法还是一个可读性问题:对于简短的装饰器而言,功能方法通常比一个类编写的方法更具可读性-有时更精细- 尤其是一种“装饰工厂”将充分利用Python编码之前的“扁平优于嵌套”建议。

考虑:

def my_dec_factory(param1, param2):
   ...
   ...
   def real_decorator(func):
       ...
       def wraper_func(*args, **kwargs):
           ...
           #use param1
           result = func(*args, **kwargs)
           #use param2
           return result
       return wraper_func
   return real_decorator

针对这种“混合”解决方案:

class MyDecorator(object):
    """Decorator example mixing class and function deFinitions."""
    def __init__(self, func, param1, param2):
        self.func = func
        self.param1, self.param2 = param1, param2

    def __call__(self, *args, **kwargs):
        ...
        #use self.param1
        result = self.func(*args, **kwargs)
        #use self.param2
        return result

def my_dec_factory(param1, param2):
    def decorator(func):
         return MyDecorator(func, param1, param2)
    return decorator

:缺少装饰器的“纯类”形式

现在,请注意“混合”方法采用“两全其美”的做法,以保持最短且可读性更高的代码。专门用类定义的完整“装饰器工厂”将需要两个类,或者需要一个“模式”属性来知道是否调用了它来注册修饰后的函数或实际调用了最终函数

class MyDecorator(object):
   """Decorator example defined entirely as class."""
   def __init__(self, p1, p2):
        self.p1 = p1
        ...
        self.mode = "decorating"

   def __call__(self, *args, **kw):
        if self.mode == "decorating":
             self.func = args[0]
             self.mode = "calling"
             return self
         # code to run prior to function call
         result = self.func(*args, **kw)
         # code to run after function call
         return result

@MyDecorator(p1, ...)
def myfunc():
    ...

最后是一个纯净的“白领”装饰器,定义了两个类-也许可以使事物更加分离,但是将冗余性提高到一定程度,这不能说它更易于维护:

class Stage2Decorator(object):
    def __init__(self, func, p1, p2, ...):
         self.func = func
         self.p1 = p1
         ...
    def __call__(self, *args, **kw):
         # code to run prior to function call
         ...
         result = self.func(*args, **kw)
         # code to run after function call
         ...
         return result

class Stage1Decorator(object):
   """Decorator example defined as two classes.

   No "hacks" on the object model, most bureacratic.
   """
   def __init__(self, p1, p2):
        self.p1 = p1
        ...
        self.mode = "decorating"

   def __call__(self, func):
       return Stage2Decorator(func, self.p1, self.p2, ...)


@Stage1Decorator(p1, p2, ...)
def myfunc():
    ...

我几年前在上面写过文字。由于创建了“更扁平”的代码,最近我想到了一个更喜欢的模式。

基本思想是使用函数,但是partial如果在用作修饰符之前使用参数调用了该对象,则返回其自身的对象:

from functools import wraps, partial

def decorator(func=None, parameter1=None, parameter2=None, ...):

   if not func:
        # The only drawback is that for functions there is no thing
        # like "self" - we have to rely on the decorator 
        # function name on the module namespace
        return partial(decorator, parameter1=parameter1, parameter2=parameter2)
   @wraps(func)
   def wrapper(*args, **kwargs):
        # Decorator code-  parameter1, etc... can be used 
        # freely here
        return func(*args, **kwargs)
   return wrapper

就是这样-使用此模式编写的装饰器可以立即装饰函数,而无需先“调用”:

@decorator
def my_func():
    pass

或使用参数定制:

@decorator(parameter1="example.com", ...):
def my_func():
    pass

-使用Python 3.8和仅位置参数,最后一个模式将变得更好,因为func可以将参数声明为仅位置参数,并要求对参数进行命名;

def decorator(func=None, *, parameter1=None, parameter2=None, ...):
python 2022/1/1 18:41:53 有275人围观

撰写回答


你尚未登录,登录后可以

和开发者交流问题的细节

关注并接收问题和回答的更新提醒

参与内容的编辑和改进,让解决方法与时俱进

请先登录

推荐问题


联系我
置顶