Я пытаюсь понять, как получить имена всех декораторов метода. Я уже могу получить имя метода и строку документации, но не могу понять, как получить список декораторов.
Самоанализ, чтобы получить имена декораторов для метода?
Ответы (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']
Я удивлен, что этот вопрос такой старый, и никто не нашел времени, чтобы добавить фактический интроспективный способ сделать это, так что вот он:
Код, который вы хотите проверить...
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 :)
inspect.getsource()
, похоже, возвращается с пробелами перед def wrapper
, что затем дает неожиданную ошибку отступа при вызове ast.parse
.
- person Jmons; 19.01.2018
OSError: source code not available
, поэтому я подозреваю, что есть экземпляры (возможно, также запуски bin) где этот процесс не будет работать. Возможно, это не сработает, когда существуют только бинарные прогоны Python?
- person Jmons; 19.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:]
.
Это потому, что декораторы - это "синтаксический сахар". Скажем, у вас есть следующий декоратор:
def MyDecorator(func):
def transformed(*args):
print "Calling func " + func.__name__
func()
return transformed
И вы применяете его к функции:
@MyDecorator
def thisFunction():
print "Hello!"
Это эквивалентно:
thisFunction = MyDecorator(thisFunction)
Возможно, вы можете встроить «историю» в объект функции, если вы контролируете декораторы. Бьюсь об заклад, есть какой-то другой умный способ сделать это (возможно, переопределив присваивание), но, к сожалению, я не очень хорошо разбираюсь в Python. :(
Как примечания Faisal, вы могли бы сами декораторы прикрепить метаданные к функции, но, насколько мне известно, это не делается автоматически.
Это невозможно по-моему. Декоратор — это не какой-то атрибут или метаданные метода. Декоратор — это удобный синтаксис для замены функции результатом вызова функции. См. http://docs.python.org/whatsnew/2.4.html?highlight=decorators#pep-318-decorators-for-functions-and-methods для получения дополнительной информации.
В общем случае сделать это невозможно, т.к.
@foo
def bar ...
точно такой же, как
def bar ...
bar = foo (bar)
Вы можете сделать это в некоторых особых случаях, например, @staticmethod
, анализируя объекты функций, но не лучше этого.
Вы не можете, но, что еще хуже, существуют библиотеки, помогающие скрыть тот факт, что вы для начала декорировали функцию. Для получения дополнительной информации см. Functools или библиотеку декораторов (@decorator
, если мне удалось ее найти).
@require_http_methods(['GET', 'POST', ])
. В дополнение к распечатке строки документации для метода было бы неплохо распечатать http_methods, которые реализует метод. - person abhillman   schedule 28.08.2014