Можно ли заменить декоратор функции / метода во время выполнения? [питон]

StackOverflow https://stackoverflow.com/questions/642762

Вопрос

Если у меня есть функция:


@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()

Можно ли заменить декоратор во время выполнения?

Это было полезно?

Решение

Я не знаю, есть ли способ "заменить" Декоратор, как только он был применен, но я думаю, что, вероятно, нет, потому что функция уже была изменена.

В любом случае, вы можете применить декоратор во время выполнения на основании некоторого условия:

#!/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)

Другие советы

Как уже упоминала Мия, вы можете заменить декоратор другой функцией в любой точке, прежде чем интерпретатор перейдет к объявлению этой функции. Однако после применения декоратора к функции я не думаю, что есть способ динамически заменить декоратор другим. Так, например:

@aDecorator
def myfunc1():
    pass

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

myfunc1 = bDecorator(myfunc1)

Не будет работать, потому что myfunc1 больше не является функцией, которую вы изначально определили; это уже было завернуто. Наилучшим подходом здесь является ручное применение декораторов в стиле oldskool, т.е.

def myfunc1():
    pass

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

Изменить. Или, чтобы быть немного яснее,

def _tempFunc():
    pass

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

Вот потрясающий рецепт для начала работы . По сути, идея состоит в том, чтобы передать экземпляр класса в декоратор. Затем вы можете установить атрибуты для экземпляра класса (если хотите, сделать его 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')

Естественно, вы также можете включить " decorator decorator " сохранять подписи и т. д.

Конечно, вы можете получить объект функции и делать с ним все, что хотите:

# 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__()

Если вы хотите явно изменить декоратор, вы можете также выбрать более явный подход вместо создания декорированной функции:

deco1(myfunc1, arg1, arg2)
deco2(myfunc1, arg2, arg3)

deco1 () и deco2 () будут применять функциональность, предоставляемую вашими декораторами, и вызывать myfunc1 () с аргументами.

Я знаю, что это старая ветка, но мне было весело делать это

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]

Если декоратор - это функция, просто замените ее.

aDecorator = otherDecorator
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top