Подтвердить что ты не робот

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) перед вызовом обернутой функции не работает, потому что обернутая функция должна быть генератором, который вообще не выполняется.

4b9b3361

Ответ 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