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?
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>
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.
Abstract#foo
. Calling it directly should be forbidden, but it should still be possible to call it with super()
.
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.
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
super().__init__()
needed in your AbstractClass
constructor?
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
.
@abstractclassmethod
is deprecated as of version 3.3, if I understand it correctly.
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
from abc import ABC
and class MyABC(ABC)
.
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()
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.
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()
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
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.
abc
module solution enforces implementing the designated abstract methods in concrete subclasses. NB in 2021 addGas
should be something like add_energy
...
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
Just a quick addition to @TimGilbert's old-school answer...you can make your abstract base class's init() method throw an exception and that would prevent it from being instantiated, no?
>>> class Abstract(object):
... def __init__(self):
... raise NotImplementedError("You can't instantiate this class!")
...
>>> a = Abstract()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
NotImplementedError: You can't instantiate this class!
Success story sharing
@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.
@abstractmethod
for__init__
method as well, see stackoverflow.com/q/44800659/547270