Introduction to Python Abstract Classes and Abstract Methods; Define and Implement Python Abstract Classes and Abstract Methods

    Beatme CodeBY-NC-ND
    17:26 read·2267 words· published

    Prerequisites

    Reading this article presupposes a good grasp of Python class inheritance as well as class polymorphism, which you can check out Introduction to Python Class Inheritance; Implement Python Class Inheritance and Multiple Inheritance, and Override Methods for information on that.

    Interfaces in Python

    You can’t define interfaces for Python as you can in other languages (e.g., C#) by using a keyword like interface; “interface” in Python is a conceptual term used to denote a functional implementation convention.

    Define Python Abstract Classes

    Although you can’t define an interface, Python abstract classes can achieve an approximation of an interface by constraining their derived classes to implement certain methods or attributes through overrides.

    Similarly, Python does not provide a keyword like abstract for declaring an abstract class or an abstract base class (ABC); the definition of a Python abstract base class needs to be accomplished by specifying a metaclass, and any class that specifies the ABCMeta class of the abc module as a metaclass can be called a Python abstract base class. Specifying a metaclass requires the keyword metaclass, so the syntax for defining a Python abstract base class is as follows.

    class <classname>(<metaclass=ABCMeta>):
        <block>

    classname part

    classname is the name of the abstract base class, which needs to conform to Python's identifier specification and cannot use Python keywords or reserved keywords.

    block part

    block is the body code of the abstract base class and needs to be indented with some sort of whitespace character to indicate that it belongs to the abstract base class.

    The ABCMeta class of the Python abc module

    The ABCMeta class of the abc module inherits from type and can be used as a metaclass for other classes to realize the effects of abstract base classes. Python metaclasses are often used to exert more control over the creation of instances, and one of the uses of the ABCMeta class is to check if all the abstract methods or attributes have been overridden, and if not, the instance cannot be created. Otherwise, ABCMeta doesn’t affect the class much in other ways; you can do the usual things in Python abstract classes, such as defining and using non-abstract instance methods, class methods, and so on.

    It is important to note that the ABCMeta class does not appear in the Method Resolution Order of Python abstract base classes and their derived classes, and the ABCMeta class is not a base class of Python abstract base classes.

    Next, we specify ABCMeta as a metaclass of class Movable, and look at the Method Resolution Order and base classes of the abstract base class Movable, and find that ABCMeta is not one of them.

    abstract.py
    from abc import ABCMeta
    
    # Abstract base class representing movable class Movable(metaclass=ABCMeta): pass
    # Show Movable's Method Resolution Order and base classes print(Movable.__mro__) print(Movable.__bases__)
    (<class '__main__.Movable'>, <class 'object'>)
    (<class 'object'>,)

    Of course, using the keyword metaclass may feel odd, and Python provides an alternative way of defining an abstract base class, by inheriting directly from the ABC class of the abc module, with the following syntax form, where classname and block have the same meaning as before.

    class <classname>(ABC):
        <block>

    The ABC class of the Python abc module

    The ABC class of the abc module is a helper class that designates ABCMeta as a metaclass, and Python classes derived from ABC become abstract base classes. You can’t use ABC via the metaclass keyword as you would use the ABCMeta class; that would cause some problems.

    Unlike the ABCMeta class, the ABC class appears in the Method Resolution Order of Python’s abstract base classes and its derived classes, and the ABC class is the base class of Python abstract base classes.

    Next, we derive the Accessible class from ABC, look at the Method Resolution Order and base classes of the abstract base class Accessible, and find ABC in it.

    abstract.py
    from abc import ABC
    
    # Abstract base class representing accessible class Accessible(ABC): pass
    # Show Accessible's Method Resolution Order and base classes print(Accessible.__mro__) print(Accessible.__bases__)
    (<class '__main__.Accessible'>, <class 'abc.ABC'>, <class 'object'>)
    (<class 'abc.ABC'>,)

    Define Abstract Methods for Python Abstract Classes

    In Python abstract base classes or their derived classes, you can use the @abstractmethod decorator provided by the abc module to define abstract methods or abstract attributes that are essentially methods. The @abstractmethod decorator can be used in conjunction with other decorators (@classmethod, @staticmethod, @property, etc.) to form more complex method definitions, such as abstract class methods, abstract static methods, abstract properties, etc.

    Python 3.2 used to include some other decorators on abstraction that were deprecated in Python 3.3, they are @abstractclassmethod, @abstractstaticmethod, @abstractproperty.

    ABCMeta checks whether a Python class derived from an abstract base class overrides all defined abstract methods or attributes in the inheritance chain when an instance of that Python class is created.

    The @abstractmethod decorator of the Python abc module

    The main purpose of the @abstractmethod decorator is to mark Python abstract methods in order to allow the ABCMeta class to inspect them; it does not affect other aspects of the marked method. For example, methods marked as abstract methods can have their own body code and can be accessed in derived classes via super.

    The @abstractmethod decorator should be written after related decorators (@classmethod, @staticmethod, @property, etc.) to avoid raising the exception AttributeError.

    Python abstract class methods and abstract static methods can be called in an unimplemented state

    Since ABCMeta checks for implementations of Python abstract methods or attributes only when creating an instance, Python abstract class methods and abstract static methods, or abstract class attributes that are essentially methods, can be called directly without being implemented, as long as this process does not create an associated Python instance.

    In the following code, the Animal class defines two abstract methods, count and name, which are not implemented by the derived class Cat, but are feasible to be called via Cat or Animal.

    abstract_methods.py
    from abc import ABCMeta, abstractmethod
    
    class Animal(metaclass=ABCMeta): # Abstract class method count @classmethod @abstractmethod def count(cls): pass
    # Abstract static method name @staticmethod @abstractmethod def name(): print('What\'s the name?')
    class Cat(Animal): pass
    # Calls the unimplemented static method name and the unimplemented class method count Cat.name() Animal.count() # ERROR Cat does not implement the abstract base class Animal Cat().name()
    What's the name?

    TypeError: Can't instantiate abstract class Cat without an implementation for abstract methods 'count', 'name'

    Update Python abstract classes with the update_abstractmethods function

    The abc module’s update_abstractmethods function (which supports use as a class decorator) can be used to update a Python abstract class when abstract attributes are dynamically added to the class, or when the state of an abstract attribute changes.

    It should be noted that the update_abstractmethods function only updates the target abstract class, it does not update the base classes or derived classes of the target abstract class.

    update_abstractmethods(cls)

    cls parameter

    The cls parameter is the abstract class to be updated.

    Next, we dynamically add an abstract method show to the abstract base class Color, which is not implemented by the Black class, but the creation of its instances is feasible because ABCMeta is not aware of the newly added abstract method until the related abstract class is updated via the update_abstractmethods function.

    abstract_update.py
    from abc import ABC, abstractmethod, update_abstractmethods
    
    # Define the abstract base class Color and the derived class Black class Color(ABC): pass class Black(Color): pass
    # Dynamically add an abstract method to the abstract base class @abstractmethod def show(self): pass Color.show = show
    print('Create the first Black instance') # There is no problem with creating an instance Black()
    # Update the abstract classes Color and Black update_abstractmethods(Color) update_abstractmethods(Black) print('Create the second Black instance') # ERROR You can't create an instance because Black doesn't implement the abstract method show Black()
    Create the first Black instance
    Create the second Black instance

    TypeError: Can't instantiate abstract class Black without an implementation for abstract method 'show'

    Implement Python Abstract Classes and Abstract Methods

    As described above, overriding an already defined Python abstract method or an abstract attribute that is essentially a method means that the abstract method or attribute is implemented. A Python abstract class is said to be implemented when all of its abstract methods and attributes are implemented.

    Overriding Python abstract methods or attributes cannot be done with the decorator @abstractmethod

    When overriding a Python abstract method or attribute, you must not write the decorator @abstractmethod for it again, or the method or attribute will be recognized as an abstract definition rather than an implementation of the abstraction.

    In the following example, the Flower class implements the abstract method grow of the Plant class and defines its own abstract class method count, so neither Plant nor Flower can create instances, and the BigFlower class implements the abstract class method count of the Flower class, so BigFlower can create instances.

    abstract_implement.py
    from abc import ABCMeta, abstractmethod
    
    class Plant(metaclass=ABCMeta): # Define the abstract method grow @abstractmethod def grow(self): print('Plant: Grow it!')
    class Flower(Plant): # Override the abstract method grow to implement it @abstractmethod def grow(self): super().grow()
    # Define the abstract class method count @classmethod @abstractmethod def count(self): pass
    class BigFlower(Flower): # Override the abstract method count to implement it @classmethod def count(self): print('How many are there?')
    # Cannot create instances of Plant and Flower BigFlower().grow()
    Plant: Grow it!

    Register Python Classes as Virtual Derived Classes that Don't Need to Implement Abstract Classes

    Any Python abstract base class, or one of its derived classes, has a method named register that can be used to register an unrelated class as a virtual derived class of a Python abstract class. Unlike Python classes derived directly from an abstract class, a virtual derived class can be instantiated regardless of whether or not it already implements all of the abstract methods in the abstract class.

    register(subclass)

    subclass parameter

    The subclass parameter is the class to be registered as a virtual derived class.

    In addition, in Python 3.3 or later, the register method can be used as a class decorator to register a class as a virtual derived class of some Python abstract class. The syntax is @abstractclass.register, with abstractclass being an abstract class.

    Python abstract classes do not appear in the Method Resolution Order of their virtual derived classes

    Although a virtual derived class can be determined by the issubclass function to be a derived class of a Python abstract class, and an instance of a virtual derived class can be determined by the isinstance function to be an instance of a Python abstract class, a Python abstract class, and all of its abstract base classes, will not appear in the Method Resolution Order of its virtual derived classes, which means that you cannot use super to access methods or other attributes in Python abstract classes because, well, these abstract classes are not searched by Python.

    How can I view the cache tokens for Python's virtual derived classes?

    Whenever the register method of any Python abstract class is called, including when it is called as a decorator, the cache token of the Python virtual derived class is changed, which can be verified with the get_cache_token function of the abc module (requires Python 3.4 or later).

    Next, we register Jerry, Tom as virtual derived classes of the abstract base class Man via the register method and use the function get_cache_token to see the changes in the cache token.

    After registration as virtual derived classes, Jerry and Tom can create instances with or without the implementation of abstract methods. However, since Man is not in the Method Resolution Order of Jerry and Tom, the statement super().run() of the Tom class will result in the exception AttributeError.

    abstract_register.py
    from abc import ABC, abstractmethod, get_cache_token
    
    # Define the abstract base class Man class Man(ABC): @abstractmethod def run(self): pass
    # Register the classes Jerry and Tom as virtual derived classes and see how the token changes print(f'The token is {get_cache_token()}')
    @Man.register class Jerry(): pass
    print(f'The token is {get_cache_token()}')
    class Tom(): def run(self): # ERROR Man is not in the MRO super().run() Man.register(Tom)
    print(f'The token is {get_cache_token()}')
    # Jerry and Tom are considered derived classes print(issubclass(Jerry, Man)) print(issubclass(Tom, Man)) # You can create an instance as normal print(isinstance(Jerry(), Man)) print(isinstance(Tom(), Man))
    Tom().run()
    The token is 24
    The token is 25
    The token is 26
    True
    True
    True
    True

    AttributeError: 'super' object has no attribute 'run'

    Use the class method __subclasshook__ to determine the derived class of a Python abstract class

    You can define the class method __subclasshook__ for Python abstract classes to determine whether a class is a derived class of a Python abstract class. The __subclasshook__ method affects the issubclass function, when its return value is True or False, the issubclass function will also return True or False, and when its return value is NotImplemented, the issubclass function will determine and return the result according to its original logic.

    Once the class method __subclasshook__ is defined for a Python abstract class, then __subclasshook__ will apply to all derived classes of that abstract class if it is not overridden by a derived class.

    __subclasshook__(subclass)

    subclass parameter

    The subclass parameter is the class that needs to be determined if it is a derived class.

    We define the class method __subclasshook__ for the following Unit class and return False, which will cause all classes not to be recognized as derived classes of Unit, even if they do inherit from Unit.

    abstract_subclasshook.py
    from abc import ABC
    
    class Unit(ABC): # None of the classes are derived from the Unit @classmethod def __subclasshook__(cls, subclass): print(f'Is {subclass} a derived class of Unit?') return False
    class Player(Unit): pass
    # View the relationship between Player and Unit print(issubclass(Player, Unit)) print(isinstance(Player(), Unit))
    Is <class '__main__.Player'> a derived class of Unit?
    False
    False

    Source Code

    src/en/classes/abstract·codebeatme/python·GitHub