2012-01-09 2 views
12

Я пишу декоратор, и по разным досадным причинам [0] было бы целесообразно проверить, является ли функция, которую она обертывает, автономно или как часть класса (и, кроме того, какие классы нового класса подклассы).Python: может ли декоратор определить, определена ли функция внутри класса?

Например:

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) не будет wo гк.
  • Пожалуйста, предлагают только предложения, которые решают эту проблему - Я знаю, что есть много подобных способов для достижения этой цели (ех, используя класс декоратора), но я хотел бы, чтобы это произошло в украшение времени, не позже.

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

+0

Для любопытных, вот в результате: http://paste.pocoo.org/show/532430/ –

ответ

11

Посмотрите на вывод 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

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

Вы можете следить за Types and Members table, чтобы получить доступ к функции внутри связанного или несвязанного метода:

>>> C.foo.im_func 
<function foo at 0x1062dfaa0> 
>>> inspect.isfunction(C.foo.im_func) 
True 
>>> inspect.ismethod(C.foo.im_func) 
False 
+0

Я ожидаю, что «проверка» будет задействована, но не так, как вы описали. См. Мою первую заметку (что в то время, когда декораторы применяются, функция является всего лишь экземпляром 'function', а не' unboundmethod' или 'boundmethod'). –

+0

Он не указал это явно, но, по его мнению, это не работает во время определения класса. –

+0

Да, вы правы - я обновил свой вопрос, чтобы быть более явным. Я ценю тщательный ответ, это просто не касается моего вопроса. –

2

Некоторые Hacky решение, которое я получил:

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

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

defined_in_class = inspect.currentframe().f_back.f_code.co_name != "<module>" 
+0

Hrm ... Но это не сработало бы для классов, вложенных в что-то, не так ли? (ex, 'def mk_class(): class MyClass: ...; return MyClass') –

+0

Что делает функция или возвращает не влияет на значение' inspect.currentframe(). f_back'; единственное, что имеет значение, - это когда вызывается 'my_decorator', и это всегда будет тот же уровень, на котором определяется предмет, который он обертывает. – chepner

+0

Возможно, я неправильно понял ваш комментарий; любые декорированные функции внутри других функций не будут запускать декоратор до тех пор, пока вызывающая функция не будет вызвана, но объем обернутой функции будет вычислен правильно. Вы можете использовать это внутри декоратора, чтобы просмотреть список охватывающих областей в момент, когда выполняется декоратор: '[x [0] .f_code.co_name для x in inspect.stack() [1:]]'. – chepner

2

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

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.

Смежные вопросы