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