Как получить все методы класса Python с данным декоратором - PullRequest
69 голосов
/ 06 мая 2011

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

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

    @decorator1
    def method_b(self, b):
      pass

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

Ответы [ 5 ]

102 голосов
/ 06 мая 2011

Способ 1: базовая регистрация декоратора

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


Метод 2: Исходный кодпарсинг

Если у вас нет контроля над классом определением , которое является одной из интерпретаций того, что вы хотели бы предположить, это невозможно (без отражения чтения кода), поскольку, например, декоратор может быть декоратором без операции (как в моем связанном примере), который просто возвращает функцию без изменений.(Тем не менее, если вы позволите себе обернуть / переопределить декораторы, см. Метод 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: Преобразование декораторов в «самосознание»

Если у вас нет контроля над декоратором определение (что является еще одной интерпретацией того, что вы хотите), тогда все эти проблемы исчезнут, потому что вы можете контролировать применение декоратора.Таким образом, вы можете изменить декоратор путем обтекания it, чтобы создать свой собственный декоратор, и использовать , что , для украшения ваших функций.Позвольте мне сказать это еще раз: вы можете создать декоратор, который украшает декоратор, который вы не можете контролировать, «просветив» его, что в нашем случае заставляет его делать то, что он делал раньше, но также добавляет .decorator свойство метаданных для вызываемого объекта, которое оно возвращает, позволяя вам отслеживать «была ли эта функция украшена или нет? Давайте проверим 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's 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: theПриведенный выше метод также может создать .decorator, который отслеживает весь стек примененных декораторов и функций ввода и аргументы фабрики декораторов .=) Например, если вы рассматриваете закомментированную строку R.original = func, возможно использовать такой метод, чтобы отслеживать все слои-обертки.Это то, что я бы сделал, если бы написал библиотеку декораторов, потому что она допускает глубокий самоанализ.

Существует также разница между @foo и @bar(...).Хотя они оба являются "выражениями декоратора", как определено в спецификации, обратите внимание, что foo является декоратором, а bar(...) возвращает динамически созданный декоратор, который затем применяется.Таким образом, вам понадобится отдельная функция makeRegisteringDecoratorFactory, которая чем-то напоминает makeRegisteringDecorator, но даже БОЛЬШЕ МЕТА:

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

Этот генератор-факторная обертка также работает:

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

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

def getDecorator(): # let's 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>]

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

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

14 голосов
/ 06 марта 2012

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

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())"]}
0 голосов
/ 16 июля 2019

Я не хочу добавлять много, просто простая вариация метода 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]
0 голосов
/ 25 января 2019

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

, например

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

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

0 голосов
/ 06 мая 2011

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

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
...