Enforcing Class Variables in a Subclass
This works. Will prevent subclasses from even being defined, let alone instantiated.
class Foo:
page_name = None
author = None
def __init_subclass__(cls, **kwargs):
for required in ('page_name', 'author',):
if not getattr(cls, required):
raise TypeError(f"Can't instantiate abstract class {cls.__name__} without {required} attribute defined")
return super().__init_subclass__(**kwargs)
class Bar(Foo):
page_name = 'New Page'
author = 'eric'
Python will already throw an exception if you try to use an attribute that doesn't exist. That's a perfectly reasonable approach, as the error message will make it clear that the attribute needs to be there. It is also common practice to provide reasonable defaults for these attributes in the base class, where possible. Abstract base classes are good if you need to require properties or methods, but they don't work with data attributes, and they don't raise an error until the class is instantiated.
If you want to fail as quickly as possible, a metaclass can prevent the user from even defining the class without including the attributes. The nice thing about a metaclass is that it's inheritable, so if you define it on a base class it is automatically used on any class derived on it.
Here's such a metaclass; in fact, here's a metaclass factory that lets you easily pass in the attribute names you wish to require.
def build_required_attributes_metaclass(*required_attrs):
class RequiredAttributesMeta(type):
def __init__(cls, name, bases, attrs):
missing_attrs = ["'%s'" % attr for attr in required_attrs
if not hasattr(cls, attr)]
if missing_attrs:
raise AttributeError("class '%s' requires attribute%s %s" %
(name, "s" * (len(missing_attrs) > 1),
", ".join(missing_attrs)))
return RequiredAttributesMeta
Now to actually define a base class using this metaclass is a bit tricky. You have to define the attributes to define the class, that being the entire point of the metaclass, but if the attributes are defined on the base class they are also defined on any class derived from it, defeating the purpose. So what we'll do is define them (using a dummy value) then delete them from the class afterward.
class Base(object):
__metaclass__ = build_required_attributes_metaclass("a", "b" ,"c")
a = b = c = 0
del Base.a, Base.b, Base.c
Now if you try to define a subclass, but don't define the attributes:
class Child(Base):
pass
You get:
AttributeError: class 'Child' requires attributes 'a', 'b', 'c'
N.B. I don't have any experience with Google App Engine, so it's possible it already uses a metaclass. In this case, you want your RequiredAttributesMeta
to derive from that metaclass, rather than type
.
Abstract Base Classes allow to declare a property abstract, which will force all implementing classes to have the property. I am only providing this example for completeness, many pythonistas think your proposed solution is more pythonic.
import abc
class Base(object):
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def value(self):
return 'Should never get here'
class Implementation1(Base):
@property
def value(self):
return 'concrete property'
class Implementation2(Base):
pass # doesn't have the required property
Trying to instantiate the first implementing class:
print Implementation1()
Out[6]: <__main__.Implementation1 at 0x105c41d90>
Trying to instantiate the second implementing class:
print Implementation2()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-bbaeae6b17a6> in <module>()
----> 1 Implementation2()
TypeError: Can't instantiate abstract class Implementation2 with abstract methods value