Reputation:
I want to create a class hierarchy in which I have a class Block
which can be instantiated by itself. Then I have a class List
which inherits from Block
and contains methods common to all lists, and finally I have classes OrderedList
, LableledList
etc that inherit from List
. I want people to be able to instantiate OrderedList
etc, but not List
.
In other words, you can instantiate a plain Block
and you can instantiate an OrderedList
that inherits from List
that inherits from Block
, but you can't instantiate List
.
All attempts to Google this lead to Abstract Base Classes, but none provides and example that fits this case and I am having trouble extrapolating.
Upvotes: 5
Views: 3487
Reputation: 21991
The following conversation with the interpreter should show how this is possible. After inheriting from the Abstract Base Class with Block
, you only need to mark the initializer on List
as being an abstractmethod
. This will prevent instantiation of the class without causing problems for child classes.
>>> import abc
>>> class Block(abc.ABC):
def __init__(self, data):
self.data = data
>>> class List(Block):
@abc.abstractmethod
def __init__(self, data, extra):
super().__init__(data)
self.extra = extra
>>> class OrderedList(List):
def __init__(self, data, extra, final):
super().__init__(data, extra)
self.final = final
>>> instance = Block(None)
>>> instance = List(None, None)
Traceback (most recent call last):
File "<pyshell#42>", line 1, in <module>
instance = List(None, None)
TypeError: Can't instantiate abstract class List with abstract methods __init__
>>> instance = OrderedList(None, None, None)
>>>
Upvotes: 4
Reputation: 110261
The "one and obvious" way to do this is to use ABCMeta and mark some methods as abstract as documented on other answers.
But if in your case you don't have a set of methods that one has to override in a mandatory way (let's suppose your __init__
is reusable in some cases, and other of the list methods as well):
In that case you can create a __new__
method that checks if the clas being istantiated is the own class, and raises. To do that, you have to use teh magic __class__
variable that is documentend only in corners of Python docs - if you as much as use the __class__
variable in any method body, it will automatically take the value of the class where it was declared, at run time. It is part of the parameterless super
mechanism of Python 3.
Thus:
class List(Block):
def __new__(cls, *args, **kw):
if cls is __class__:
raise TypeError(cls.__name__ + " can't be directly instantiated")
return super().__new__(cls, *args, **kw)
Btw, you should give preference for the ABCMeta abstractmethods if your pattern allows it. Note that if your classes use a custom metaclass it will conflict with the ABCMeta as well - so you may need to resort to this as well
(If you don't further customize __new__
, then you'd better not pass args
and kw
upstream on the __new__
method: Python's object.__new__
ignore extra args if __init__
is defined but __new__
is not in the subclasses - but if both are defined it raises an error)
Upvotes: 0
Reputation: 160397
Inherit from ABC
located in the abc
module and make methods that are implemented in base classes (that inherit from List
) @abstractmethod
s (a decorator located in abc
):
from abc import ABC, abstractmethod
class List(ABC, Block):
@abstractmethod
def size(self):
return 0
Having an ABC with @abstractmethod
s defined forbids from instantiation.
Upvotes: 0
Reputation: 314
Your List class should have ABCMeta as a metaclass and make the init methods abstract.
from abc import ABCMeta
class List(metaclass=ABCMeta):
@abstractmethod
__init__():
pass
https://docs.python.org/3/library/abc.html
Upvotes: 0