Python: может ли декоратор определить, определяется ли функция внутри класса? - PullRequest
14 голосов
/ 09 января 2012

Я пишу декоратор, и по разным раздражающим причинам [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

Также обратите внимание:

  • В точке применения декораторов функция все равно будет функцией, а не несвязанным методом, поэтому тестирование экземпляра / несвязанного метода (с использованием typeof или inspect) не будет работать.
  • Пожалуйста, предлагайте только предложения, которые решают эту проблему - я знаю, что есть много подобных способов достижения этой цели (например, с помощью декоратора классов), но я бы хотел, чтобы они произошли в оформление время, не позднее.

[0]: в частности, я пишу декоратор, который упростит проведение параметризованного тестирования с nose. Тем не менее, nose не будет не запускать генераторы тестов на подклассах unittest.TestCase, поэтому я бы хотел, чтобы мой декоратор мог определить, используется ли он внутри подкласса TestCase, и потерпел неудачу с соответствующим ошибка. Очевидное решение - использование isinstance(self, TestCase) перед вызовом упакованной функции не работает, потому что для упакованной функции требуется , чтобы быть генератором, который вообще не выполняется .

Ответы [ 6 ]

13 голосов
/ 09 января 2012

Посмотрите на вывод inspect.stack() при переносе метода. Когда ваш декоратор выполняется, текущий кадр стека является вызовом функции вашего декоратора; следующий кадр стека вниз - это действие обтекания @, которое применяется к новому методу; и третий кадр будет самим определением класса, который заслуживает отдельного стекового кадра, потому что определение класса является его собственным пространством имен (которое оборачивается, чтобы создать класс, когда оно выполнено).

Я предлагаю, поэтому:

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 голосов
/ 27 августа 2013

Немного опоздал на вечеринку, но это оказалось надежным средством определения того, используется ли декоратор для функции, определенной в классе:

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.

2 голосов
/ 09 января 2012

Некоторое хакерское решение, которое у меня есть:

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 в функции.

1 голос
/ 03 июля 2019

вы можете использовать пакет wrapt для проверки
- методы экземпляра / класса
- занятия
- автономные функции / статические методы:

См. Страницу проекта wrapt: https://pypi.org/project/wrapt/

1 голос
/ 09 января 2012

Вы можете проверить, вызывается ли сам декоратор на уровне модуля или вложен во что-то еще.

defined_in_class = inspect.currentframe().f_back.f_code.co_name != "<module>"
0 голосов
/ 09 января 2012

Я думаю, что функции в модуле 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
...