Как получить все методы данного класса 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 =)
Если у вас нет контроля над определением 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 ...>
, а не имя функции, с большим трудом. Однако этот метод хакерский и ужасный.
Если вы не контролируете определение 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
, для достижения желаемой семантики.
Чтобы расширить на @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())"]}
Возможно, если декораторы не слишком сложны (но я не знаю, есть ли менее хакерский способ).
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 deco(foo):
functions.append(foo)
return foo
Теперь каждая функция с Деко декоратором будет добавлена функциям.
Я не хочу добавлять много, просто простой вариант метода 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]
Если у вас есть контроль над декораторами, вы можете использовать классы декораторов, а не функции:
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>}