Как различить метод и функцию в декораторе? - PullRequest
8 голосов
/ 12 марта 2010

Я хочу написать декоратор, который действует по-разному в зависимости от того, применяется ли он к функции или к методу.

def some_decorator(func):
    if the_magic_happens_here(func): # <---- Point of interest
        print 'Yay, found a method ^_^ (unbound jet)'
    else:
        print 'Meh, just an ordinary function :/'
    return func

class MyClass(object):
    @some_decorator
    def method(self):
        pass

@some_decorator
def function():
    pass

Я пытался inspect.ismethod(), inspect.ismethoddescriptor() и inspect.isfunction(), но не повезло. Проблема в том, что метод на самом деле не является ни связанным, ни несвязанным методом, а является обычной функцией, если к нему обращаются из тела класса.

Что я действительно хочу сделать, так это отложить действия декоратора до точки, в которой фактически создан экземпляр класса, потому что мне нужно, чтобы методы вызывались в их экземпляре. Для этого я хочу пометить методы атрибутом, а затем искать эти атрибуты при вызове метода .__new__() из MyClass. Классы, для которых должен работать этот декоратор, должны наследоваться от класса, который находится под моим контролем. Вы можете использовать этот факт для своего решения.

В случае нормальной функции задержка не требуется, и декоратор должен немедленно принять меры. Вот почему я хочу различать эти два случая.

Ответы [ 4 ]

6 голосов
/ 13 марта 2010

Я бы полагался на соглашение, что функции, которые станут методами, имеют первый аргумент с именем self, а другие функции - нет. Хрупкий, но тогда нет действительно твердого пути.

Итак (псевдокод, поскольку у меня есть комментарии вместо того, что вы хотите сделать в любом случае ...):

import inspect
import functools

def decorator(f):
  args = inspect.getargspec(f)
  if args and args[0] == 'self':
     # looks like a (future) method...
  else:
     # looks like a "real" function
     @functools.wraps(f)
     def wrapper  # etc etc

Один из способов сделать его немного более четким, как вы говорите, все участвующие классы наследуют от класса, находящегося под вашим контролем, состоит в том, чтобы этот класс предоставил метакласс (который также, конечно, будет наследоваться указанными классами), который проверяет вещи в конце тела класса. Сделайте доступной упакованную функцию, например с помощью wrapper._f = f и метакласса __init__ можно проверить, что все завернутые методы действительно имеют self в качестве первого аргумента.

К сожалению, нет простого способа проверить, что другие функции (не-будущие методы), обернутые не не имеют такого первого аргумента, так как вы не контролируете окружающей среды в этом случае. Декоратор может проверять функции «верхнего уровня» (те, чья def является оператором верхнего уровня в их модуле), через атрибуты f_globals (globals dict, то есть dict модуля) и f_name функции - если функция является такой глобальной, по-видимому, она не будет позже назначена в качестве атрибута класса (таким образом, в любом случае становясь методом будущего ;-), так что имя self с именем first arg, если оно есть, может быть диагностировано как неправильное и предупрежден о (пока все еще рассматривает функцию как реальную функцию; -).

Одной из альтернатив было бы сделать оформление в самом декораторе в соответствии с гипотезой о реальной функции, но также сделать доступным исходный объект функции как wrapper._f. Затем метакласс __init__ может заново выполнить оформление для всех функций в теле класса, которые, как он видит, были помечены таким образом. Этот подход гораздо более надежен, чем основанный на соглашении, который я только что набросал, даже с дополнительными проверками. Тем не менее, что-то вроде

class Foo(Bar): ... # no decorations

@decorator
def f(*a, **k): ...

Foo.f = f   # "a killer"... function becomes method!

все еще будет проблематичным - вы можете попытаться перехватить это с __setattr__ в вашем метаклассе (но тогда другие назначения атрибутам класса после оператора class могут стать проблематичными).

Чем больше у пользовательского кода свободы для выполнения забавных вещей (а Python обычно оставляет программисту много такой свободы), тем труднее будет, конечно, ваш код "framework-y" держать вещи под жестким контролем; - ).

1 голос
/ 23 февраля 2019

Начиная с версии Python 3.3, используя PEP 3155 :

def some_decorator(func):
    if func.__name__ != func.__qualname__:
        print 'Yay, found a method ^_^ (unbound jet)'
    else:
        print 'Meh, just an ordinary function :/'
    return func

Метод x класса A будет иметь __qualname__, равный A.x, тогда как функция x будет иметь __qualname__ из x.

1 голос
/ 13 марта 2010

Вам нужно, чтобы магия происходила там, где вы выбираете, какую обертку возвращать, или вы можете отложить магию, пока функция не будет вызвана? Вы всегда можете попробовать параметр в декораторе, чтобы указать, какую из двух оболочек он должен использовать, например

def some_decorator( clams ):
   def _mydecor(func ):
       @wraps(func)
       def wrapping(*args....)
          ...
       return wrapping
   def _myclassdecor(func):
       @wraps(func)
       .....

   return _mydecor if clams else _myclassdecor

Другая вещь, которую я мог бы предложить, - это создать метакласс и определить метод init в метаклассе, чтобы искать методы, украшенные вашим декоратором, и соответственно их пересматривать, как подсказал Алекс. Используйте этот метакласс с вашим базовым классом, и, поскольку все классы, которые будут использовать декоратор, будут наследовать от базового класса, они также получат тип метакласса и также будут использовать его init .

0 голосов
/ 13 марта 2010

Вам просто нужно проверить, имеет ли декорируемая функция атрибут im_func. Если это так, то это метод. Если нет, то это функция.

Обратите внимание , что пример кода ниже выполняет обнаружение во время вызова, но вы можете сделать это и во время оформления. Просто переместите чек hasattr на генератор внешнего декоратора.

Python 2.6.4 (r264:75706, Dec  7 2009, 18:45:15) 
[GCC 4.4.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def deco(f):
...   def _wrapper(*args, **kwargs):
...     if hasattr(f, 'im_func'):
...       print 'method'
...     else:
...       print 'function'
...   return _wrapper
... 
>>> deco(lambda x: None)()
function
>>> def f(x):
...   return x + 5
... 
>>> deco(f)()
function
>>> class A:
...   def f(self, x):
...     return x + 5
... 
>>> a = A()
>>> deco(a.f)()
method
>>> deco(A.f)()
method
>>> 

Редактировать

О, хватит! И я все понял неправильно. Я поэтому следовало бы прочитать пост Алекса более тщательно.

>>> class B:
...   @deco
...   def f(self, x):
...     return x +5
... 
>>> b = B()
>>> b.f()
function
...