在运行时更改Python函数/方法装饰器是否可行?

23

如果我有一个函数:


@aDecorator
def myfunc1():
  # do something here

if __name__ = "__main__":
  # this will call the function and will use the decorator @aDecorator
  myfunc1() 
  # now I want the @aDecorator to be replaced with the decorator @otherDecorator
  # so that when this code executes, the function no longer goes through
  # @aDecorator, but instead through @otherDecorator. How can I do this?
  myfunc1()

在运行时更改装饰器是否可能?

7个回答

20

就像Miya提到的那样,你可以在解释器到达函数声明之前的任何时候用另一个函数替换装饰器。但是,一旦将装饰器应用于函数,我认为就没有办法动态地用不同的装饰器替换它了。例如:

@aDecorator
def myfunc1():
    pass

# Oops! I didn't want that decorator after all!

myfunc1 = bDecorator(myfunc1)

由于myfunc1已经被包装,所以这种方法行不通,因为它不再是您最初定义的函数。在这里最好的方法是手动应用修饰符,即旧式风格:

def myfunc1():
    pass

myfunc2 = aDecorator(myfunc1)
myfunc3 = bDecorator(myfunc1)

编辑:或者更明确地说,
def _tempFunc():
    pass

myfunc1 = aDecorator(_tempFunc)
myfunc1()
myfunc1 = bDecorator(_tempFunc)
myfunc1()

5

我不知道是否有一种方法可以在装饰器被应用之后“替换”它,但我猜可能没有,因为函数已经被改变了。

无论如何,您仍然可以根据某些条件在运行时应用装饰器:

#!/usr/bin/env python

class PrintCallInfo:
    def __init__(self,f):
        self.f = f
    def __call__(self,*args,**kwargs):
        print "-->",self.f.__name__,args,kwargs
        r = self.f(*args,**kwargs)
        print "<--",self.f.__name__,"returned: ",r
        return r

# the condition to modify the function...
some_condition=True

def my_decorator(f):
    if (some_condition): # modify the function
        return PrintCallInfo(f)
    else: # leave it as it is
        return f

@my_decorator
def foo():
    print "foo"

@my_decorator
def bar(s):
    print "hello",s
    return s

@my_decorator
def foobar(x=1,y=2):
    print x,y
    return x + y

foo()
bar("world")
foobar(y=5)

4

这里有一个绝妙的示例代码,可以帮助你入门。基本上,想法是将类实例传递给装饰器。然后,您可以在类实例上设置属性(如果您喜欢,可以将其制作为Borg),并使用它来控制装饰器本身的行为。

下面是一个例子:

class Foo:
    def __init__(self, do_apply):
        self.do_apply = do_apply

def dec(foo):
    def wrap(f):
        def func(*args, **kwargs):
            if foo.do_apply:
                # Do something!
                pass 
            return f(*args, **kwargs)
        return func
    return wrap

foo = Foo(False)
@dec(foo)
def bar(x):
    return x

bar('bar') 
foo.do_apply = True 
# Decorator now active!
bar('baz')

自然地,您也可以使用“装饰器装饰器”来保留签名等信息。

2
如果您想明确更改装饰器,最好选择更明确的方法而不是创建一个装饰函数:
deco1(myfunc1, arg1, arg2)
deco2(myfunc1, arg2, arg3)

deco1() 和 deco2() 将应用您的修饰符提供的功能并使用参数调用 myfunc1()。


是的,我可以使用一个以函数作为第一个参数、我的函数作为第二个参数,后跟参数的函数来完成这个任务,从而实现一种相当通用的方式。然而,我对于能否用自己的东西替换装饰器很感兴趣。我想要“玩弄”其他类的行为。 - Geo

1
当然可以 - 你可以获取函数对象并对其进行任何操作:
# Bypass a decorator

import types

class decorator_test(object):

    def __init__(self, f):
        self.f = f

    def __call__(self):
        print "In decorator ... entering: ", self.f.__name__
        self.f()
        print "In decorator ... exiting: ", self.f.__name__


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

print "\nCalling func1 with decorator..."
func1()

print "\nBypassing decorator..."
for value in func1.__dict__.values():
    if isinstance(value, types.FunctionType) and value.func_name == "func1":
        value.__call__()

1
这假定控制初始装饰器。想象一下装饰器 def aDecorator(f): return lambda *args, **kwargs: "HAHAHAHAHAHAHA"。通常,这不是一个有效的方法。如果你 确实 控制了初始装饰器,那么有更干净、更惯用的方法来做到这一点。 - aaronasterling

-1

我知道这是一个旧帖子,但我做这个很开心

def change_deco(name, deco, placeholder='    #'):
with open(name + '.py', 'r') as file:
    lines = file.readlines()
for idx, string in enumerate(lines):
    if placeholder in string and repr(placeholder) not in string:
        lines[idx] = f'    @{deco}\r\n'
exec(''.join(lines))
return locals()[name]

-3
如果装饰器是一个函数,只需替换它即可。
aDecorator = otherDecorator

1
但这不会改变myfunc1()。 - unbeknown
我尝试使用你的代码,但函数仍然使用最初的装饰器执行。 - Geo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接