Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 232
Shiva Krishna Bavandla Avatar asked Apr 30 '18 11:04

Shiva Krishna Bavandla


Video Answer


2 Answers

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)
like image 110
hspandher Avatar answered Oct 02 '22 15:10

hspandher


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
like image 38
Aran-Fey Avatar answered Oct 02 '22 15:10

Aran-Fey