Как получить все методы данного класса A, украшенные @decorator2?
class A():
def method_a(self):
pass
@decorator1
def method_b(self, b):
pass
@decorator2
def method_c(self, t=5):
pass
Как получить все методы данного класса A, украшенные @decorator2?
class A():
def method_a(self):
pass
@decorator1
def method_b(self, b):
pass
@decorator2
def method_c(self, t=5):
pass
Я уже ответил на этот вопрос здесь: Вызов функций по индексу массива в Python а> =)
Если у вас нет контроля над определением класса, что является одной из интерпретаций того, что вы хотели бы предположить, это невозможно (без code-reading-reflection), поскольку, например, декоратор может быть декоратором без операций (как в моем связанном примере), который просто возвращает функцию без изменений. (Тем не менее, если вы позволите себе обернуть/переопределить декораторы, см. Метод 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 ...>
, а не имя функции, что требует больших усилий. Однако этот метод является хакерским и ужасным.
Если у вас нет контроля над определением decorator (что является другой интерпретацией того, что вам нужно), то все эти проблемы исчезнут, поскольку вы контролируете, как применяется декоратор. Таким образом, вы можете изменить декоратор, обернув его, чтобы создать свой собственный декоратор и использовать его для украшения ваших функций. Позвольте мне повторить это еще раз: вы можете создать декоратор, который украшает декоратор, над которым вы не можете контролировать, «просветляя» его, что в нашем случае заставляет его делать то, что он делал раньше, но также добавляет .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
, если только мы не сохраняем ссылки на «более внутренние» оболочки.
примечание: приведенный выше метод также может создавать .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>]
Как видите, в отличие от метода2, @deco распознается правильно, даже если он никогда не был явно написан в классе. В отличие от method2, это также будет работать, если метод добавляется во время выполнения (вручную, через метакласс и т. д.) или наследуется.
Имейте в виду, что вы также можете декорировать класс, поэтому, если вы «просветите» декоратор, который используется как для декорирования методов, так и для классов, а затем напишите класс внутри тела класса, который вы хотите проанализировать , то methodsWithDecorator
вернет декорированные классы, а также декорированные методы. Можно было бы считать это особенностью, но вы можете легко написать логику, чтобы игнорировать их, изучив аргумент декоратора, то есть .original
, для достижения желаемой семантики.
Чтобы расширить превосходный ответ @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())"]}
Если у вас есть контроль над декораторами, вы можете использовать классы декораторов, а не функции:
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>}
Может быть, если декораторы не слишком сложны (но я не знаю, есть ли менее хакерский способ).
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
def new_f():
(первая, строка 4), def new_f():
(вторая, строка 11) и def method_a(self):
. Вам будет трудно найти настоящие строки, которые вы хотите, если у вас нет соглашения всегда писать свои декораторы, определяя новую функцию в качестве первой строки, и, кроме того, вы не должны писать строки документации... хотя вы могли бы избежать необходимости не напишите строки документации, используя метод, который проверяет отступы по мере их продвижения вверх по строке, чтобы найти имя настоящего декоратора.
- person ninjagecko; 06.05.2011
Простой способ решить эту проблему — поместить в декоратор код, который добавляет каждую переданную функцию/метод в набор данных (например, список).
e.g.
def deco(foo):
functions.append(foo)
return foo
теперь каждая функция с декоратором deco будет добавлена в functions.
Я не хочу много добавлять, просто простая вариация Метода 2 от ninjagecko. Он творит чудеса.
Тот же код, но с использованием понимания списка вместо генератора, что мне и было нужно.
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]
Для тех из нас, кто просто хочет абсолютно простейший возможный случай, а именно решение с одним файлом, в котором у нас есть полный контроль как над классом, с которым мы работаем, так и над декоратором, который мы пытаемся отслеживать, у меня есть ответ . ninjagecko связан с решением, когда у вас есть контроль над декоратором, который вы хотите отслеживать, но лично мне это показалось сложным и очень трудным для понимания, возможно, потому, что я никогда не работал с декораторами до сих пор. Итак, я создал следующий пример с целью сделать его максимально простым и понятным. Это декоратор, класс с несколькими декоративными методами и код для извлечения и запуска всех методов, к которым применен определенный декоратор.
# our decorator
def cool(func, *args, **kwargs):
def decorated_func(*args, **kwargs):
print("cool pre-function decorator tasks here.")
return_value = func(*args, **kwargs)
print("cool post-function decorator tasks here.")
return return_value
# add is_cool property to function so that we can check for its existence later
decorated_func.is_cool = True
return decorated_func
# our class, in which we will use the decorator
class MyClass:
def __init__(self, name):
self.name = name
# this method isn't decorated with the cool decorator, so it won't show up
# when we retrieve all the cool methods
def do_something_boring(self, task):
print(f"{self.name} does {task}")
@cool
# thanks to *args and **kwargs, the decorator properly passes method parameters
def say_catchphrase(self, *args, catchphrase="I'm so cool you could cook an egg on me.", **kwargs):
print(f"{self.name} says \"{catchphrase}\"")
@cool
# the decorator also properly handles methods with return values
def explode(self, *args, **kwargs):
print(f"{self.name} explodes.")
return 4
def get_all_cool_methods(self):
"""Get all methods decorated with the "cool" decorator.
"""
cool_methods = {name: getattr(self, name)
# get all attributes, including methods, properties, and builtins
for name in dir(self)
# but we only want methods
if callable(getattr(self, name))
# and we don't need builtins
and not name.startswith("__")
# and we only want the cool methods
and hasattr(getattr(self, name), "is_cool")
}
return cool_methods
if __name__ == "__main__":
jeff = MyClass(name="Jeff")
cool_methods = jeff.get_all_cool_methods()
for method_name, cool_method in cool_methods.items():
print(f"{method_name}: {cool_method} ...")
# you can call the decorated methods you retrieved, just like normal,
# but you don't need to reference the actual instance to do so
return_value = cool_method()
print(f"return value = {return_value}\n")
Выполнение приведенного выше примера дает нам следующий вывод:
explode: <bound method cool.<locals>.decorated_func of <__main__.MyClass object at 0x00000220B3ACD430>> ...
cool pre-function decorator tasks here.
Jeff explodes.
cool post-function decorator tasks here.
return value = 4
say_catchphrase: <bound method cool.<locals>.decorated_func of <__main__.MyClass object at 0x00000220B3ACD430>> ...
cool pre-function decorator tasks here.
Jeff says "I'm so cool you could cook an egg on me."
cool post-function decorator tasks here.
return value = None
Обратите внимание, что декорированные методы в этом примере имеют разные типы возвращаемых значений и разные сигнатуры, поэтому практическая ценность возможности получить и запустить их все немного сомнительна. Однако в тех случаях, когда существует много похожих методов с одинаковой сигнатурой и/или типом возвращаемого значения (например, если вы пишете коннектор для извлечения ненормализованных данных из одной базы данных, их нормализации и вставки во вторую, нормализованной базы данных, и у вас есть куча похожих методов, например, 15 методов read_and_normalize_table_X), возможность извлекать (и запускать) их все на лету может быть более полезной.