Как дифференцироваться между методом и функцией в декораторе?

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

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 назван. Классы, на которые должен работать этот декоратор, требуются, чтобы наследоваться классу, который находится под моим контролем. Можно использовать тот факт для решения.

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

8
задан user242486 12 March 2010 в 20:45
поделиться

3 ответа

Я бы полагался на соглашение, согласно которому функции, которые станут методами, имеют первый аргумент с именем 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

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

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

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

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

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

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

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

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

6
ответ дан 5 December 2019 в 23:14
поделиться

Вам просто нужно проверить, есть ли у декорируемой функции атрибут 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
-3
ответ дан 5 December 2019 в 23:14
поделиться

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

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 .

1
ответ дан 5 December 2019 в 23:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: