Equivalent of NotImplementedError for fields in Python
Question:
In Python 2.x when you want to mark a method as abstract, you can define it like so:
class Base:
def foo(self):
raise NotImplementedError("Subclasses should implement this!")
Then if you forget to override it, you get a nice reminder exception. Is there an equivalent way to mark a field as abstract? Or is stating it in the class docstring all you can do?
At first I thought I could set the field to NotImplemented, but when I looked up what it’s actually for (rich comparisons) it seemed abusive.
Answers:
Yes, you can. Use the @property
decorator. For instance, if you have a field called “example” then can’t you do something like this:
class Base(object):
@property
def example(self):
raise NotImplementedError("Subclasses should implement this!")
Running the following produces a NotImplementedError
just like you want.
b = Base()
print b.example
def require_abstract_fields(obj, cls):
abstract_fields = getattr(cls, "abstract_fields", None)
if abstract_fields is None:
return
for field in abstract_fields:
if not hasattr(obj, field):
raise RuntimeError, "object %s failed to define %s" % (obj, field)
class a(object):
abstract_fields = ("x", )
def __init__(self):
require_abstract_fields(self, a)
class b(a):
abstract_fields = ("y", )
x = 5
def __init__(self):
require_abstract_fields(self, b)
super(b, self).__init__()
b()
a()
Note the passing of the class type into require_abstract_fields
, so if multiple inherited classes use this, they don’t all validate the most-derived-class’s fields. You might be able to automate this with a metaclass, but I didn’t dig into that. Defining a field to None is accepted.
Alternate answer:
@property
def NotImplementedField(self):
raise NotImplementedError
class a(object):
x = NotImplementedField
class b(a):
# x = 5
pass
b().x
a().x
This is like Evan’s, but concise and cheap–you’ll only get a single instance of NotImplementedField.
And here is my solution:
def not_implemented_method(func):
from functools import wraps
from inspect import getargspec, formatargspec
@wraps(func)
def wrapper(self, *args, **kwargs):
c = self.__class__.__name__
m = func.__name__
a = formatargspec(*getargspec(func))
raise NotImplementedError(''%s' object does not implement the method '%s%s'' % (c, m, a))
return wrapper
def not_implemented_property(func):
from functools import wraps
from inspect import getargspec, formatargspec
@wraps(func)
def wrapper(self, *args, **kwargs):
c = self.__class__.__name__
m = func.__name__
raise NotImplementedError(''%s' object does not implement the property '%s'' % (c, m))
return property(wrapper, wrapper, wrapper)
It can be used as
class AbstractBase(object):
@not_implemented_method
def test(self):
pass
@not_implemented_property
def value(self):
pass
class Implementation(AbstractBase):
value = None
def __init__(self):
self.value = 42
def test(self):
return True
A better way to do this is using Abstract Base Classes:
import abc
class Foo(abc.ABC):
@property
@abc.abstractmethod
def demo_attribute(self):
raise NotImplementedError
@abc.abstractmethod
def demo_method(self):
raise NotImplementedError
class BadBar(Foo):
pass
class GoodBar(Foo):
demo_attribute = 'yes'
def demo_method(self):
return self.demo_attribute
bad_bar = BadBar()
# TypeError: Can't instantiate abstract class BadBar
# with abstract methods demo_attribute, demo_method
good_bar = GoodBar()
# OK
Note that you should still have raise NotImplementedError
instead of something like pass
, because there is nothing preventing the inheriting class from calling super().demo_method()
, and if the abstract demo_method
is just pass
, this will fail silently.
An interesting pattern to handle this is to set attribute to None
in the parent class and to access the attribute with a function that ensure it has been set in the child class.
Here is an example from django-rest-framework:
class GenericAPIView(views.APIView):
[...]
serializer_class = None
[...]
def get_serializer_class(self):
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class
It seem that this question was open to both instance attributes and class attributes, I’ll focus on the first topic only.
So, for instance attributes, an alternate answer to Evan’s is to define a mandatory field using pyfields
:
from pyfields import field
class Base(object):
example = field(doc="This should contain an example.")
b = Base()
b.example
yields
pyfields.core.MandatoryFieldInitError:
Mandatory field 'example' has not been initialized yet
on instance <__main__.Base object at 0x000002C1000C0C18>.
Granted, it does not provide you with the ability to edit the error message by talking about subclasses. But in a way it is more realistic to not talk about subclasses – indeed in python, attributes can be overridden on instances of the base class – not only in subclasses.
Note: I’m the author of pyfields
. See documentation for details.
Here is a simple example how to set required properties/methods for sublasses in Python 3.
class Base:
requires = ('foo', 'bar')
def __init_subclass__(cls, **kwargs):
for requirement in cls.requires:
if not hasattr(cls, requirement):
raise NotImplementedError(
f'"{cls.__name__}" must have "{requirement}".')
super().__init_subclass__(**kwargs)
In Python 2.x when you want to mark a method as abstract, you can define it like so:
class Base:
def foo(self):
raise NotImplementedError("Subclasses should implement this!")
Then if you forget to override it, you get a nice reminder exception. Is there an equivalent way to mark a field as abstract? Or is stating it in the class docstring all you can do?
At first I thought I could set the field to NotImplemented, but when I looked up what it’s actually for (rich comparisons) it seemed abusive.
Yes, you can. Use the @property
decorator. For instance, if you have a field called “example” then can’t you do something like this:
class Base(object):
@property
def example(self):
raise NotImplementedError("Subclasses should implement this!")
Running the following produces a NotImplementedError
just like you want.
b = Base()
print b.example
def require_abstract_fields(obj, cls):
abstract_fields = getattr(cls, "abstract_fields", None)
if abstract_fields is None:
return
for field in abstract_fields:
if not hasattr(obj, field):
raise RuntimeError, "object %s failed to define %s" % (obj, field)
class a(object):
abstract_fields = ("x", )
def __init__(self):
require_abstract_fields(self, a)
class b(a):
abstract_fields = ("y", )
x = 5
def __init__(self):
require_abstract_fields(self, b)
super(b, self).__init__()
b()
a()
Note the passing of the class type into require_abstract_fields
, so if multiple inherited classes use this, they don’t all validate the most-derived-class’s fields. You might be able to automate this with a metaclass, but I didn’t dig into that. Defining a field to None is accepted.
Alternate answer:
@property
def NotImplementedField(self):
raise NotImplementedError
class a(object):
x = NotImplementedField
class b(a):
# x = 5
pass
b().x
a().x
This is like Evan’s, but concise and cheap–you’ll only get a single instance of NotImplementedField.
And here is my solution:
def not_implemented_method(func):
from functools import wraps
from inspect import getargspec, formatargspec
@wraps(func)
def wrapper(self, *args, **kwargs):
c = self.__class__.__name__
m = func.__name__
a = formatargspec(*getargspec(func))
raise NotImplementedError(''%s' object does not implement the method '%s%s'' % (c, m, a))
return wrapper
def not_implemented_property(func):
from functools import wraps
from inspect import getargspec, formatargspec
@wraps(func)
def wrapper(self, *args, **kwargs):
c = self.__class__.__name__
m = func.__name__
raise NotImplementedError(''%s' object does not implement the property '%s'' % (c, m))
return property(wrapper, wrapper, wrapper)
It can be used as
class AbstractBase(object):
@not_implemented_method
def test(self):
pass
@not_implemented_property
def value(self):
pass
class Implementation(AbstractBase):
value = None
def __init__(self):
self.value = 42
def test(self):
return True
A better way to do this is using Abstract Base Classes:
import abc
class Foo(abc.ABC):
@property
@abc.abstractmethod
def demo_attribute(self):
raise NotImplementedError
@abc.abstractmethod
def demo_method(self):
raise NotImplementedError
class BadBar(Foo):
pass
class GoodBar(Foo):
demo_attribute = 'yes'
def demo_method(self):
return self.demo_attribute
bad_bar = BadBar()
# TypeError: Can't instantiate abstract class BadBar
# with abstract methods demo_attribute, demo_method
good_bar = GoodBar()
# OK
Note that you should still have raise NotImplementedError
instead of something like pass
, because there is nothing preventing the inheriting class from calling super().demo_method()
, and if the abstract demo_method
is just pass
, this will fail silently.
An interesting pattern to handle this is to set attribute to None
in the parent class and to access the attribute with a function that ensure it has been set in the child class.
Here is an example from django-rest-framework:
class GenericAPIView(views.APIView):
[...]
serializer_class = None
[...]
def get_serializer_class(self):
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class
It seem that this question was open to both instance attributes and class attributes, I’ll focus on the first topic only.
So, for instance attributes, an alternate answer to Evan’s is to define a mandatory field using pyfields
:
from pyfields import field
class Base(object):
example = field(doc="This should contain an example.")
b = Base()
b.example
yields
pyfields.core.MandatoryFieldInitError:
Mandatory field 'example' has not been initialized yet
on instance <__main__.Base object at 0x000002C1000C0C18>.
Granted, it does not provide you with the ability to edit the error message by talking about subclasses. But in a way it is more realistic to not talk about subclasses – indeed in python, attributes can be overridden on instances of the base class – not only in subclasses.
Note: I’m the author of pyfields
. See documentation for details.
Here is a simple example how to set required properties/methods for sublasses in Python 3.
class Base:
requires = ('foo', 'bar')
def __init_subclass__(cls, **kwargs):
for requirement in cls.requires:
if not hasattr(cls, requirement):
raise NotImplementedError(
f'"{cls.__name__}" must have "{requirement}".')
super().__init_subclass__(**kwargs)