67

I am trying to declare an abstract class A with a constructor with a default behavior: all subclasses must initialize a member self.n:

from abc import ABCMeta

class A(object):
    __metaclass__ = ABCMeta

    def __init__(self, n):
        self.n = n

However, I do not want to let the A class be instantiated because, well, it is an abstract class. The problem is, this is actually allowed:

a = A(3)

This produces no errors, when I would expect it should.

So: how can I define an un-instantiable abstract class while defining a default behavior for the constructor?

ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
dabadaba
  • 9,064
  • 21
  • 85
  • 155
  • 1
    Technically, Abstract Classes shouldn't be instantiated, since that they work as blueprints. Plenty of material point this concept, if you want to review them. Here are some: https://en.wikipedia.org/wiki/Abstract_type and https://www.python-course.eu/python3_abstract_classes.php. Anyways, I appreciate that you pointed out the need of defining a constructor for your Abstract Class, which might be handy for a Concrete Class. Thanks :). – ivanleoncz Aug 07 '20 at 19:32

6 Answers6

85

Making the __init__ an abstract method:

from abc import ABCMeta, abstractmethod

class A(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def __init__(self, n):
        self.n = n


if __name__ == '__main__':
    a = A(3)

helps:

TypeError: Can't instantiate abstract class A with abstract methods __init__

Python 3 version:

from abc import ABCMeta, abstractmethod

class A(object, metaclass=ABCMeta):

    @abstractmethod
    def __init__(self, n):
        self.n = n


if __name__ == '__main__':
    a = A(3)

Works as well:

TypeError: Can't instantiate abstract class A with abstract methods __init__
Mike Müller
  • 82,630
  • 20
  • 166
  • 161
  • 1
    That is not producing any error !! instantiating works fine – void Jun 28 '17 at 11:19
  • Tested with Python 2.7. – Mike Müller Jun 28 '17 at 11:21
  • Yeah I'm using python3 ...Why is it though? @MikeMüller – void Jun 28 '17 at 11:23
  • 29
    This would still require defining `__init__` in each of the base classes(with a super`()` call). – Ashwini Chaudhary Jun 28 '17 at 11:30
  • 5
    quoting myself from a similar answer: question: what's the point of defining a class as abstract if you need to explicitly specify the constructor as an abstract method? (note I am not implying that all an abstract class is, is just an un-instantiable class) I would expect this behavior as default for an abstract class – dabadaba Jun 28 '17 at 11:35
  • @AshwiniChaudhary Isn't this the intention of the OP "all subclasses must initialize a member self.n"? – Mike Müller Jun 28 '17 at 11:35
  • 6
    @dabadaba: Setting the meta class to ABCmeta does not declare a class as abstract, it only makes it possible to do that (by declaring one of the methods as abstract). – Rörd Jun 28 '17 at 11:39
  • 1
    @MikeMüller Indeed, but that doesn't necessarily means all of them should redefine `__init__` every time. Like they asked: "how can I define an un-instantiable abstract class while defining a default behavior for the constructor?". Your approach was the first thing in my mind as well, but they had a different request. But anyways this has been accepted, so doesn't matter I guess. – Ashwini Chaudhary Jun 28 '17 at 23:01
  • 1
    But this has the obvious drawback of misleadingly denoting the constructor as abstract despite it clearly having an implementation! -- I don't like this. – Sebastian Nielsen Jan 09 '20 at 16:02
  • I think that this doesn't make sense. You can either use a mix-in class. For example, if you wanted to use the advantages of having an abstract class, but using the __init__ method in more than one inherited class(say for example, that you wanted to build a polymorphic class. You may be writing sth like this: class B(C, A). In which C will contain all the related with __init__ and so on. – Nick Cuevas Feb 27 '20 at 21:09
9

A not so elegant solution can be this:

class A(object):
  def __init__(self, n):
    if self.__class__ == A:
      raise Exception('I am abstract!')
    self.n = n

Usage

class B(A):
  pass
a = A(1)  # Will throw exception
b = B(1)  # Works fine as expected.
gipsy
  • 3,859
  • 1
  • 13
  • 21
  • 1
    Why not use the built-in functionalities to create an actual abstract class with an abstract constructor (as shown by @Mike Müller) ? – Younes El Ouarti Feb 17 '20 at 13:48
  • 1
    because OP didn't ask for an abstract method at all. She says multiple times that she doesn't want to override `__init__` in child classes. – Joooeey Sep 24 '21 at 12:30
6

You can override __new__ method to prevent direct instantiation.

class A(object):
    __metaclass__ = ABCMeta

    def __new__(cls, *args, **kwargs):
        if cls is A:
            raise TypeError(
                "TypeError: Can't instantiate abstract class {name} directly".format(name=cls.__name__)
            )
        return object.__new__(cls)

Output:

>>> A()
Traceback (most recent call last):
  File "<ipython-input-8-3cd318a12eea>", line 1, in <module>
    A()
  File "/Users/ashwini/py/so.py", line 11, in __new__
    "TypeError: Can't instantiate abstract class {name} directly".format(name=cls.__name__)
TypeError: TypeError: Can't instantiate abstract class A directly
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • You're not supposed to forward `args` and `kwargs` to the `object` constructor. `object` takes no parameters. – Aran-Fey Jun 28 '17 at 11:19
  • @Rawing [Where does it say so?](https://docs.python.org/2/reference/datamodel.html#object.__new__) The arguments passed to `__init__` are passed to `__new__` and `__call__` later on. – Ashwini Chaudhary Jun 28 '17 at 11:26
  • Well then try `A(3)` and tell me if it works... The arguments passed to `__new__` are automatically forwarded to `__init__` after `__new__` returns an instance. You don't want to forward them to `object.__new__`, because it doesn't accept them. – Aran-Fey Jun 28 '17 at 11:30
  • 1
    Oh, nevermind, this works in python 2. Weird. Still, there's no reason to forward the parameters, is there? – Aran-Fey Jun 28 '17 at 11:34
4

You should define the methods as abstract as well with the @abc.abstractmethod decorator.

Ignacio Vergara Kausel
  • 5,521
  • 4
  • 31
  • 41
  • 1
    question: what's the point of defining a class as abstract if you need to explicitly specify the constructor as an abstract method? (note I am not implying that all an abstract class is, is just an un-instantiable class) – dabadaba Jun 28 '17 at 11:34
  • I guess because an ABCMeta is not exactly an abstract class like in other languages. – Ignacio Vergara Kausel Jun 28 '17 at 11:36
  • @dabadaba One benefit I can see is that you can have default methods defined in the abstract class, as well as abstract methods that must be implemented in its subclasses. – ryanjdillon Aug 03 '20 at 08:50
2

You can implement something like below :

from abc import ABC
class A(ABC):
    def __init__(self, n):
        self.n = n
        super(A,self).__init__()

Instantiating this class would throw an error

Tiger
  • 37
  • 5
1

I do not want to let the A class be instantiated because, well, it is an abstract class. The problem is, this is actually allowed

Python on its own doesn't provide abstract classes. 'abc' module which provides the infrastructure for defining Abstract Base Classes.

Abstract classes are classes that contain one or more abstract methods. In your case code still an abstract class that should provide "Abstract classes cannot be instantiated" behavior.

If you don't want to allow, program need corrections: i.e add decorator @abstractmethod. Below code executed in python 3.6

from abc import ABC, abstractmethod

class Myabs(ABC):

    def __init__(self, connection):
        self.connection = connection
        print("1. calling from base init : {}".format(self.connection))
        super(Myabs, self).__init__()
    @abstractmethod
    def fun1(self, val):
        pass

class D(Myabs):
    def fun1(self, val="hi"):
        print("calling from D: fun1")


object0 = Myabs('connection') # Python 3.6.9

output:

ClassConcept/abs_class.py Traceback (most recent call last): File "ClassConcept/abs_class.py", line 19, in object0 = Myabs('connection') # Python 3.6.9 TypeError: Can't instantiate abstract class Myabs with abstract methods fun1

Process finished with exit code 1

John Conde
  • 217,595
  • 99
  • 455
  • 496