Декоратор метода класса / метакласса для производного класса - PullRequest
0 голосов
/ 30 ноября 2018

У меня есть метакласс, который определяет атрибут уровня класса, который должен быть уникальным для каждого подкласса, но общим для экземпляров каждого подкласса.

class MetaValidator(type):
    def __new__(
             cls, name, bases, dct
    ):
        new_cls = super().__new__(cls, name, bases, dct)
        new_cls.valid_funcs = []
        return new_cls

Теперь я хотел бы реализовать декоратор, который добавляет декорированныйметод класса для valid_funcs в производном классе.Однако, поскольку производный класс все еще определяется, у меня нет ссылки на производный декоратор, поэтому я добавляю базовый класс.Вот мой код:

class Validator(object, metaclass=MetaValidator):

    @classmethod
    def add(cls, f):
        cls.valid_funcs.append(f)
        return f

    def _validate(self, **kwargs):
        for f in self.valid_funcs:
            params = inspect.signature(f).parameters.keys()
            f_kwargs = {name: kwargs[name] for name in params}
            f(**f_kwargs)

    def validate(self, **kwargs):
        self._validate(**kwargs)

class A(Validator):

    @staticmethod
    @Validator.add
    def test_func(x):
        return x

class B(Validator):

    @staticmethod
    @Validator.add
    def test_func(x, y):
        return x, y

a = A()
a.validate(x="In A")
b = B()
b.validate(x="In B", y=" Called with arg y")

print(Validator.valid_funcs)
print(a.valid_funcs)
print(b.valid_funcs)

Это печатает:

[<function A.test_func at 0x7f0189d4fc80>, 
<function B.test_func at 0x7f0189d4fd08>]
[]
[]

Я хочу:

[]
[<function A.test_func at 0x7f0189d4fc80>]
[<function B.test_func at 0x7f0189d4fd08>]

Ответы [ 2 ]

0 голосов
/ 30 ноября 2018

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

class MetaValidator(type):
    @classmethod
    def __prepare__(cls, name, bases, **kwds):
        ns = super().__prepare__(name, bases, **kwds)
        ns['valid_funcs'] = []
        return ns

def register(func_list):
    def inner_register(func):
        func_list.append(func)
        return func
    return inner_register

class A(metaclass=MetaValidator):
    @register(valid_funcs)
    def method(self):
        ...

Я бы, вероятно, пропустил все вещи метакласса и потребовал бы, чтобы классы сами сделали valid_funcs = [].Дополнительная сложность метакласса не стоит того, чтобы просто сохранять одну строку стандартного шаблона на класс.

0 голосов
/ 30 ноября 2018

Нет объекта класса , пока , когда выполняются декораторы функций в теле класса.Сначала выполняется тело класса, , а затем класс создается.

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

Я собираюсь предположить, что выВы хотите сохранить порядок, в котором декораторы добавили бы украшенные элементы в список:

from itertools import count

class Validator(metaclass=MetaValidator):
    @classmethod
    def add(cls, f):
        _count = getattr(Validator.add, '_count', None)
        if _count is None:
            _count = Validator.add.__func__._count = count()
        f._validator_function_id = next(_count)
        return f

и в метаклассе:

class MetaValidator(type):
    def __new__(cls, name, bases, dct):
        new_cls = super().__new__(cls, name, bases, dct)
        registered = []
        for v in dct.values():
            id = getattr(v, '_validator_function_id', None)
            if id is None and isinstance(v, (staticmethod, classmethod)):
                # unwrap staticmethod or classmethod decorators
                id = getattr(v.__func__, '_validator_function_id', None)
            if id is not None:
                registered.append((id, v))
        new_cls.valid_funcs = [f for _, f in sorted(registered)]
        return new_cls

Обратите внимание, что если вы используетеPython 3.6 или новее, тогда вам больше не нужен метакласс.Вы можете поместить ту же логику в метод class.__init_subclass__ .

Обратите внимание, что здесь регистрируется несвязанных объектов .Для staticmethod объектов это означает, что вызов не будет выполнен с:

TypeError: <staticmethod object at 0x10d1b7048> is not a callable object

Возможно, вы захотите зарегистрировать атрибут __func__ в этом случае или использовать .__get__, чтобы «привязать» объект к чему-либо(a staticmethod игнорирует контекст привязки в любом случае) `.

Если вы явно привязываете метод _validate(), вам не нужно использовать staticmethod объекты:

def _validate(self, **kwargs):
    for f in self.valid_funcs:
        bound = f.__get__(self)
        signature = inspect.signature(bound)
        bound(**{name: kwargs[name] for name in signature.parameters})

Теперь @validator.add будет работать с staticmethod, classmethod и обычными функциями.

И если у вас есть метод _validate(), ищущий методы, тогда связывание может быть сделано для вас .Вы можете выбрать поддержку наследования, просто используя dir() и getattr():

from operator import itemgetter
from itertools import count


class Validator:
    @classmethod
    def add(cls, f):
        _count = getattr(Validator.add, '_count', None)
        if _count is None:
            _count = Validator.add.__func__._count = count()
        f._validator_function_id = next(_count)
        return f

    def _list_validators(self):
        objects = (getattr(self, name) for name in dir(self))
        return sorted(
            (o for o in objects if hasattr(o, '_validator_function_id')),
            key=attrgetter('_validator_function_id'))

    def _validate(self, **kwargs):
        for f in self._list_validators():
            signature = inspect.signature(f)
            f(**{name: kwargs[name] for name in signature.parameters})

getattr() дает вам связанный объект, дальнейшее связывание не требуется.

...