ABC pour les classes abstraites Python et la saisie de canard

Cet article est l'article du 8ème jour du Calendrier de l'Avent Python 2015.


J'écris habituellement Go, et honnêtement, j'écris rarement Python. Je pense l'utiliser en mathématiques, alors j'ai sauté dans le calendrier de l'Avent de Python cette fois.

Désormais, dans Go, il est important de définir une interface et de l'implémenter dans un modèle abstrait pour un développement à grande échelle. Il ne s'agit pas seulement de Go, les objets sont abstraits, et c'est un excellent code plus lisible et moins dépendant d'une utilisation modeste.

Pendant ce temps, j'ai étudié le type de méthode disponible pour l'abstraction en Python. Utilisez celui qui n'existe pas dans la spécification du langage et qui est fourni sous la forme d'un module nommé ABC (Abstract Base Class).

ABC - Module de classes de base abstraites

En Python, les classes abstraites peuvent être implémentées à l'aide du module ABC (Abstract Base Class) (https://docs.python.org/3.5/library/abc.html). La classe de base abstraite peut être définie par une métaclasse appelée ABCMeta, et la classe de base abstraite définie peut être utilisée comme une superclasse pour définir des sous-classes.

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

from abc import ABCMeta, abstractmethod

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

#Hériter de la classe abstraite
class Cat(Animal):
    def sound(self):
        print("Meow")

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

Nous avons défini une classe abstraite appelée ʻAnimal et implémenté la classe Cat qui en a hérité. Puisqu'il est hérité, il semble naturel que ʻis sous-classe, ʻis instance` passe.

Si la classe héritée Cat n'implémente pas le son de la méthode abstraite (@abstractmethod sera décrite plus tard), une erreur d'exécution se produira comme indiqué ci-dessous. (* Lors de la création d'une instance)

class Cat(Animal):
    pass

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

Enregistrement de sous-classe virtuelle avec la méthode register

D'un autre côté, au lieu de définir des sous-classes, il est possible d'enregistrer des classes non liées pour se comporter comme des classes abstraites. C'est ce qu'on appelle une ** sous-classe virtuelle **.

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

from abc import ABCMeta, abstractmethod

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

#N'hérite pas de la classe abstraite,`sound`Mettre en œuvre la méthode
class Dog():
    def sound(self):
        print("Bow")

#Enregistrer un chien à un animal de classe abstraite
Animal.register(Dog)

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

Si vous vous inscrivez dans une sous-classe virtuelle, cette classe sera celle d'une classe abstraite, mais si la méthode abstraite n'est pas implémentée, une erreur d'exécution se produira comme indiqué ci-dessous. (* Lors de l'appel d'une méthode)

class Dog():
    pass

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

Décorateur de méthode abstraite

@abstractmethod

Un décorateur qui montre une méthode abstraite. Bien qu'il s'agisse d'une méthode abstraite, il est également possible de décrire le processus dans une méthode qui spécifie un décorateur et de l'appeler à partir d'une sous-classe.

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

from abc import ABCMeta, abstractmethod

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

#Hériter de la classe abstraite
class Cat(Animal):
    def sound(self):
        #Appelez le son de la source d'héritage
        super(Cat, self).sound()
        print("Meow")

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

Vous pouvez appeler la méthode abstraite héritée avec super (Cat, self) .sound (). C'est un peu différent de Java.

@abstractclassmethod (version 3.2)

C'est un décorateur de la méthode de classe abstraite, mais à partir de la version 3.3, il est décrit par @ classmethod comme suit.

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

@abstractstaticmethod (version 3.2)

C'est un décorateur d'une méthode statique abstraite, mais à partir de la version 3.3, il est décrit par @ staticmethod comme suit.

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

Typage de canard

Ce qui précède est suffisant pour les classes abstraites, mais je voulais vérifier [Duck Typing](https://ja.wikipedia.org/wiki/Duck Typing) après cela, donc je vais l'implémenter. ..

Qu'est-ce que le typage du canard?

"S'il marche comme un canard et charlatan comme un canard, ce doit être un canard." - "Si vous marchez comme un canard et pleurez, c'est un canard."

C'est un peu comme un nom.

Simplifions cela et disons: «Si vous pleurez, c'est un animal». Et si vous le mettez de force dans la programmation, "Si vous implémentez une méthode appelée" pleurer "sur un objet, la classe concrète sera" animal "." C'est difficile à comprendre pour la première fois.

la mise en oeuvre

C'est plus rapide de voir que de penser dans sa tête.

#!/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)

Si vous implémentez et exécutez comme, vous obtiendrez le résultat et l'erreur d'exécution suivants.

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

La saisie de canard est correcte car «Livre» sans «son» n'est pas «pas de son = animal». Cependant, je voudrais détecter l'erreur si possible. Je ne peux pas nier le sentiment qu'il s'agit de frappe dynamique, mais j'aimerais éviter cela si possible, alors je vais faire ce qui suit.

Proposition 1. try ~ ʻexcept`

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

C'est une méthode pour détecter si une exception est crachée avec try, mais comme il s'agit d'une base d'exécution, elle ne peut être gérée qu'après qu'elle devienne réellement une exception. Je ne peux pas le recommander beaucoup.

Proposition 2. hasattr

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

Vérifie si l'attribut existe. Eh bien, c'est raisonnable.

Proposition 3. «est une instance»

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

Détermine si la classe est spécifiée par ʻis instance`. Cela vous permettra de gérer la classe abstraite prévue.

mypy

Il y avait aussi une vérification de typage statique appelée mypy. Vous pouvez être assuré si vous êtes habitué à la saisie statique.

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

Mais les langages typés dynamiquement s'y sont établis, il est donc subtil de les rendre statiques.

en conclusion

C'est bien de pouvoir implémenter des classes abstraites en Python, mais je ne peux pas dire quand une erreur se produira. Quant à l'abstraction, elle est intéressante car les spécifications sont fixées dans chaque langue mais les concepts sont légèrement différents. Je sens que je ressens les caractéristiques de la langue.

Recommended Posts

ABC pour les classes abstraites Python et la saisie de canard
À propos des objets et des classes Python
[Python] À propos des classes Executor et Future
J'ai créé une classe en Python et essayé de taper du canard
Typage Python
Classes et instances Python, méthodes d'instance
Lier des méthodes aux classes et instances Python
Parler d'anciennes et de nouvelles classes en Python
Typage dynamique de Python
[Introduction à Python3 Jour 12] Chapitre 6 Objets et classes (6.3-6.15)
Comment fonctionnent les classes python et les méthodes magiques.
[Introduction à Python3 Jour 11] Chapitre 6 Objets et classes (6.1-6.2)
[Hikari-Python] Chapitre 09-02 Classes (Création et instanciation de classes)
[python] Compresser et décompresser
Astuces Python et Numpy
Le mémo Python le plus simple au Japon (classes et objets)
[Python] pip et roue
Itérateur et générateur Python
Paquets et modules Python
Intégration Vue-Cli et Python
Les classes Python sont lentes
Ruby, Python et carte
entrée et sortie python
Python et Ruby se séparent
Cours de base Python (13 cours)
Comprendre le typage du canard Python
Python asyncio et ContextVar
Liste des informations sur les arguments de méthode pour les classes et les modules en Python
[Introduction to Data Scientists] Bases de Python ♬ Fonctions et classes
Programmation avec Python et Tkinter
Chiffrement et déchiffrement avec Python
Python: variables de classe et d'instance
3-3, chaîne Python et code de caractère
Série Python 2 et série 3 (édition Anaconda)
Python et matériel - Utilisation de RS232C avec Python -
Python sur Ruby et Ruby en colère sur Python
Indentation Python et format de chaîne
division des nombres réels python (/) et division des nombres entiers (//)
Installez Python et Flask (Windows 10)
À propos des variables et des objets Python
Apache mod_auth_tkt et Python AuthTkt
Å (Ongustorome) et NFC @ Python
Apprenez à connaître les packages et les modules Python
# 2 [python3] Séparation et commentaire
Copie superficielle Python et copie profonde
Mémo tranche python et rubis
Installation de Python et grammaire de base
J'ai comparé Java et Python!
Copie superficielle Python et copie profonde
Classe Python pour apprendre avec la chimioinfomatique
À propos de Python, len () et randint ()
À propos de la date et du fuseau horaire Python
Installez Python 3.7 et Django 3.0 (CentOS)
Paiza Python Primer 8: Comprendre les classes
Construction d'environnement Python et TensorFlow
Variables de classe et d'instance Python
Syntaxe Ruby et Python ~ branch ~
[Python] Python et sécurité-① Qu'est-ce que Python?