Применение правил метакласса ко всем потомкам, а не к прямому классу метакласса

Позвольте мне начать с определения целей, которые у меня есть:

  1. Включение определения членов абстрактного класса (не свойств, методов или членов экземпляра) без значений по умолчанию (не None или какое-либо другое магическое значение, но выдает ошибку, если оно не реализовано).

  2. На службе (1) создание повторно используемого абстрактного механизма, который делает создание классов с абстрактными членами тривиальным, а код для этого максимально лаконичным.

  3. Наличие возможности присоединять члены абстрактного класса к родительскому классу (в качестве подкласса, метакласса или любым другим способом), где абстрактные члены не нужно определять до тех пор, пока не будет определен конкретный подкласс.

Что у меня есть до сих пор:

метакласс_lib.py:

class AbstractRequiredClassMembersMetaclass(type):

    def __new__(cls, name, bases, attrs):

        missed_members = []
        for class_member in cls.REQUIRED_CLASS_MEMBERS:
            if class_member not in attrs:
                missed_members.append(class_member)
        if missed_members:
            raise NotImplementedError('Class missing required members %s.' % missed_members)
        return super(AbstractRequiredClassMembersMetaclass, cls).__new__(cls, name, bases, attrs)



class _MakeRequiredClassMemebersMetaclass(object):

    existing_classes = {}

    def __call__(self, name, required_members):

        if name in _MakeRequiredClassMemebersMetaclass.exisiting_classes:
            if required_members != _MakeRequiredClassMemebersMetaclass.exisiting_classes[name].REQUIRED_CLASS_MEMBERS:
                raise RuntimeError('Class of name %s already defined with different required_members.' % name)
        else:
            NewClass = type(name, (AbstractRequiredClassMembersMetaclass,), {'REQUIRED_CLASS_MEMBERS' : required_members})
            _MakeRequiredClassMemebersMetaclass.exisiting_classes[name] = NewClass
        return _MakeRequiredClassMemebersMetaclass.exisiting_classes[name]

make_required_class_members_metaclass = _MakeRequiredClassMemebersMetaclass()

goods.py (реализация для иллюстрации):

from metaclass_lib import make_required_class_members_metaclass

class AbstractGood(object):

    __metaclass__ = make_required_class_members_metaclass('AbstractGoodMeta', ('unit_measure',))

    def __init__(self):

        self.unit_price = None

    def total_cost(self, number_of_units):

        return self.unit_price * self.number_of_units



class DeliGood(AbstractGood):

    unit_measure = 'lbs'

    def __init__(self):

        self.sandwich_counter_product = False



class RefridgeratedGood(AbstractGood):

    unit_measure = 'package'

    def __init__(self):

        self.freezer_safe = False
        self.optimal_temp = '35'

Это не работает, потому что метакласс задыхается во время создания объекта AbstractGood type. Проблема в том, что я хочу, чтобы все конкретные товары определяли член класса, но я не хочу определять член класса ни в одной из абстрактных баз. Все, о чем я могу думать, это заставить метакласс выполнять проверку in attrs только в том случае, если ключевое слово не находится в name (например, if 'Abstract' not in name), но это кажется дерганым и хрупким.

Есть лучший способ сделать это?


person Silas Ray    schedule 23.05.2012    source источник
comment
Да, есть лучший способ. Используйте модуль ABC из стандартной библиотеки Python.   -  person agf    schedule 23.05.2012
comment
@agf, за исключением того, что я понимаю, это допускает только абстрактные методы и свойства, а не абстрактные члены класса.   -  person Silas Ray    schedule 23.05.2012


Ответы (1)


Тоже взлом, но более надежный, чем проверка имени. Вы знаете, что абстрактный базовый класс не имеет базовых классов (или имеет только тип object в python 2). Так что можете проверить :)

class AbstractBase(object):
   class __metaclass__(type):
        def __new__(mcs, name, bases, dict_):
           if bases[0] is not object: # hack to not apply these on AbstractBase
                assert 'unit_measure' in dict_
           return type.__new__(mcs, name, bases, dict_)


class DeliGood(AbstractBase):
    unit_measure = 'lbs'
person schettino72    schedule 29.06.2013