Applying metaclass rules to all descendants rather than direct metaclassed class

211 Views Asked by At

Let me start by defining the goals I have:

  1. Enabling definition of abstract class members (not properties, methods, or instance members) with no default values (not None or some other magic value, but throwing an error if not implemented).

  2. In service of (1), creating a reusable abstracted mechanism that makes creation of classes with abstract members trivial and the code to do so maximally concise.

  3. Having the ability to attach the abstract class members to a parent class (as a subclass, metaclass, or by any other means) where the abstract members need not be defined until a concrete subclass is defined.

What I've got so far:

metaclass_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 (implementation for illustration):

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'

This doesn't work because the metaclass chokes during creation of the AbstractGood type object. The problem is I want all concrete goods to define the class member, but I don't want to define the class member in any of the abstract bases. All I can think of is to have the metaclass only do the in attrs check if a keyword is not in name (ex if 'Abstract' not in name), but that seems janky and fragile.

Is there a better way to do this?

1

There are 1 best solutions below

0
On

Also a hack, but more reliable than checking the name. You know that an Abstract Base Class has no base classes (or has only object type in python 2). So you could check for that :)

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'