Як отримати всі методи даного класу 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 =)
Якщо у вас немає контролю над визначенням класу , що є однією з інтерпретацій того, що ви хотіли б припустити, це неможливо (без читання-роздуму коду), оскільки, наприклад, декоратор може бути декоратором без операції (наприклад у моєму зв’язаному прикладі), яка просто повертає функцію незміненою. (Тим не менше, якщо ви дозволите собі обернути / перевизначити декоратори, див. Метод 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
властивість метаданих до викликається, який він повертає , що дозволяє вам відстежувати "чи була ця функція оформлена чи ні? давайте перевіримо 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: вищезазначений метод може також створити такий, .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, це також буде працювати, якщо метод додається під час виконання (вручну, через метаклас тощо) або успадковується.
Майте на увазі, що ви також можете прикрасити клас, тому, якщо ви "просвітлите" декоратор, який використовується як для декорування методів, так і для класів, а потім напишете клас у тілі класу, який ви хочете проаналізувати , тоді methodsWithDecorator
повернете оформлені класи як а також декоровані методи. Можна вважати це особливістю, але ви можете легко написати логіку, щоб ігнорувати їх, досліджуючи аргумент до декоратора, тобто .original
для досягнення бажаної семантики.
Щоб розширити чудову відповідь @ 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())"]}
Можливо, якщо декоратори не надто складні (але я не знаю, чи є менш вдалий спосіб).
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):
. Вам буде важко знайти реальні рядки, які ви хочете, якщо у вас немає домовленості, щоб завжди писати свої декоратори, визначаючи нову функцію як перший рядок, і, крім того, ви не повинні писати жодних рядків ... хоча ви можете уникнути необхідності не запишіть рядки документації, маючи метод, який перевіряє відступ, коли він рухається вгору за рядком, щоб знайти ім'я справжнього декоратора.
Простий спосіб вирішити цю проблему - помістити код у декоратор, який додає кожну передану функцію / метод до набору даних (наприклад, до списку).
напр
def deco(foo):
functions.append(foo)
return foo
Тепер кожна функція з Деко декоратором буде додана функцій .
Я не хочу додавати багато, просто просту варіацію методу 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]
Якщо ви все-таки контролюєте декоратори, ви можете використовувати класи декораторів, а не функції:
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>}