Python: может ли декоратор определить, определена ли функция внутри класса?
Я пишу декоратора, и по разным досадным причинам [0] было бы целесообразно проверить, является ли функция, которую она обертывает, автономно или как часть класса (и далее, какие классы нового класса является подклассом).
Например:
def my_decorator(f):
defined_in_class = ??
print "%r: %s" %(f, defined_in_class)
@my_decorator
def foo(): pass
class Bar(object):
@my_decorator
def bar(self): pass
Должен печатать:
<function foo …>: False
<function bar …>: True
Также обратите внимание:
- При применении декораторов функция все равно будет функцией, а не несвязанным методом, поэтому тестирование метода instance/unbound (с использованием
typeof
или inspect
) не будет работать.
- Пожалуйста, предлагайте только предложения, которые решают эту проблему. Я знаю, что существует много подобных способов достижения этой цели (например, с использованием декоратора класса), но я бы хотел, чтобы они произошли во время украшения, а не позже.
[0]: в частности, я пишу декоратор, который упростит параметризованное тестирование с помощью nose
. Однако nose
не будет запускать генераторы тестов в подклассах unittest.TestCase
, поэтому я хочу, чтобы мой декоратор смог определить, используется ли он в подклассе TestCase
и сбой с соответствующей ошибкой. Очевидное решение - с помощью isinstance(self, TestCase)
перед вызовом обернутой функции не работает, потому что обернутая функция должна быть генератором, который вообще не выполняется.
Ответы
Ответ 1
Взгляните на вывод inspect.stack()
при переносе метода. Когда выполняется ваш декорирование, текущий стек стека - это вызов функции вашему декоратору; следующий стек стека - это действие @
wrapping, которое применяется к новому методу; и третий кадр будет само определение класса, которое заслуживает отдельного фрейма стека, потому что определение класса - это собственное пространство имен (которое завершается для создания класса, когда оно выполняется).
Я предлагаю, поэтому:
defined_in_class = (len(frames) > 2 and
frames[2][4][0].strip().startswith('class '))
Если все эти сумасшедшие индексы выглядят недостижимыми, то вы можете быть более явными, разделив фрагмент на части, например:
import inspect
frames = inspect.stack()
defined_in_class = False
if len(frames) > 2:
maybe_class_frame = frames[2]
statement_list = maybe_class_frame[4]
first_statment = statement_list[0]
if first_statment.strip().startswith('class '):
defined_in_class = True
Обратите внимание, что я не вижу способа спросить Python о имени класса или иерархии наследования в момент запуска вашей оболочки; этот момент "слишком рано" на этапах обработки, поскольку создание класса еще не завершено. Либо выполните синтаксический анализ строки, начинающейся с class
самостоятельно, а затем загляните в эти фрейм-глобалы, чтобы найти суперкласс, или же выкрой вокруг объекта кода frames[1]
, чтобы узнать, что вы можете узнать, - похоже, что имя класса завершается frames[1][0].f_code.co_name
в приведенном выше коде, но я не могу найти способ узнать, какие суперклассы будут прикреплены при завершении создания класса.
Ответ 2
Некоторое хакерское решение, которое у меня есть:
import inspect
def my_decorator(f):
args = inspect.getargspec(f).args
defined_in_class = bool(args and args[0] == 'self')
print "%r: %s" %(f, defined_in_class)
Но он ссылается на наличие аргумента self
в функции.
Ответ 3
Немного поздно для вечеринки здесь, но это оказалось надежным средством определения того, используется ли декоратор для функции, определенной в классе:
frames = inspect.stack()
className = None
for frame in frames[1:]:
if frame[3] == "<module>":
# At module level, go no further
break
elif '__module__' in frame[0].f_code.co_names:
className = frame[0].f_code.co_name
break
Преимущество этого метода в отношении принятого ответа заключается в том, что он работает, например, py2exe.
Ответ 4
Вы можете проверить, вызван ли сам декоратор на уровне модуля или вложен в другое.
defined_in_class = inspect.currentframe().f_back.f_code.co_name != "<module>"
Ответ 5
Вы можете использовать пакет wrapt
для проверки
- методы экземпляра/класса
- занятия
- автономные функции/статические методы:
Смотрите страницу проекта wrapt
: https://pypi.org/project/wrapt/
Ответ 6
Я думаю, что функции в модуле inspect
будут делать то, что вы хотите, в частности isfunction и ismethod:
>>> import inspect
>>> def foo(): pass
...
>>> inspect.isfunction(foo)
True
>>> inspect.ismethod(foo)
False
>>> class C(object):
... def foo(self):
... pass
...
>>> inspect.isfunction(C.foo)
False
>>> inspect.ismethod(C.foo)
True
>>> inspect.isfunction(C().foo)
False
>>> inspect.ismethod(C().foo)
True
Затем вы можете перейти к Таблицам типов и членов для доступа к функции внутри связанного или несвязанного метода:
>>> C.foo.im_func
<function foo at 0x1062dfaa0>
>>> inspect.isfunction(C.foo.im_func)
True
>>> inspect.ismethod(C.foo.im_func)
False