Reputation: 26748
Basically, I knew that abstract base classes are used as skeleton classes just like regular classes, but enforces that abstract methods should be overridden by the child/inherited classes if it has one like below
Class AbstractClass(object):
__metaclass__ = abc.ABCMeta
@abstractmethod
def absmethod(self):
pass
class ChildClass(AbstractClass):
def absmethod(self):
print "Yeah!"
obj = ChildClass()
So we can create an object of ChildClass
as above
We know we can't instantiate an abstract class as it meant to be just skeleton and we will get an error if we try to instantiate it as below
obj = AbstractClass()
*** TypeError: Can't instantiate abstract class AbstractClass with abstract methods absmethod
But what my actual query about posting this StackOverflow
is if we create an abstract class by using abc.ABCMeta
, without abstract methods, I can able to create an instance of the abstract class which should not be the case(correct me if I am wrong)
Class AbstractClass(object):
__metaclass__ = abc.ABCMeta
obj = AbstractClass()
OOOPPPSS it worked, we can actually create an object of abstract classes without abstract methods? So please let me know the key points behind this
Upvotes: 16
Views: 12992
Reputation: 11522
I actually think Python's ABC implementation is incorrect, or at least misleading. Wikipedia gives a pretty standard definition for an abstract type/class:
an abstract type is a type in a nominative type system that cannot be instantiated directly [emphasis mine]
Yet Python's "abstract classes" can clearly be instantiated directly:
from abc import ABC
class Abstract(ABC):
pass
abc_instance = ABC() # No errors here
abstract_instance = Abstract() # Also totally fine
Building on @Aran-Fey's answer, I've written a more complete version of AbstractBase
which resists instantiation:
from abc import ABC
class AbstractBase(ABC):
_abstract: bool = True
def __init_subclass__(cls, /, abstract=False, **kwargs):
parent = cls.mro()[1]
if parent is not AbstractBase:
cls._abstract = abstract
return super().__init_subclass__(**kwargs)
def __new__(cls, *args, **kwargs):
if cls._abstract:
raise TypeError((
"Abstract classes descended from AbstractBase"
" may not be directly instantiated"
))
return super().__new__(cls, *args, **kwargs)
Here are its characteristics:
# AbstractBase will raise a TypeError on direct instantiation:
base_instance = AbstractBase() # raises TypeError
# Direct children of AbstractBase will also be considered abstract,
# and so also raise a TypeError:
class MyAbstractClass(AbstractBase):
pass
abstract_instance = MyAbstractClass() # raises TypeError
# Grandchildren of AbstractBase will be considered concrete,
# and so can be instantiated:
class ConcreteChild(MyAbstractClass):
pass
concrete_instance = ConcreteChild() # Success
# But this can be overridden to create abstract grandchildren
# by passing `abstract=True` in the class definition
class AbstractChild(MyAbstractClass, abstract=True)
pass
abstractchild_instance = AbstractChild() # raises TypeError
Upvotes: 0
Reputation: 91
I usually just declare the base class's __init__
with @abc.abstractmethod
.
If my base class does not have an __init__
, I add a trivial one.
Something like this:
class AbstractClass(abc.ABC):
@abc.abstractmethod
def __init__(self):
pass
# other useful non-abstract methods...
class ChildClass(AbstractClass):
def __init__(self):
pass
if __name__ == '__main__':
child = ChildClass() # allowed
abstract = AbstractClass() # TypeError
Upvotes: 9
Reputation: 151
Here is solution without using ABC. More on specific case it required for here
class AbstractClass:
_is_abstract = True
def __init__(self):
if self._is_abstract:
raise RuntimeError("Abstract class instantiation.")
# do initialization
def __init_subclass__(self): # is called every time class is subclassed
self._is_abstract = False # thus makes sure abstract check returns False on subclass
Upvotes: 0
Reputation: 43326
From the docs:
A class that has a metaclass derived from
ABCMeta
cannot be instantiated unless all of its abstract methods and properties are overridden.
Conversely, this means that any class with no abstract methods or properties like your AbstractClass
can be instantiated.
If you want to disallow instantiation of the topmost parent class, you can write a custom class that performs a type check in its __new__
method:
class SubclassOnlyABC(object):
__metaclass__ = abc.ABCMeta
def __new__(cls, *args, **kwargs):
if cls.__bases__ == (SubclassOnlyABC,):
msg = 'Abstract class {} cannot be instantiated'.format(cls.__name__)
raise TypeError(msg)
return super(SubclassOnlyABC, cls).__new__(cls, *args, **kwargs)
class AbstractClass(SubclassOnlyABC):
pass
class ChildClass(AbstractClass):
pass
ChildClass() # works because it's a child class of an abstract class
AbstractClass() # throws TypeError because its parent class is "object"
You can also write a __new__
method that prevents instantiation of classes with no abstract methods:
class NonEmptyABC(object):
__metaclass__ = abc.ABCMeta
def __new__(cls, *args, **kwargs):
# check if ANY abstractmethod exists
for parentcls in cls.__mro__:
if any(getattr(attr, '__isabstractmethod__', False)
for attr in vars(parentcls).values()):
break
else:
msg = 'Abstract class {} cannot be instantiated'.format(cls.__name__)
raise TypeError(msg)
return super(NonEmptyABC, cls).__new__(cls, *args, **kwargs)
class EmptyAbstractClass(NonEmptyABC):
pass
class NonemptyAbstractClass(NonEmptyABC):
@abc.abstractmethod
def foo(self):
pass
class NonemptyChild(NonemptyAbstractClass):
def foo(self):
pass
NonemptyChild() # works because "foo" is an abstractmethod
EmptyAbstractClass() # throws TypeError because there are no abstractmethods
Upvotes: 11
Reputation: 16763
Since Python is a dynamic languages, the very idea of enforcing classes to inherit from a particular class goes against duck typing. Hence, the use case of Abstract classes in Python is pretty limited and provided more for a conventional reason. Still if you want to block the instantiation of a class without declaring virtual methods, you can, however,
class AbstractClass(object):
__metaclass__ = abc.ABCMeta
def __new__(cls, *args, **kwargs):
if cls is AbstractClass:
raise Exception('Abstract class cannot be instantiatied')
return object.__new__(*args, **kwargs)
Upvotes: 7