Самоанализ, чтобы получить имена декораторов для метода?

Я пытаюсь понять, как получить имена всех декораторов метода. Я уже могу получить имя метода и строку документации, но не могу понять, как получить список декораторов.


person Tony    schedule 12.07.2010    source источник
comment
Это кажется ненужным. У вас есть источник. Что плохого в том, чтобы прочитать исходник?   -  person S.Lott    schedule 13.07.2010
comment
@S.Lott: не могли бы вы ответить так же на любой вопрос, связанный с самоанализом? И все же самоанализ полезен.   -  person Ned Batchelder    schedule 13.07.2010
comment
@S.Lott: Нет ничего плохого в чтении источника, так же как нет ничего плохого в чтении содержимого базы данных напрямую вместо использования представлений или сценариев, если только я не хочу автоматизации. Я использую декораторы для аутентификации и создаю отчеты с различными представлениями, чтобы показать, какие группы пользователей имеют доступ к тем или иным ресурсам. Поэтому мне нужен программный доступ к источнику, так же как мне нужен программный доступ к источнику данных.   -  person Tony    schedule 13.07.2010
comment
@Ned Batchelder: Я не отвечаю — по крайней мере, я так не думаю. Я спрашиваю, каков вариант использования. Самоанализ — это то, что юристы называют привлекательной неприятностью. Я не понимаю варианта использования этого примера самоанализа. Вопрос слишком короткий и тонкий по деталям.   -  person S.Lott    schedule 13.07.2010
comment
Это не очень полезное уточнение. Можете ли вы предоставить некоторые варианты использования и некоторый код?   -  person S.Lott    schedule 13.07.2010
comment
Немного поздно в игру здесь, но очень веская причина для этого — документация. В Django, например, есть декоратор, которым пользуются многие: @require_http_methods(['GET', 'POST', ]). В дополнение к распечатке строки документации для метода было бы неплохо распечатать http_methods, которые реализует метод.   -  person abhillman    schedule 28.08.2014


Ответы (8)


Если вы можете изменить способ вызова декораторов из

class Foo(object):
    @many
    @decorators
    @here
    def bar(self):
        pass

to

class Foo(object):
    @register(many,decos,here)
    def bar(self):
        pass

тогда вы можете зарегистрировать декораторы следующим образом:

def register(*decorators):
    def register_wrapper(func):
        for deco in decorators[::-1]:
            func=deco(func)
        func._decorators=decorators        
        return func
    return register_wrapper

Например:

def many(f):
    def wrapper(*args,**kwds):
        return f(*args,**kwds)
    return wrapper

decos = here = many

class Foo(object):
    @register(many,decos,here)
    def bar(self):
        pass

foo=Foo()

Здесь мы получаем доступ к кортежу декораторов:

print(foo.bar._decorators)
# (<function many at 0xb76d9d14>, <function decos at 0xb76d9d4c>, <function here at 0xb76d9d84>)

Здесь мы печатаем только имена декораторов:

print([d.func_name for d in foo.bar._decorators])
# ['many', 'decos', 'here']
person unutbu    schedule 12.07.2010
comment
Это отличное решение. :D Предполагается, что у вас есть доступ к коду, который назначает декораторы... - person Faisal; 13.07.2010
comment
Хорошо, это может сработать, но почему я не могу просто добавить код func._whatever='something' в мой существующий декоратор и проверить значение атрибута _whatever при выполнении самоанализа метода? - person Tony; 13.07.2010
comment
Вы можете, но тогда вам придется замарать каждый декоратор, который вы пишете, сквозной заботой о том, чтобы оставить его следы в функции, которую он модифицирует. - person Faisal; 13.07.2010

Я удивлен, что этот вопрос такой старый, и никто не нашел времени, чтобы добавить фактический интроспективный способ сделать это, так что вот он:

Код, который вы хотите проверить...

def template(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

baz = template
che = template

class Foo(object):
    @baz
    @che
    def bar(self):
        pass

Теперь вы можете проверить вышеприведенный класс Foo примерно так...

import ast
import inspect

def get_decorators(cls):
    target = cls
    decorators = {}

    def visit_FunctionDef(node):
        decorators[node.name] = []
        for n in node.decorator_list:
            name = ''
            if isinstance(n, ast.Call):
                name = n.func.attr if isinstance(n.func, ast.Attribute) else n.func.id
            else:
                name = n.attr if isinstance(n, ast.Attribute) else n.id

            decorators[node.name].append(name)

    node_iter = ast.NodeVisitor()
    node_iter.visit_FunctionDef = visit_FunctionDef
    node_iter.visit(ast.parse(inspect.getsource(target)))
    return decorators

print get_decorators(Foo)

Это должно напечатать что-то вроде этого...

{'bar': ['baz', 'che']}

или, по крайней мере, так было, когда я очень быстро тестировал это с Python 2.7.9 :)

person Jaymon    schedule 03.07.2015
comment
Хорошо, у этого есть проблемы в python 3: (по крайней мере, кажется, и я уверен, что я не лучший человек со знанием inspect/ast, чтобы комментировать с таким уровнем уверенности); в основном у меня есть код из того, что у вас есть выше, и inspect.getsource(), похоже, возвращается с пробелами перед def wrapper , что затем дает неожиданную ошибку отступа при вызове ast.parse. - person Jmons; 19.01.2018
comment
ТАКЖЕ когда я пытался продемонстрировать это с помощью онлайн-инструментов запуска скриптов (которые, я думаю, скорее скрипт, чем запуск из файла), я просто получаю OSError: source code not available, поэтому я подозреваю, что есть экземпляры (возможно, также запуски bin) где этот процесс не будет работать. Возможно, это не сработает, когда существуют только бинарные прогоны Python? - person Jmons; 19.01.2018
comment
Какая версия python3? Я только что протестировал его в Python 2.7.13 и Python 3.6.4 (это версии, которые у меня есть на моем компьютере), и они оба работали нормально. Кроме того, я не уверен, что это будет работать везде, но оно работало везде, где мне это было нужно. Я определенно мог видеть, что онлайн-скрипт запуска имеет защиту для таких модулей, как ast и inspect, и, возможно, другие вещи, такие как открытие файлов, поскольку это, по определению, более замкнутая среда. - person Jaymon; 21.01.2018
comment
Спасибо за проверку: я попытаюсь взглянуть еще раз, но без возможности продемонстрировать хороший полезный код, это усложняет задачу. Если я смогу воспроизвести это, я открою его как новый вопрос и отмечу вас;) - person Jmons; 24.01.2018

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

Декораторы тестировались отдельно, поэтому мне не нужно было тестировать общую логику для каждой декорированной функции, просто использовались декораторы.

Наконец, я придумал следующую вспомогательную функцию:

import inspect

def get_decorators(function):
    """Returns list of decorators names

    Args:
        function (Callable): decorated method/function

    Return:
        List of decorators as strings

    Example:
        Given:

        @my_decorator
        @another_decorator
        def decorated_function():
            pass

        >>> get_decorators(decorated_function)
        ['@my_decorator', '@another_decorator']

    """
    source = inspect.getsource(function)
    index = source.find("def ")
    return [
        line.strip().split()[0]
        for line in source[:index].strip().splitlines()
        if line.strip()[0] == "@"
    ]

С пониманием списка это немного «плотно», но это помогает, и в моем случае это вспомогательная функция тестирования.

Это работает, если вас интересуют только имена декораторов, а не потенциальные аргументы декораторов. Если вы хотите, чтобы декораторы принимали аргументы, что-то вроде line.strip().split()[0].split("(")[0] может помочь (не проверено)

Наконец, вы можете удалить "@", если хотите, заменив line.strip().split()[0] на line.strip().split()[0][1:].

person dbu    schedule 29.05.2020
comment
Работает как шарм! - person Stanislav Hordiyenko; 30.06.2020

Это потому, что декораторы - это "синтаксический сахар". Скажем, у вас есть следующий декоратор:

def MyDecorator(func):
    def transformed(*args):
        print "Calling func " + func.__name__
        func()
    return transformed

И вы применяете его к функции:

@MyDecorator
def thisFunction():
    print "Hello!"

Это эквивалентно:

thisFunction = MyDecorator(thisFunction)

Возможно, вы можете встроить «историю» в объект функции, если вы контролируете декораторы. Бьюсь об заклад, есть какой-то другой умный способ сделать это (возможно, переопределив присваивание), но, к сожалению, я не очень хорошо разбираюсь в Python. :(

person Faisal    schedule 12.07.2010

Как примечания Faisal, вы могли бы сами декораторы прикрепить метаданные к функции, но, насколько мне известно, это не делается автоматически.

person Russell Borogove    schedule 12.07.2010

Это невозможно по-моему. Декоратор — это не какой-то атрибут или метаданные метода. Декоратор — это удобный синтаксис для замены функции результатом вызова функции. См. http://docs.python.org/whatsnew/2.4.html?highlight=decorators#pep-318-decorators-for-functions-and-methods для получения дополнительной информации.

person Achim    schedule 12.07.2010

В общем случае сделать это невозможно, т.к.

@foo
def bar ...

точно такой же, как

def bar ...
bar = foo (bar)

Вы можете сделать это в некоторых особых случаях, например, @staticmethod, анализируя объекты функций, но не лучше этого.

person doublep    schedule 12.07.2010

Вы не можете, но, что еще хуже, существуют библиотеки, помогающие скрыть тот факт, что вы для начала декорировали функцию. Для получения дополнительной информации см. Functools или библиотеку декораторов (@decorator, если мне удалось ее найти).

person wheaties    schedule 12.07.2010