Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent instantiation of an abstract class

In Java, we can prevent instantiation of a class by making it an abstract class. I thought python would behave the same way. But to my surprise, I found that I can create an object of an abstract class:

from abc import ABCMeta
class Foo(metaclass=ABCMeta):
    pass
Foo()

Why does python allow this and how can I prevent this?

like image 413
codingsplash Avatar asked Oct 28 '25 17:10

codingsplash


1 Answers

Python is for "consenting adults" - you could mark a class as abstract by naming convention within a project if you want (or module membership for that). However, it is feasible to do a hard "uninstantiable" abstract class - but that would not increase the security or good practices in a project in itself, as the commenters to the question propose.

So, to keep the remaining machinery for ABC's abstract classes, you can inherit the ABCMeta class, and use it to decorate the __new__ method so it won't instantiate - otherwise, just do the same, but inherit from type instead.

In other words, the code below wraps __new__ method on classes created with it as a metaclass. When that method is run, it checks if the class it is instantiating is the class marked with the ABC meta itself, if it is, it raises a typeerror instead.

    class ReallyAbstract(ABCMeta):
        def __new__(metacls, name, bases, namespace):
            outter_cls = super().__new__(metacls, name, bases, namespace)
            for bases in outter_cls.__mro__:
                if getattr(getattr(bases, "__new__", None), "_abstract", False):
                    # Base class already marked as abstract. No need to do anything else
                    return outter_cls
            original_new = getattr(outter_cls, "__new__")
            if getattr(original_new, "_abstract", False):
                # If we get a method that has already been wrapped
                # just return it unchanged.
                # TODO: if further classes on the hierarhy redfine __new__
                return outter_cls
    
            def __new__(cls, *args, **kw):
                if cls is outter_cls:
                    raise TypeError
                return original_new(cls, *args, **kw)
            __new__._abstract = True
            outter_cls.__new__ = __new__
            return outter_cls

And on the console:

    In [7]: class A(metaclass=ReallyAbstract):
        ...:     pass
        ...: 
    
    In [7]: A()
       TypeError                                 Traceback (most recent call last)
    <ipython-input-7-...> in <module>()
    ----> 1 A()
    
    ....

Just for sake of completeness - ABCMeta's in Python are not instantiable if they contain at least one "abstractmethod". Just like other O.O. features that are enforced in more static languages, the idea is to have this by convention. But yes, I agree that since they got to the work of creating an AbstractClass mechanism at all, it should probably behave with less surprises, and that would mean that the should not be instantiable by default.

like image 143
jsbueno Avatar answered Oct 31 '25 10:10

jsbueno