Shiva Krishna Bavandla
Shiva Krishna Bavandla

Reputation: 26748

abstract classes without abstract methods creating objects in python

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

Answers (5)

Robin Winslow
Robin Winslow

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

Extending ABC into a true "abstract base class"

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

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

Andrey Gusakov
Andrey Gusakov

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

Aran-Fey
Aran-Fey

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

hspandher
hspandher

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

Related Questions