Is it possible to make abstract classes in Python?
Asked Answered
B

13

457

How can I make a class or method abstract in Python?

I tried redefining __new__() like so:

class F:
    def __new__(cls):
        raise Exception("Unable to create an instance of abstract class %s" %cls)

But now, if I create a class G that inherits from F like so:

class G(F):
    pass

Then, I can't instantiate G either, since it calls its super class's __new__ method.

Is there a better way to define an abstract class?

Bullheaded answered 30/11, 2012 at 13:29 Comment(1)
Yes, you can create abstract classes in python with the abc (abstract base classes) module. This site will help you with it: http://docs.python.org/2/library/abc.htmlBrittle
A
756

Use the abc module to create abstract classes. Use the abstractmethod decorator to declare a method abstract, and declare a class abstract using one of three ways, depending upon your Python version.

In Python 3.4 and above, you can inherit from ABC. In earlier versions of Python, you need to specify your class's metaclass as ABCMeta. Specifying the metaclass has different syntax in Python 3 and Python 2. The three possibilities are shown below:

# Python 3.4+
from abc import ABC, abstractmethod
class Abstract(ABC):
    @abstractmethod
    def foo(self):
        pass
# Python 3.0+
from abc import ABCMeta, abstractmethod
class Abstract(metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass
# Python 2
from abc import ABCMeta, abstractmethod
class Abstract:
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

Whichever way you use, you won't be able to instantiate an abstract class that has abstract methods, but will be able to instantiate a subclass that provides concrete definitions of those methods:

>>> Abstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Abstract with abstract methods foo
>>> class StillAbstract(Abstract):
...     pass
... 
>>> StillAbstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class StillAbstract with abstract methods foo
>>> class Concrete(Abstract):
...     def foo(self):
...         print('Hello, World')
... 
>>> Concrete()
<__main__.Concrete object at 0x7fc935d28898>
Assuan answered 30/11, 2012 at 13:30 Comment(13)
what does the @abstractmethod do? Why do you need it? If the class is already established as abstract shouldn't the compiler/interpret know that all the methods are from the abstract class in question?Internalcombustion
@CharlieParker - The @abstractmethod makes it so that the decorated function must be overridden before the class can be instantiated. 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.Penal
@CharlieParker - Basically, it lets you define a class in a manner where the sub-class must implement a specified suite of methods to instantiate at all.Penal
Is there a way to prohibit users from creating Abstract() without any @abstractmethod methods?Profitsharing
@Profitsharing it appears you may use @abstractmethod for __init__ method as well, see https://mcmap.net/q/24937/-how-define-constructor-implementation-for-an-abstract-class-in-python/547270Lyndseylyndsie
How do I write code which does this and is able to support any python version?Mosenthal
Why can't I use only @abstractmethod without ABC? It feels unpythonic. When I write abstractmethod it means that this is an abstract base class already. Or do I miss sth?Kaminski
There is one more reason have @abstractmethod decorator to methods and inheriting ABC. A class that is derived from an abstract class cannot be instantiated unless all of its abstract methods are overridden.Venegas
fyi, the Python2 solution is compatible with Python3 as well, if you care about portability.Knighthood
I think it's funny that you have to import a childishly named abc module to do something that the language should support out of the box. Python showing its roots as a scripting language and not a language to be used for OOP.Humidify
It might be worth noting that an @abstractmethod such as foo() can have a non-trivial implementation. Although this code cannot be used directly (as the abstract class cannot be instantiated), a derived class implementing foo() can invoke super().foo().Purview
@Humidify it's not childishly named, just using the acronym of Abstract Base Class.Wert
@CharlieParker, you may want to cover up the implementation, but Python has never been afraid of showing some of it's roots above ground, and this can be especially useful. It's Pythonic. Also, Python does support abstract base classes out of the box: abc.ABC is part of the standard library. I think it's funny that people who don't like Python try to talk down to it as if it's not some "proper" language that they are more familiar with that is getting less popular while Python is taking over the world.Chaps
A
152

The old-school (pre-PEP 3119) way to do this is just to raise NotImplementedError in the abstract class when an abstract method is called.

class Abstract(object):
    def foo(self):
        raise NotImplementedError('subclasses must override foo()!')

class Derived(Abstract):
    def foo(self):
        print 'Hooray!'

>>> d = Derived()
>>> d.foo()
Hooray!
>>> a = Abstract()
>>> a.foo()
Traceback (most recent call last): [...]

This doesn't have the same nice properties as using the abc module does. You can still instantiate the abstract base class itself, and you won't find your mistake until you call the abstract method at runtime.

But if you're dealing with a small set of simple classes, maybe with just a few abstract methods, this approach is a little easier than trying to wade through the abc documentation.

Amphioxus answered 14/8, 2014 at 4:33 Comment(4)
Appreciate the simplicity and effectiveness of this approach.Brunell
Haha, I saw this everywhere in my OMSCS course and had no clue what it was :)Disappointed
It isn't really helpful, though. Because you might want to implement some common behavior inside Abstract#foo. Calling it directly should be forbidden, but it should still be possible to call it with super().Decigram
Nice little light weight alternative solution for use-cases that don't feel threatened by it.Chaps
J
29

Here's a very easy way without having to deal with the ABC module.

In the __init__ method of the class that you want to be an abstract class, you can check the "type" of self. If the type of self is the base class, then the caller is trying to instantiate the base class, so raise an exception. Here's a simple example:

class Base():
    def __init__(self):
        if type(self) is Base:
            raise Exception('Base is an abstract class and cannot be instantiated directly')
        # Any initialization code
        print('In the __init__  method of the Base class')

class Sub(Base):
    def __init__(self):
        print('In the __init__ method of the Sub class before calling __init__ of the Base class')
        super().__init__()
        print('In the __init__ method of the Sub class after calling __init__ of the Base class')

subObj = Sub()
baseObj = Base()

When run, it produces:

In the __init__ method of the Sub class before calling __init__ of the Base class
In the __init__  method of the Base class
In the __init__ method of the Sub class after calling __init__ of the Base class
Traceback (most recent call last):
  File "/Users/irvkalb/Desktop/Demo files/Abstract.py", line 16, in <module>
    baseObj = Base()
  File "/Users/irvkalb/Desktop/Demo files/Abstract.py", line 4, in __init__
    raise Exception('Base is an abstract class and cannot be instantiated directly')
Exception: Base is an abstract class and cannot be instantiated directly

This shows that you can instantiate a subclass that inherits from a base class, but you cannot instantiate the base class directly.

Jobyna answered 16/1, 2018 at 5:30 Comment(1)
Another interesting alternative.Chaps
O
24

Most Previous answers were correct but here is the answer and example for Python 3.7. Yes, you can create an abstract class and method. Just as a reminder sometimes a class should define a method which logically belongs to a class, but that class cannot specify how to implement the method. For example, in the below Parents and Babies classes they both eat but the implementation will be different for each because babies and parents eat a different kind of food and the number of times they eat is different. So, eat method subclasses overrides AbstractClass.eat.

from abc import ABC, abstractmethod

class AbstractClass(ABC):

    def __init__(self, value):
        self.value = value
        super().__init__()

    @abstractmethod
    def eat(self):
        pass

class Parents(AbstractClass):
    def eat(self):
        return "eat solid food "+ str(self.value) + " times each day"

class Babies(AbstractClass):
    def eat(self):
        return "Milk only "+ str(self.value) + " times or more each day"

food = 3    
mom = Parents(food)
print("moms ----------")
print(mom.eat())

infant = Babies(food)
print("infants ----------")
print(infant.eat())

OUTPUT:

moms ----------
eat solid food 3 times each day
infants ----------
Milk only 3 times or more each day
Overburden answered 10/9, 2018 at 5:1 Comment(3)
Can we write @abstractmethod on init function also?Mireille
+1 Why @abstractmethod def eat(self)? If this class is abstract and hence not meant to be instantiated why do you pass (self) to eat? It works fine without itChinook
Why is a call to super().__init__() needed in your AbstractClass constructor?Purview
G
17

As explained in the other answers, yes you can use abstract classes in Python using the abc module. Below I give an actual example using abstract @classmethod, @property and @abstractmethod (using Python 3.6+). For me it is usually easier to start off with examples I can easily copy&paste; I hope this answer is also useful for others.

Let's first create a base class called Base:

from abc import ABC, abstractmethod

class Base(ABC):

    @classmethod
    @abstractmethod
    def from_dict(cls, d):
        pass
    
    @property
    @abstractmethod
    def prop1(self):
        pass

    @property
    @abstractmethod
    def prop2(self):
        pass

    @prop2.setter
    @abstractmethod
    def prop2(self, val):
        pass

    @abstractmethod
    def do_stuff(self):
        pass

Our Base class will always have a from_dict classmethod, a property prop1 (which is read-only) and a property prop2 (which can also be set) as well as a function called do_stuff. Whatever class is now built based on Base will have to implement all of these four methods/properties. Please note that for a method to be abstract, two decorators are required - classmethod and abstract property.

Now we could create a class A like this:

class A(Base):
    def __init__(self, name, val1, val2):
        self.name = name
        self.__val1 = val1
        self._val2 = val2

    @classmethod
    def from_dict(cls, d):
        name = d['name']
        val1 = d['val1']
        val2 = d['val2']

        return cls(name, val1, val2)

    @property
    def prop1(self):
        return self.__val1

    @property
    def prop2(self):
        return self._val2

    @prop2.setter
    def prop2(self, value):
        self._val2 = value

    def do_stuff(self):
        print('juhu!')

    def i_am_not_abstract(self):
        print('I can be customized')

All required methods/properties are implemented and we can - of course - also add additional functions that are not part of Base (here: i_am_not_abstract).

Now we can do:

a1 = A('dummy', 10, 'stuff')
a2 = A.from_dict({'name': 'from_d', 'val1': 20, 'val2': 'stuff'})

a1.prop1
# prints 10

a1.prop2
# prints 'stuff'

As desired, we cannot set prop1:

a.prop1 = 100

will return

AttributeError: can't set attribute

Also our from_dict method works fine:

a2.prop1
# prints 20

If we now defined a second class B like this:

class B(Base):
    def __init__(self, name):
        self.name = name

    @property
    def prop1(self):
        return self.name

and tried to instantiate an object like this:

b = B('iwillfail')

we will get an error

TypeError: Can't instantiate abstract class B with abstract methods do_stuff, from_dict, prop2

listing all the things defined in Base which we did not implement in B.

Gurrola answered 28/7, 2019 at 15:9 Comment(3)
Note that there is an @abstactclassmethod annotation which you can use instead of two separate onesRehearsal
@FishingIsLife: According to the documentation @abstractclassmethod is deprecated as of version 3.3, if I understand it correctly.Gurrola
you are right. Thanks for the hint.Rehearsal
R
9

This one will be working in python 3

from abc import ABCMeta, abstractmethod

class Abstract(metaclass=ABCMeta):

    @abstractmethod
    def foo(self):
        pass

Abstract()
>>> TypeError: Can not instantiate abstract class Abstract with abstract methods foo
Rebbecarebbecca answered 11/12, 2015 at 15:47 Comment(1)
At present we can just from abc import ABC and class MyABC(ABC).Farley
B
3

also this works and is simple:

class A_abstract(object):

    def __init__(self):
        # quite simple, old-school way.
        if self.__class__.__name__ == "A_abstract": 
            raise NotImplementedError("You can't instantiate this abstract class. Derive it, please.")

class B(A_abstract):

        pass

b = B()

# here an exception is raised:
a = A_abstract()
Bibliophage answered 12/1, 2018 at 14:0 Comment(0)
T
3

You can also harness the __new__ method to your advantage. You just forgot something. The __new__ method always returns the new object so you must return its superclass' new method. Do as follows.

class F:
    def __new__(cls):
        if cls is F:
            raise TypeError("Cannot create an instance of abstract class '{}'".format(cls.__name__))
        return super().__new__(cls)

When using the new method, you have to return the object, not the None keyword. That's all you missed.

Tomlinson answered 10/5, 2019 at 0:45 Comment(0)
N
3

I find the accepted answer, and all the others strange, since they pass self to an abstract class. An abstract class is not instantiated so can't have a self.

So try this, it works.

from abc import ABCMeta, abstractmethod


class Abstract(metaclass=ABCMeta):
    @staticmethod
    @abstractmethod
    def foo():
        """An abstract method. No need to write pass"""


class Derived(Abstract):
    def foo(self):
        print('Hooray!')


FOO = Derived()
FOO.foo()
Noneffective answered 27/9, 2019 at 22:7 Comment(1)
Even abc documentation use self in their examples linkAstray
D
3
 from abc import ABCMeta, abstractmethod

 #Abstract class and abstract method declaration
 class Jungle(metaclass=ABCMeta):
     #constructor with default values
     def __init__(self, name="Unknown"):
     self.visitorName = name

     def welcomeMessage(self):
         print("Hello %s , Welcome to the Jungle" % self.visitorName)

     # abstract method is compulsory to defined in child-class
     @abstractmethod
     def scarySound(self):
         pass
Diecious answered 25/11, 2019 at 11:22 Comment(1)
Nice @Shivam Bharadwaj , I did it same wayOpposition
R
3

You can create an abstract class by extending ABC which stands for "Abstract Base Classes" and can create the abstract method with @abstractmethod in the abstract class as shown below:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

And, to use an abstract class, it should be extended by a child class and the child class should override the abstract method of the abstract class as shown below:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

class Cat(Animal): # Extends "Animal" abstract class
    def sound(self): # Overrides "sound()" abstract method
        print("Meow!!")

obj = Cat()
obj.sound()

Output:

Meow!!

And, an abstract method can have code rather than pass and can be called by a child class as shown below:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        print("Wow!!") # Here

class Cat(Animal):
    def sound(self):
        super().sound() # Here
        
obj = Cat()
obj.sound()

Output:

Wow!!

And, an abstract class can have the variables and non-abstract methods which can be called by a child class and non-abstract methods don't need to be overridden by a child class as shown below:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass
    
    def __init__(self): # Here
        self.name = "John" # Here
    
    x = "Hello" # Here
    
    def test1(self): # Here
        print("Test1")
    
    @classmethod # Here
    def test2(cls):
        print("Test2")
        
    @staticmethod # Here
    def test3():
        print("Test3")

class Cat(Animal):
    def sound(self):
        print(self.name) # Here
        print(super().x) # Here
        super().test1()  # Here
        super().test2()  # Here
        super().test3()  # Here

obj = Cat()
obj.sound()

Output:

John
Hello
Test1
Test2
Test3

And, you can define an abstract class and static methods and an abstract getter, setter and deleter in an abstract class as shown below. *@abstractmethod must be the innermost decorator otherwise error occurs and you can see my answer which explains more about an abstract getter, setter and deleter:

from abc import ABC, abstractmethod

class Person(ABC):

    @classmethod
    @abstractmethod # The innermost decorator
    def test1(cls):
        pass
    
    @staticmethod
    @abstractmethod # The innermost decorator
    def test2():
        pass

    @property
    @abstractmethod # The innermost decorator
    def name(self):
        pass

    @name.setter
    @abstractmethod # The innermost decorator
    def name(self, name):
        pass

    @name.deleter
    @abstractmethod # The innermost decorator
    def name(self):
        pass

Then, you need to override them in a child class as shown below:

class Student(Person):
    
    def __init__(self, name):
        self._name = name
    
    @classmethod
    def test1(cls): # Overrides abstract class method
        print("Test1")
    
    @staticmethod
    def test2(): # Overrides abstract static method
        print("Test2")
    
    @property
    def name(self): # Overrides abstract getter
        return self._name
    
    @name.setter
    def name(self, name): # Overrides abstract setter
        self._name = name
    
    @name.deleter
    def name(self): # Overrides abstract deleter
        del self._name

Then, you can instantiate the child class and call them as shown below:

obj = Student("John") # Instantiates "Student" class
obj.test1() # Class method
obj.test2() # Static method
print(obj.name) # Getter
obj.name = "Tom" # Setter
print(obj.name) # Getter
del obj.name # Deleter
print(hasattr(obj, "name"))

Output:

Test1
Test2
John 
Tom  
False

And, if you try to instantiate an abstract class as shown below:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

obj = Animal()

The error below occurs:

TypeError: Can't instantiate abstract class Animal with abstract methods sound

And, if you don't override the abstract method of an abstract class in a child class and you instantiate the child class as shown below:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

class Cat(Animal):
    pass # Doesn't override "sound()" abstract method

obj = Cat() # Here

The error below occurs:

TypeError: Can't instantiate abstract class Cat with abstract methods sound

And, if you define an abstract method in the non-abstract class which doesn't extend ABC, the abstract method is a normal instance method so there are no errors even if the non-abstract class is instantiated and even if a child class doesn't override the abstract method of the non-abstract class as shown below:

from abc import ABC, abstractmethod

class Animal: # Doesn't extend "ABC"
    @abstractmethod # Here
    def sound(self):
        print("Wow!!")

class Cat(Animal):
    pass # Doesn't override "sound()" abstract method

obj1 = Animal() # Here
obj1.sound()

obj2 = Cat() # Here
obj2.sound()

Output:

Wow!!
Wow!!

In addition, you can replace Cat class extending Animal class below:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

# ↓↓↓ Here ↓↓↓

class Cat(Animal):
    def sound(self):
        print("Meow!!")

# ↑↑↑ Here ↑↑↑

print(issubclass(Cat, Animal))

With this code having register() below:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

# ↓↓↓ Here ↓↓↓

class Cat:
    def sound(self):
        print("Meow!!")
        
Animal.register(Cat)

# ↑↑↑ Here ↑↑↑

print(issubclass(Cat, Animal))

Then, both of the code above outputs the same result below showing Cat class is the subclass of Animal class:

True
Roble answered 21/11, 2022 at 18:14 Comment(0)
W
2

Late to answer here, but to answer the other question "How to make abstract methods" which points here, I offer the following.

# decorators.py
def abstract(f):
    def _decorator(*_):
        raise NotImplementedError(f"Method '{f.__name__}' is abstract")
    return _decorator


# yourclass.py
class Vehicle:
    def add_energy():
       print("Energy added!")

    @abstract
    def get_make(): ...

    @abstract
    def get_model(): ...

The class base Vehicle class can still be instantiated for unit testing (unlike with ABC), and the Pythonic raising of an exception is present. Oh yes, you also get the method name that is abstract in the exception with this method for convenience.

Whereabouts answered 29/4, 2021 at 0:19 Comment(1)
I like this, but have a doubt: isn't the contract that an abstract method has to be implemented in a concrete subclass? The (admirable) simplicity of your solution (the poss of testing is a non-trivial aspect of its goodness) means that I can ask that question: it's not clear to me (as yet) whether the abc module solution enforces implementing the designated abstract methods in concrete subclasses. NB in 2021 addGas should be something like add_energy...Brooder
P
-3

In your code snippet, you could also resolve this by providing an implementation for the __new__ method in the subclass, likewise:

def G(F):
    def __new__(cls):
        # do something here

But this is a hack and I advise you against it, unless you know what you are doing. For nearly all cases I advise you to use the abc module, that others before me have suggested.

Also when you create a new (base) class, make it subclass object, like this: class MyBaseClass(object):. I don't know if it is that much significant anymore, but it helps retain style consistency on your code

Pelasgian answered 30/11, 2012 at 14:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.