Python ABC-Abstract Classes and Duck Typing

This article is the 8th day article of Python Advent Calendar 2015.


I usually write Go, and honestly I rarely write Python. I'm thinking about using it in mathematics, so I jumped into Python's Advent Calendar this time.

Now, in Go, it is important to define an interface and implement it in an abstract pattern for large-scale development. This isn't just about Go, objects are abstracted, and it's a great code that's more readable and less dependent on modest use.

Meanwhile, I investigated what kind of method is available for abstraction in Python. Use the one that does not exist in the language specification and is provided as a module named ABC (Abstract Base Class).

ABC --Abstract Base Classes Module

In Python, abstract classes can be implemented using the ABC (Abstract Base Class) (https://docs.python.org/3.5/library/abc.html) module. The abstract base class can be defined by a metaclass called ABCMeta, and the defined abstract base class can be used as a superclass to define subclasses.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from abc import ABCMeta, abstractmethod

#Abstract class
class Animal(metaclass=ABCMeta):
    @abstractmethod
    def sound(self):
        pass

#Inherit abstract class
class Cat(Animal):
    def sound(self):
        print("Meow")

if __name__ == "__main__":
    assert issubclass(Cat().__class__, Animal)
    assert isinstance(Cat(), Animal)

We have defined an abstract class called ʻAnimal and implemented the Cat class that inherited it. Since it is inherited, it seems natural that ʻis subclass, ʻis instance` passes.

If the inherited Cat class does not implement the sound of the abstract method (@abstractmethod will be described later), a runtime error will occur as shown below. (* When creating an instance)

class Cat(Animal):
    pass

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

Register a virtual subclass with the register method

On the other hand, instead of defining subclasses, it is possible to register unrelated classes to behave like abstract classes. This is called a ** virtual subclass **.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from abc import ABCMeta, abstractmethod

class Animal(metaclass=ABCMeta):
    @abstractmethod
    def sound(self):
        pass

#Does not inherit the abstract class, but`sound`Implement method
class Dog():
    def sound(self):
        print("Bow")

#Register Dog to Animal of abstract class
Animal.register(Dog)

if __name__ == "__main__":
    assert issubclass(Dog().__class__, Animal)
    assert isinstance(Dog(), Animal)

If you register to a virtual subclass, that class will be an abstract class, but if the abstract method is not implemented, a runtime error will occur as shown below. (* When calling a method)

class Dog():
    pass

# AttributeError: 'Dog' object has no attribute 'sound'

Abstract method decorator

@abstractmethod

A decorator that represents an abstract method. Although it is an abstract method, it is also possible to describe the process in a method that specifies a decorator and call it from a subclass.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from abc import ABCMeta, abstractmethod

class Animal(metaclass=ABCMeta):
    @abstractmethod
    def sound(self):
        print("Hello")

#Inherit abstract class
class Cat(Animal):
    def sound(self):
        #Call the inherited sound
        super(Cat, self).sound()
        print("Meow")

if __name__ == "__main__":
    print(Cat().sound())

You can call the inherited abstract method with super (Cat, self) .sound (). It's a little different from Java.

@abstractclassmethod (version 3.2)

It is a decorator of abstract class method, but from version 3.3, it is described by @ classmethod as follows.

class Animal(metaclass=ABCMeta):
    @classmethod
    @abstractmethod
    def sound_classmethod(self):
        pass

@abstractstaticmethod (version 3.2)

It is a decorator of abstract static method, but from version 3.3, it is described by @staticmethod as follows.

class Animal(metaclass=ABCMeta):
    @staticmethod
    @abstractmethod
    def sound_staticmethod(self):
        pass

Duck typing

The above is enough for abstract classes, but I wanted to check [duck typing](https://ja.wikipedia.org/wiki/duck typing) after doing so, so I will implement it. ..

What is duck typing?

"If it walks like a duck and quacks like a duck, it must be a duck."-"If you walk like a duck and cry, it's a duck."

It's kind of like a name.

Let's simplify this and say, "If you cry, it's an animal." And if you forcibly put it into programming, "If you implement a method called" crying "on an object, the concrete class will be" animal "." It's hard to understand for the first time.

Implementation

It's faster to see than to think in your head.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from abc import ABCMeta, abstractmethod

class Animal(metaclass=ABCMeta):
    @abstractmethod
    def sound(self):
        pass

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

class Dog():
    def sound(self):
        print("Bow")

class Book():
    pass

Animal.register(Dog)

def output(animal):
    print(animal.__class__.__name__, end=": ")
    animal.sound()

if __name__ == "__main__":
    c = Cat()
    output(c)

    d = Dog()
    output(d)

    b = Book()
    output(b)

If you implement and execute like, you will get the following result and runtime error.

Cat: Meow
Dog: Bow
AttributeError: 'Book' object has no attribute 'sound'

Book without sound in duck typing is OK because it is not "no sound = animal". However, I would like to detect the error if possible. I can't deny the feeling that this is dynamically typed, but I'd like to avoid it if possible, so I'll do the following.

Proposal 1. try ~ ʻexcept`

def output(animal):
    print(animal.__class__.__name__, end=": ")
    try:
        animal.sound()
    except AttributeError:
        print('No sound')

It is a method to catch if exception is thrown with try, but since it is an execution base, it can be handled only after it actually becomes an exception. I can't recommend it very much.

Proposal 2. hasattr

def output(animal):
    if not hasattr(animal, 'sound'):
        return
    print(animal.__class__.__name__, end=": ")
    animal.sound()

Checks if the Attribute exists. Well, it's reasonable.

Proposal 3. ʻis instance`

def output(animal):
    if not isinstance(animal, Animal):
        return
    print(animal.__class__.__name__, end=": ")
    animal.sound()

Determines if the class is specified by ʻis instance`. This will allow you to handle the intended abstract class.

mypy

There was also a static typing check called mypy. If you're used to static typing, you can rest assured.

From Python...

def fib(n):
    a, b = 0, 1
    while a < n:
        yield a
        a, b = b, a+b

...to statically typed Python

def fib(n: int) -> Iterator[int]:
    a, b = 0, 1
    while a < n:
        yield a
        a, b = b, a+b

However, dynamically typed languages have established a position with it, so it seems subtle to make it static now.

in conclusion

It's nice to be able to implement abstract classes in Python, but I can't say when the error will occur. As for abstraction, it is interesting because the specifications are fixed in each language but the concept is slightly different. I feel that I feel the characteristics of the language.

Recommended Posts

Python ABC-Abstract Classes and Duck Typing
About python objects and classes
[Python] About Executor and Future classes
I created a class in Python and tried duck typing
Python typing
Python classes and instances, instance methods
Bind methods to Python classes and instances
A way to understand Python duck typing
Talking about old and new Python classes
Python dynamic typing
[Introduction to Python3 Day 12] Chapter 6 Objects and Classes (6.3-6.15)
How python classes and magic methods are working.
[Introduction to Python3 Day 11] Chapter 6 Objects and Classes (6.1-6.2)
[Python for Hikari] Chapter 09-02 Classes (Creating and instantiating classes)
[python] Compress and decompress
Python and numpy tips
The simplest Python memo in Japan (classes and objects)
[Python] pip and wheel
Batch design and python
Python iterators and generators
Python packages and modules
Vue-Cli and Python integration
Python classes are slow
Ruby, Python and map
python input and output
Python and Ruby split
Python basic course (13 classes)
Understand Python's duck typing
Python3, venv and Ansible
Python asyncio and ContextVar
List method argument information for classes and modules in Python
[Introduction to Data Scientists] Basics of Python ♬ Functions and classes
Programming with Python and Tkinter
Encryption and decryption with Python
Python: Class and instance variables
3-3, Python strings and character codes
Python 2 series and 3 series (Anaconda edition)
Python and hardware-Using RS232C with Python-
Python on Ruby and angry Ruby on Python
Python indentation and string format
Python real division (/) and integer division (//)
Install Python and Flask (Windows 10)
About Python variables and objects
Apache mod_auth_tkt and Python AuthTkt
Å (Ongustromu) and NFC @ Python
Understand Python packages and modules
# 2 [python3] Separation and comment out
Python shallow copy and deep copy
Python and ruby slice memo
Python installation and basic grammar
I compared Java and Python!
Python shallow and deep copy
Python classes learned in chemoinformatics
About Python, len () and randint ()
About Python datetime and timezone
Install Python 3.7 and Django 3.0 (CentOS)
Paiza Python Primer 8: Understanding Classes
Python environment construction and TensorFlow
Python class variables and instance variables
Ruby and Python syntax ~ branch ~
[Python] Python and security-① What is Python?