Как получить все методы класса Python с данным декоратором

Как получить все методы данного класса A, украшенные с помощью @decorator2?

class A():
    def method_a(self):
      pass

    @decorator1
    def method_b(self, b):
      pass

    @decorator2
    def method_c(self, t=5):
      pass

Ответ 1

Способ 1: Базовый регистратор оформления

Я уже ответил на этот вопрос здесь: Вызов функций по индексу массива в Python =)


Метод 2: синтаксический анализ исходного кода

Если у вас нет контроля над определением class, которое является одной интерпретацией того, что вы хотели бы предположить, это невозможно (без чтения кода - отражение), поскольку, например, декоратор может быть не-op-декоратором (как в моем связанном примере), который просто возвращает немодифицированную функцию. (Тем не менее, если вы позволите себе обернуть/переопределить декораторов, см. Метод 3: Преобразование декораторов в "самосознание" , тогда вы найдете элегантное решение)

Это ужасный ужасный взлом, но вы можете использовать модуль inspect для чтения самого исходного кода и проанализировать его. Это не будет работать в интерактивном интерпретаторе, потому что модуль проверки откажется предоставить исходный код в интерактивном режиме. Однако ниже приведено доказательство концепции.

#!/usr/bin/python3

import inspect

def deco(func):
    return func

def deco2():
    def wrapper(func):
        pass
    return wrapper

class Test(object):
    @deco
    def method(self):
        pass

    @deco2()
    def method2(self):
        pass

def methodsWithDecorator(cls, decoratorName):
    sourcelines = inspect.getsourcelines(cls)[0]
    for i,line in enumerate(sourcelines):
        line = line.strip()
        if line.split('(')[0].strip() == '@'+decoratorName: # leaving a bit out
            nextLine = sourcelines[i+1]
            name = nextLine.split('def')[1].split('(')[0].strip()
            yield(name)

Это работает!:

>>> print(list(  methodsWithDecorator(Test, 'deco')  ))
['method']

Обратите внимание, что нужно обратить внимание на разбор и синтаксис python, например. @deco и @deco(... являются допустимыми результатами, но @deco2 не следует возвращать, если мы просто запрашиваем 'deco'. Мы отмечаем, что согласно официальному синтаксису python в http://docs.python.org/reference/compound_stmts.html декораторы выглядят следующим образом:

decorator      ::=  "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE

Мы вздохнули с облегчением, не имея дел с такими случаями, как @(deco). Но обратите внимание, что это все равно не поможет вам, если у вас действительно очень сложные декораторы, например @getDecorator(...), например.

def getDecorator():
    return deco

Таким образом, эта стратегия, которая лучше всего подходит для вас, не может обнаружить такие случаи. Хотя, если вы используете этот метод, то, что вам действительно нужно, это то, что написано поверх метода в определении, которое в этом случае getDecorator.

В соответствии с спецификацией также справедливо иметь @foo1.bar2.baz3(...) в качестве декоратора. Вы можете расширить этот метод, чтобы работать с этим. Вы также можете расширить этот метод, чтобы вернуть <function object ...>, а не имя функции, с большим трудом. Однако этот метод хакерский и ужасный.


Способ 3: Преобразование декораторов в "самосознание"

Если вы не контролируете определение decorator (которое является другой интерпретацией того, что вам бы хотелось), то все эти проблемы исчезают, потому что вы контролируете, как применяется декоратор. Таким образом, вы можете изменить декоратор, обернув его, создать свой собственный декоратор и использовать его для украшения ваших функций. Позвольте мне сказать, что еще раз: вы можете сделать декоратор, который украсит декоратор, который вы не контролируете, "просвещаете" его, что в нашем случае делает так, как он делал раньше, но также добавляет свойство метаданных .decorator к вызываемый он возвращает, позволяя вам отслеживать "была ли эта функция украшена или нет? let check function.decorator!". И , затем, вы можете перебирать методы класса и просто проверить, есть ли у декоратора соответствующее свойство .decorator! =) Как показано здесь:

def makeRegisteringDecorator(foreignDecorator):
    """
        Returns a copy of foreignDecorator, which is identical in every
        way(*), except also appends a .decorator property to the callable it
        spits out.
    """
    def newDecorator(func):
        # Call to newDecorator(method)
        # Exactly like old decorator, but output keeps track of what decorated it
        R = foreignDecorator(func) # apply foreignDecorator, like call to foreignDecorator(method) would have done
        R.decorator = newDecorator # keep track of decorator
        #R.original = func         # might as well keep track of everything!
        return R

    newDecorator.__name__ = foreignDecorator.__name__
    newDecorator.__doc__ = foreignDecorator.__doc__
    # (*)We can be somewhat "hygienic", but newDecorator still isn't signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it not a big issue

    return newDecorator

Демонстрация для @decorator:

deco = makeRegisteringDecorator(deco)

class Test2(object):
    @deco
    def method(self):
        pass

    @deco2()
    def method2(self):
        pass

def methodsWithDecorator(cls, decorator):
    """ 
        Returns all methods in CLS with DECORATOR as the
        outermost decorator.

        DECORATOR must be a "registering decorator"; one
        can make any decorator "registering" via the
        makeRegisteringDecorator function.
    """
    for maybeDecorated in cls.__dict__.values():
        if hasattr(maybeDecorated, 'decorator'):
            if maybeDecorated.decorator == decorator:
                print(maybeDecorated)
                yield maybeDecorated

Это работает!:

>>> print(list(   methodsWithDecorator(Test2, deco)   ))
[<function method at 0x7d62f8>]

Однако "зарегистрированный декоратор" должен быть внешним дизайнером, в противном случае аннотация .decorator будет потеряна. Например, в поезде

@decoOutermost
@deco
@decoInnermost
def func(): ...

вы можете видеть только метаданные, которые decoOutermost предоставляет, если мы не будем ссылаться на "более внутренние" обертки.

sidenote: вышеупомянутый метод также может создать .decorator, который отслеживает весь стек прикладных декораторов и входных функций и аргументов decorator- factory. =) Например, если вы рассматриваете пропущенную строку R.original = func, можно использовать такой метод, чтобы отслеживать все слои оболочки. Это лично то, что я сделал бы, если бы я написал библиотеку декоратора, потому что это позволяет сделать глубокую интроспекцию.

Существует также разница между @foo и @bar(...). В то время как они являются "кумирами декоратора", как определено в спецификации, обратите внимание, что foo является декоратором, а bar(...) возвращает динамически созданный декоратор, который затем применяется. Таким образом, вам понадобится отдельная функция makeRegisteringDecoratorFactory, которая несколько похожа на makeRegisteringDecorator, но даже MORE META:

def makeRegisteringDecoratorFactory(foreignDecoratorFactory):
    def newDecoratorFactory(*args, **kw):
        oldGeneratedDecorator = foreignDecoratorFactory(*args, **kw)
        def newGeneratedDecorator(func):
            modifiedFunc = oldGeneratedDecorator(func)
            modifiedFunc.decorator = newDecoratorFactory # keep track of decorator
            return modifiedFunc
        return newGeneratedDecorator
    newDecoratorFactory.__name__ = foreignDecoratorFactory.__name__
    newDecoratorFactory.__doc__ = foreignDecoratorFactory.__doc__
    return newDecoratorFactory

Демонстрация для @decorator(...):

def deco2():
    def simpleDeco(func):
        return func
    return simpleDeco

deco2 = makeRegisteringDecoratorFactory(deco2)

print(deco2.__name__)
# RESULT: 'deco2'

@deco2()
def f():
    pass

Эта оболочка-генератор factory также работает:

>>> print(f.decorator)
<function deco2 at 0x6a6408>

бонус Пусть даже попробуйте следующее с помощью метода # 3:

def getDecorator(): # let do some dispatching!
    return deco

class Test3(object):
    @getDecorator()
    def method(self):
        pass

    @deco2()
    def method2(self):
        pass

Результат:

>>> print(list(   methodsWithDecorator(Test3, deco)   ))
[<function method at 0x7d62f8>]

Как вы можете видеть, в отличие от метода2, @deco правильно распознается, хотя он никогда не был явно написан в классе. В отличие от метода2, это также будет работать, если метод будет добавлен во время выполнения (вручную, через метакласс и т.д.) Или унаследован.

Помните, что вы также можете украсить класс, поэтому, если вы "просветите" декоратор, который используется как для украшения методов, так и для классов, а затем напишите класс в теле класса, который вы хотите проанализировать, затем methodsWithDecorator вернет оформленные классы, а также украшенные методы. Можно рассматривать эту функцию, но вы можете легко написать логику, чтобы игнорировать ее, исследуя аргумент декоратору, т.е. .original, для достижения желаемой семантики.

Ответ 2

Чтобы расширить на @ninjagecko отличный ответ в методе 2: Разбор исходного кода, вы можете использовать модуль ast, представленный в Python 2.6, для выполнения самопроверки, если модуль проверки имеет доступ к исходному коду.

def findDecorators(target):
    import ast, inspect
    res = {}
    def visit_FunctionDef(node):
        res[node.name] = [ast.dump(e) for e in node.decorator_list]

    V = ast.NodeVisitor()
    V.visit_FunctionDef = visit_FunctionDef
    V.visit(compile(inspect.getsource(target), '?', 'exec', ast.PyCF_ONLY_AST))
    return res

Я добавил несколько более сложный декорированный метод:

@x.y.decorator2
def method_d(self, t=5): pass

Результаты:

> findDecorators(A)
{'method_a': [],
 'method_b': ["Name(id='decorator1', ctx=Load())"],
 'method_c': ["Name(id='decorator2', ctx=Load())"],
 'method_d': ["Attribute(value=Attribute(value=Name(id='x', ctx=Load()), attr='y', ctx=Load()), attr='decorator2', ctx=Load())"]}

Ответ 3

Возможно, если декораторы не слишком сложны (но я не знаю, есть ли менее хакерский способ).

def decorator1(f):
    def new_f():
        print "Entering decorator1", f.__name__
        f()
    new_f.__name__ = f.__name__
    return new_f

def decorator2(f):
    def new_f():
        print "Entering decorator2", f.__name__
        f()
    new_f.__name__ = f.__name__
    return new_f


class A():
    def method_a(self):
      pass

    @decorator1
    def method_b(self, b):
      pass

    @decorator2
    def method_c(self, t=5):
      pass

print A.method_a.im_func.func_code.co_firstlineno
print A.method_b.im_func.func_code.co_firstlineno
print A.method_c.im_func.func_code.co_firstlineno

Ответ 4

Простой способ решить эту проблему - поместить код в декоратор, который добавляет каждую переданную функцию/метод в набор данных (например, список).

например

def deco(foo):
    functions.append(foo)
    return foo

Теперь каждая функция с Деко декоратором будет добавлена функциям.

Ответ 5

Я не хочу добавлять много, просто простой вариант метода ninjagecko 2. Он творит чудеса.

Тот же код, но с использованием понимания списка вместо генератора, что мне и было нужно.

def methodsWithDecorator(cls, decoratorName):

    sourcelines = inspect.getsourcelines(cls)[0]
    return [ sourcelines[i+1].split('def')[1].split('(')[0].strip()
                    for i, line in enumerate(sourcelines)
                    if line.split('(')[0].strip() == '@'+decoratorName]

Ответ 6

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

class awesome(object):
    def __init__(self, method):
        self._method = method
    def __call__(self, obj, *args, **kwargs):
        return self._method(obj, *args, **kwargs)
    @classmethod
    def methods(cls, subject):
        def g():
            for name in dir(subject):
                method = getattr(subject, name)
                if isinstance(method, awesome):
                    yield name, method
        return {name: method for name,method in g()}

class Robot(object):
   @awesome
   def think(self):
      return 0

   @awesome
   def walk(self):
      return 0

   def irritate(self, other):
      return 0

и если я позвоню awesome.methods(Robot), он вернется

{'think': <mymodule.awesome object at 0x000000000782EAC8>, 'walk': <mymodulel.awesome object at 0x000000000782EB00>}