Python 装饰器最佳实践,使用类与函数 [英] Python decorator best practice, using a class vs a function

查看:22
本文介绍了Python 装饰器最佳实践,使用类与函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

据我所知,有两种方法可以做一个 Python 装饰器,要么使用类的 __call__,要么定义和调用一个函数作为装饰器.这些方法的优点/缺点是什么?有没有一种首选方法?

As I've understood it there are two ways to do a Python decorator, to either use the __call__ of a class or to define and call a function as the decorator. What's the advantages/disadvantages of these methods? Is there one preferred method?

示例 1

class dec1(object):
    def __init__(self, f):
        self.f = f
    def __call__(self):
        print "Decorating", self.f.__name__
        self.f()

@dec1
def func1():
    print "inside func1()"

func1()

# Decorating func1
# inside func1()

示例 2

def dec2(f):
    def new_f():
        print "Decorating", f.__name__
        f()
    return new_f

@dec2
def func2():
    print "inside func2()"

func2()

# Decorating func2
# inside func2()

推荐答案

说有没有优点"是比较主观的到每个方法.

It is rather subjective to say whether there are "advantages" to each method.

然而,对引擎盖下的内容有一个很好的理解会让它变得自然为每个场合挑选最佳选择.

However, a good understanding of what goes under the hood would make it natural for one to pick the best choice for each occasion.

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

A decorator (talking about function decorators), is simply a callable object that takes a function as its input parameter. Python has its rather interesting design that allows one to create other kinds of callable objects, besides functions - and one can put that to use to create more maintainable or shorter code on occasion.

装饰器作为语法快捷方式"在 Python 2.3 中重新添加;对于

Decorators were added back in Python 2.3 as a "syntactic shortcut" for

def a(x):
   ...

a = my_decorator(a)

除此之外,我们通常将装饰器称为可调用对象".那宁愿是装饰工厂"- 当我们使用这种类型时:

Besides that, we usually call decorators some "callables" that would rather be "decorator factories" - when we use this kind:

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

调用my_decorator";使用 param1 和 param2 - 然后返回一个将再次调用的对象,这次具有my_func";作为参数.因此,在这种情况下,从技术上讲,装饰器"是指装饰器".是my_decorator"返回的任何内容,使其成为装饰工厂".

the call is made to "my_decorator" with param1 and param2 - it then returns an object that will be called again, this time having "my_func" as a parameter. So, in this case, technically the "decorator" is whatever is returned by the "my_decorator", making it a "decorator factory".

现在,无论是装饰器还是装饰器工厂"如所描述的,通常必须保持一些内部状态.在第一种情况下,它唯一保留的是对原始函数的引用(在您的示例中名为 f 的变量).一家装饰工厂"可能想要注册额外的状态变量(上面例子中的param1"和param2").

Now, either decorators or "decorator factories" as described usually have to keep some internal state. In the first case, the only thing it does keep is a reference to the original function (the variable called f in your examples). A "decorator factory" may want to register extra state variables ("param1" and "param2" in the example above).

这种额外的状态,在装饰器编写为函数的情况下,保存在封闭函数内的变量中,并作为非本地"访问.由实际包装函数定义的变量.如果编写了一个合适的类,它们可以作为实例变量保存在装饰器函数中(这将被视为可调用对象",而不是函数")——并且对它们的访问更加明确且更具可读性.

This extra state, in the case of decorators written as functions is kept in variables within the enclosing functions, and accessed as "nonlocal" variables by the actual wrapper function. If one writes a proper class, they can be kept as instance variables in the decorator function (which will be seen as a "callable object", not a "function") - and access to them is more explicit and more readable.

因此,在大多数情况下,您更喜欢一种方法还是另一种方法是可读性的问题:简而言之,简单的装饰器,函数式方法通常比编写为类的方法更具可读性——而有时更复杂——尤其是一个装饰器工厂";将充分利用扁平优于嵌套"的优势对 Python 编码的建议.

So, for most cases it is a matter of readability whether you will prefer one approach or the other: for short, simple decorators, the functional approach is often more readable than one written as a class - while sometimes a more elaborate one - especially one "decorator factory" will take full advantage of the "flat is better than nested" advice fore Python coding.

考虑:

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

反对这种混合"解决方案:

against this "hybrid" solution:

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

更新:缺少纯类"装饰器的形式

update: Missing "pure class" forms of decorators

现在,注意混合"方法需要两全其美";试图保持最短和更易读的代码.一个完整的装饰工厂"专门用类定义的需要两个类,或者需要一个模式".属性来知道它是被调用来注册装饰函数还是实际调用最终函数:

Now, note the "hybrid" method takes the "best of both Worlds" trying to keep the shortest and more readable code. A full "decorator factory" defined exclusively with classes would either need two classes, or a "mode" attribute to know if it was called to register the decorated function or to actually call the final function:

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():
    ...

最后是纯净的白领"用两个类定义的装饰器 - 也许让事情更加分离,但将冗余增加到不能说它更易于维护的地步:

And finally a pure, "white colar" decorator defined with two classes - maybe keeping things more separated, but increasing the redundancy to a point one can't say it is more maintainable:

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():
    ...

2018 更新

我几年前写了上面的文字.我最近想出了一个我更喜欢的模式,因为我创建的代码更扁平".

2018 update

I wrote the text above a couple years ago. I came up recently with a pattern I prefer due to creating code that is "flatter".

基本思想是使用函数,但如果在用作装饰器之前使用参数调用它,则返回自身的partial对象:

The basic idea is to use a function, but return a partial object of itself if it is called with parameters before being used as a decorator:

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

就是这样 - 使用这种模式编写的装饰器可以装饰立即调用函数而无需调用"第一:

And that is it - decorators written using this pattern can decorate a function right away without being "called" first:

@decorator
def my_func():
    pass

或自定义参数:

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

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

2019 - With Python 3.8 and positional only parameters this last pattern will become even better, as the func argument can be declared as positional only, and require the parameters to be named;

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

这篇关于Python 装饰器最佳实践,使用类与函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆