这些类型的python装饰器是如何编写的?

时间:2009-07-09 20:16:56

标签: python language-features decorator

我想编写一个装饰器来限制函数执行的次数,具体方法如下:


@max_execs(5)
def my_method(*a,**k):
   # do something here
   pass

我认为可以编写这种类型的装饰器,但我不知道如何。我认为一个函数不会是这个装饰者的第一个参数,对吗?我想要一个“普通装饰”实现,而不是一些带 调用 方法的类。

这样做的原因是要了解它们的编写方式。请解释语法,以及该装饰器的工作原理。

6 个答案:

答案 0 :(得分:12)

这是我掀起的。它不使用类,但它确实使用函数属性:

def max_execs(n=5):
    def decorator(fn):
        fn.max = n
        fn.called = 0
        def wrapped(*args, **kwargs):
            fn.called += 1
            if fn.called <= fn.max:
                return fn(*args, **kwargs)
            else:
                # Replace with your own exception, or something
                # else that you want to happen when the limit
                # is reached
                raise RuntimeError("max executions exceeded")
        return wrapped
    return decorator

max_execs返回一个名为decorator的函数,该函数又返回wrappeddecoration将max execs和当前exec数存储在两个函数属性中,然后在wrapped中进行检查。

翻译:使用这样的装饰器:

@max_execs(5)
def f():
    print "hi!"

你基本上是这样做的:

f = max_execs(5)(f)

答案 1 :(得分:4)

Decorator只是一个可调用函数,可以将函数转换为其他函数。在您的情况下,max_execs(5)必须是可调用的,它将函数转换为另一个可调用的对象,该对象将计算和转发调用。

class helper:
    def __init__(self, i, fn):
        self.i = i
        self.fn = fn
    def __call__(self, *args, **kwargs):
        if self.i > 0:
            self.i = self.i - 1
            return self.fn(*args, **kwargs)

class max_execs:
    def __init__(self, i):
        self.i = i
    def __call__(self, fn):
        return helper(self.i, fn)

我不明白你为什么要限制自己的功能(而不是一个类)。但如果你真的想......

def max_execs(n):
    return lambda fn, i=n: return helper(i, fn)

答案 2 :(得分:3)

有两种方法可以做到这一点。面向对象的方法是创建一个类:

class max_execs:
    def __init__(self, max_executions):
        self.max_executions = max_executions
        self.executions = 0

    def __call__(self, func):
        @wraps(func)
        def maybe(*args, **kwargs):
            if self.executions < self.max_executions:
                self.executions += 1
                return func(*args, **kwargs)
            else:
                print "fail"
        return maybe

有关wraps的解释,请参阅this question

我更喜欢上述OOP方法用于这种装饰器,因为你基本上有一个私有计数变量来跟踪执行次数。但是,另一种方法是使用闭包,例如

def max_execs(max_executions):
    executions = [0]
    def actual_decorator(func):
        @wraps(func)
        def maybe(*args, **kwargs):
            if executions[0] < max_executions:
                executions[0] += 1
                return func(*args, **kwargs)
            else:
                print "fail"
        return maybe
    return actual_decorator

这涉及三个功能。 max_execs函数被赋予执行次数的参数,并返回一个装饰器,它将限制您进行多次调用。该函数actual_decorator与OOP示例中的__call__方法完全相同。唯一的奇怪之处在于,由于我们没有带私有变量的类,我们需要改变闭包外部范围内的executions变量。 Python 3.0使用nonlocal语句支持这一点,但在Python 2.6或更早版本中,我们需要将执行计数包装在一个列表中,以便可以对其进行变异。

答案 3 :(得分:2)

如果不依赖于类中的状态,则必须在函数本身中保存状态(count):

def max_execs(count):
    def new_meth(meth):
        meth.count = count
        def new(*a,**k):
            meth.count -= 1
            print meth.count            
            if meth.count>=0:
                return meth(*a,**k)
        return new
    return new_meth

@max_execs(5)
def f():
    print "invoked"

[f() for _ in range(10)]

它给出了:

5
invoked
4
invoked
3
invoked
2
invoked
1
invoked
0
-1
-2
-3
-4

答案 4 :(得分:1)

此方法不会修改函数内部,而是将其包装到可调用对象中。

与使用修补函数相比,使用类会使执行速度降低约20%!

def max_execs(n=1):
    class limit_wrapper:
        def __init__(self, fn, max):
            self.calls_left = max
            self.fn = fn
        def __call__(self,*a,**kw):
            if self.calls_left > 0:
                self.calls_left -= 1
                return self.fn(*a,**kw)
            raise Exception("max num of calls is %d" % self.i)


    def decorator(fn):
        return limit_wrapper(fn,n)

    return decorator

@max_execs(2)
def fun():
    print "called"

答案 5 :(得分:0)

我知道你说你不想上课,但不幸的是,这是我能想到如何做到这一点的唯一方法。

class mymethodwrapper:
    def __init__(self):
        self.maxcalls = 0
    def mymethod(self):
        self.maxcalls += 1
        if self.maxcalls > 5:
            return
        #rest of your code
        print "Code fired!"

像这样点燃它

a = mymethodwrapper
for x in range(1000):
    a.mymethod()

输出结果为:

>>> Code fired!
>>> Code fired!
>>> Code fired!
>>> Code fired!
>>> Code fired!