Декоратор изменяет статус функции с метода на функцию - PullRequest
5 голосов
/ 25 августа 2010

[Обновлено]: ответьте на встроенный вопрос ниже

У меня есть программа проверки, и одна цель состоит в том, чтобы логика в декораторе знала, является ли функция, которую она украшает, методом класса или обычной функцией.Это терпит неудачу странным образом.Ниже приведен код, запускаемый в Python 2.6:

def decorate(f):
    print 'decorator thinks function is', f
    return f

class Test(object):
    @decorate
    def test_call(self):
        pass

if __name__ == '__main__':
    Test().test_call()
    print 'main thinks function is', Test().test_call

Затем при выполнении:

decorator thinks function is <function test_call at 0x10041cd70>
main thinks function is <bound method Test.test_call of <__main__.Test object at 0x100425a90>>

Любая подсказка о том, что идет не так, и если @decorate может правильно вывести этот test_callЭто метод?

[Ответ] Карл ответ ниже почти идеально.У меня возникла проблема при использовании декоратора в методе, вызываемом подклассами.Я адаптировал его код, чтобы включить сравнение im_func для членов суперкласса:

ismethod = False
for item in inspect.getmro(type(args[0])):
    for x in inspect.getmembers(item):
        if 'im_func' in dir(x[1]):
            ismethod = x[1].im_func == newf
            if ismethod:
                break
    else:
        continue
    break

Ответы [ 4 ]

5 голосов
/ 25 августа 2010

Как уже говорили другие, функция оформляется до ее привязки, поэтому вы не можете напрямую определить, является ли она «методом» или «функцией».

Разумный способ определить, является ли функция методом или нет, состоит в том, чтобы проверить, является ли «self» первым параметром. Хотя и не является надежным, большинство кода Python придерживается этого соглашения:

import inspect
ismethod = inspect.getargspec(method).args[0] == 'self'

Вот запутанный способ, который, кажется, автоматически определяет, является ли метод связанным или нет. Работает для нескольких простых случаев на CPython 2.6, но без обещаний. Он решает, что функция - это метод, если первый аргумент - это объект с привязанной к нему декорированной функцией.

import inspect

def decorate(f):
    def detect(*args, **kwargs):
        try:
            members = inspect.getmembers(args[0])
            members = (x[1].im_func for x in members if 'im_func' in dir(x[1]))
            ismethod = detect in members
        except:
            ismethod = False
        print ismethod

        return f(*args, **kwargs)
    return detect

@decorate
def foo():
    pass

class bar(object):
    @decorate
    def baz(self):
        pass

foo() # prints False
bar().baz() # prints True
3 голосов
/ 25 августа 2010

Нет, это невозможно, как вы и просили, потому что нет никакой внутренней разницы между связанными методами и функциями.Метод - это просто функция, обернутая, чтобы получить вызывающий экземпляр в качестве первого аргумента (используя Python дескрипторы ).

Подобный вызов:

Test.test_call

, который возвращаетнесвязанный метод, переводится как

Test.__dict__[ 'test_call' ].__get__( None, spam )

, который является несвязанным методом, даже если

Test.__dict__[ 'test_call' ]

является функцией.Это связано с тем, что функции являются дескрипторами, методы которых __get__ возвращают методы;когда Python видит один из них в цепочке поиска, он вызывает метод __get__ вместо продолжения вверх по цепочке.

По сути, «связанная методичность» функции определяется во время выполнения, а не при определении-time!

Декоратор просто видит функцию в том виде, как она определена, не просматривая ее в __dict__, поэтому не может определить, смотрит ли он на связанный метод.


1024 * может быть возможно сделать с помощью декоратора классов, который модифицирует __getattribute__, но это особенно неприятный хак.Почему у вас должен быть этот функционал?Конечно, поскольку вы должны поместить декоратор в функцию самостоятельно, вы можете передать ему аргумент, который говорит, определена ли указанная функция в классе?

class Test:
    @decorate( method = True )
    def test_call:
        ...

@decorate( method = False )
def test_call:
    ...
1 голос
/ 25 августа 2010

Ваш декоратор запускается до того, как функция станет методом. def Ключевое слово внутри класса определяет строку функции в любом другом месте, затем функции, определенные в теле класса, добавляются в класс как методы. Декоратор работает с функцией до ее обработки классом, поэтому ваш код «терпит неудачу».

У @decorate нет возможности увидеть, что функция на самом деле является методом. Обходной путь для этого должен был бы украсить функцию, какой бы она ни была (например, добавив атрибут do_something_about_me_if_I_am_a_method ;-)), а затем обработать его снова после вычисления класса (перебирая членов класса и делая все, что вы хотите с этими оформленными) .

0 голосов
/ 25 августа 2010

Я попробовал немного другой пример, с одним декорированным методом и одним недекорированным методом.

def decorate(f):
  print 'decorator thinks function is', f
  return f

class Test(object):
  @decorate
  def test_call(self):
    pass
  def test_call_2(self):
    pass

if __name__ == '__main__':
  print 'main thinks function is', Test.test_call
  print 'main thinks function 2 is', Test.test_call_2

Тогда вывод:

decorator thinks function is <function test_call at 0x100426b18>
main thinks function is <unbound method Test.test_call>
main thinks function 2 is <unbound method Test.test_call_2>

Таким образом, декоратор увидел другой типчем основная функция, но декоратор не изменил тип функции, иначе она будет отличаться от неокрашенной функции.

...