Let me start by defining the goals I have:
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).
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.
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?
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 :)