Compréhension complète de la programmation orientée objet de Python

Orientation objet

1. Origine orientée objet

Lauréat du prix Turing 2003 [Alan Kay](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%A9%E3%83%B3%E3%83%BB% E3% 82% B1% E3% 82% A4) dit souvent [Programmation orientée objet](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82% B8% E3% 82% A7% E3% 82% AF% E3% 83% 88% E6% 8C% 87% E5% 90% 91% E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0) est appelé le père. La personne elle-même a déclaré à plusieurs reprises le droit d'invention dans lieu public. Cependant, Kay méprise les langages modernes orientés objet tels que "C ++" et "Java". Ces langues sont héritées du langage "Simula 67", et j'ai créé "[Smalltalk](https: // ja. Kay pense que cela n'a rien à voir avec "wikipedia.org/wiki/Smalltalk)".

Le nom orienté objet vient certainement d'Alan Kay. Cependant, l'orientation objet moderne utilisée en C ++ et Java est assez différente de l'original. Kay lui-même ne reconnaît pas ces langues comme successeurs. Alors, quel type de langage est Simula 67, le parent de C ++ et Java, selon Kay? Jetons maintenant un coup d'œil à un exemple de code simple.

Class Rectangle (Width, Height); Real Width, Height;
                           ! Class with two parameters;
 Begin
    Real Area, Perimeter;  ! Attributes;
 
    Procedure Update;      ! Methods (Can be Virtual);
    Begin
      Area := Width * Height;
      Perimeter := 2*(Width + Height)
    End of Update;
 
    Boolean Procedure IsSquare;
      IsSquare := Width=Height;
 
    Update;                ! Life of rectangle started at creation;
    OutText("Rectangle created: "); OutFix(Width,2,6);
    OutFix(Height,2,6); OutImage
 End of Rectangle;

C'est une classe avec deux variables. Je ne connais pas la grammaire, mais les commentaires vous donneront une idée générale de ce à quoi cela ressemble. Simula 67, comme son nom l'indique, est un langage de programmation introduit en 1967 et sorti en 1973. Smalltalk, en revanche, est le langage sorti dans les années 1980, avec la première version (Smalltalk-72) annoncée en 1975.

Avec class, ce n'est pas de la programmation orientée objet, mais la classe` de Simula 67 est" instance "," héritage "," méthode "et" [liaison tardive](https: //ja.wikipedia. org / wiki /% E3% 83% 80% E3% 82% A4% E3% 83% 8A% E3% 83% 9F% E3% 83% 83% E3% 82% AF% E3% 83% 90% E3% 82 % A4% E3% 83% B3% E3% 83% 87% E3% 82% A3% E3% 83% B3% E3% 82% B0) ". Simula 67 est définitivement un langage orienté objet.

Cependant, la conception orientée objet du Simula 67 n'est pas non plus originale. 1965, [Antony Hoa](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%B3%E3%83%88%E3%83%8B%E3%83% BC% E3% 83% BB% E3% 83% 9B% E3% 83% BC% E3% 82% A2) (lauréat du prix Turing 1980) un article /resources/text/algol/ACM_Algol_bulletin/1061032/p39-hoare.pdf) a été annoncé. Le concept de «classe d'enregistrement» a été soumis à l'article. Hoa a écrit un exemple dans la langue ALGOL.

record class person;
    begin   integer date of birth;
            Boolean male;
            reference   father, mother, youngest offspring, elder sbling (person)
    end;

Il s'agit d'un type de données complexe similaire au langage C Structure. Droite.

Et en 1966, lors d'une école d'été, Hoa a déclaré [Kristen Nigard](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AA%E3%82%B9% E3% 83% 86% E3% 83% B3% E3% 83% BB% E3% 83% 8B% E3% 82% AC% E3% 83% BC% E3% 83% 89) et [Oryohan Dahl]( https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%AB%E3%83%A8%E3%83%8F%E3%83%B3%E3%83%BB%E3 J'ai rencontré% 83% 80% E3% 83% BC% E3% 83% AB). Ce sont ces deux-là qui ont fait plus tard Simula 67. Hoa a partagé l'idée d'une «classe d'enregistrement» avec deux personnes. Selon M. Dar, à ce moment-là, M. Hoa avait déjà proposé le concept d '«héritage» et les avait enseignés. Et en 2001, Kristen Nigard et Orjohan Dahl ont remporté le prix Turing pour leur contribution à l'orientation objet. C'était deux ans plus tôt qu'Alan Kay.

Présentation de Simula 67. Vous comprenez également que Simula 67 est le premier langage orienté objet au monde. Alors, le Smalltalk que Kay a fait est-il un faux? En conclusion, ce n'est pas le cas. Lisp Pour le langage "Tout est une liste", Smalltalk a créé pour la première fois le concept de "Tout est un objet". .. De plus, Smalltalk interprète toutes les expressions, y compris les opérateurs, comme des «messages» aux objets. Smalltalk est le langage de programmation qui a donné un coup de pouce à l'orientation objet. Dans les années 1980, grâce à Smalltalk, des langages de programmation orientés objet ont été produits. Parmi eux, le C ++, qui est toujours bien vivant. En outre, Programmation fonctionnelle Le camp Lisp, le créateur du langage, a également aidé avec le "Common Lisp Object System".

Enfin, en 1996, Java, le summum du paradigme de programmation orienté objet moderne, a été annoncé. Il s'agit d'une étape majeure dans l'histoire orientée objet. Java lui-même n'a rien inventé dans l'orientation objet, mais il absorbe les excellents concepts jusqu'à présent, ainsi que [JVM](https://ja.wikipedia.org/wiki/Java%E4%BB%AE% Excellentes performances multi-plateformes de E6% 83% B3% E3% 83% 9E% E3% 82% B7% E3% 83% B3) et GC 82% AC% E3% 83% 99% E3% 83% BC% E3% 82% B8% E3% 82% B3% E3% 83% AC% E3% 82% AF% E3% 82% B7% E3% 83% Avec A7% E3% 83% B3), c'est toujours l'un des 3 premiers langages de programmation au monde.

2. Fonctionnalités orientées objet

Introduit l'origine de l'orientation de l'objet. Mais qu'est-ce qui est orienté objet en premier lieu? Avant d'entrer dans le sujet principal, je voudrais expliquer en utilisant un exemple simple.

L'orientation objet est souvent orientée processus ([Programmation procédurale](https://ja.wikipedia.org/wiki/%E6%89%8B%E7%B6%9A%E3%81%8D%E5%9E%8B%] Aussi appelé E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0)). Le code ci-dessous représente les formulaires orientés processus et orientés objet.

a = 0
# a+Je veux réaliser 3 fonctions

#Orienté processus
sum(a, 3)

#Orientation objet
a.sum(3)

Vous pourriez penser que c'est simplement écrit différemment. En fait, la programmation orientée objet peut clarifier la logique de votre code. Et plus le programme est gros, plus il est puissant. Ensuite, examinons de plus près les différences dans le code ci-dessus.

** 1. Syntaxe ** Vous pouvez interpréter la syntaxe des appels de fonction en termes d'ordre des mots.

** 2. Méthode de définition **

** 3. Méthode d'appel ** En pratique, lorsque vous souhaitez faire de même pour plusieurs objets:

Lorsqu'il existe de nombreux processus de ce type

Ensuite, examinons de plus près les avantages de l'orientation objet à travers des exemples de code Python.

2-1. Unification et gestion des interfaces

Définissez trois classes: oiseau, chien et poisson.

class Bird:
    def __init__(self, name):
        self.name = name

    def move(self):
        print("The bird named {} is flying".format(self.name))


class Dog:
    def __init__(self, name):
        self.name = name

    def move(self):
        print("The dog named {} is running".format(self.name))


class Fish:
    def __init__(self, name):
        self.name = name

    def move(self):
        print("The fish named {} is swimming".format(self.name))

Créez une instance.

bob = Bird("Bob")
john = Bird("John")
david = Dog("David")
fabian = Fish("Fabian")

Appelez ensuite la méthode move de toutes les instances.

bob.move()
john.move()
david.move()
fabian.move()

Résultat de l'exécution:

The bird named Bob is flying
The bird named John is flying
The dog named David is running
The fish named Fabian is swimming

Lorsque vous créez une instance, vous devez transmettre des paramètres. Ce paramètre est les données qui distinguent un objet des autres objets. Par exemple, le «nom» de l'objet «bob» est «Bob» et le «nom» de «john» est «John», donc même si l'instance a été créée à partir de la même «classe», ce sera un objet différent et le même. Même si vous exécutez la méthode, le résultat sera différent.

De plus, différentes méthodes de déplacement de classe produisent des résultats différents. Par exemple, move of Bird renvoieThe bird named ...et Dog afficheThe dog named .... La méthode «move» signifie «déplacer», et chaque «classe» d'animal peut être déplacée, donc en l'implémentant comme le même «move», l'interface est unifiée et il est plus facile à retenir.

Lorsqu'il est implémenté de manière orientée processus, cela peut ressembler à ceci:

def move_bird(name):
    print("The bird named {} is flying".format(name))


def move_dog(name):
    print("The dog named {} is runing".format(name))


def move_fish(name):
    print("The fish named {} is swimming".format(name))


bob = "Bob"
john = "John"
david = "David"
fabian = "Fabian"

move_bird(bob)
move_bird(john)
move_dog(david)
move_fish(fabian)

Lorsqu'un objet appelé «bob» arrive, il n'est pas possible de décider s'il faut le faire «move_bird» ou «move_dog» sans d'abord préciser s'il s'agit d'un «oiseau» ou d'un «chien». Dans un programme réel, il est courant d'implémenter non seulement "move", mais des dizaines de types de fonctions de traitement. À mesure que le nombre de fonctions augmente, il devient extrêmement difficile de clarifier la correspondance avec les variables. De plus, ces fonctions peuvent appeler d'autres fonctions en interne, et lors de la réutilisation de cette fonction dans d'autres programmes, il est nécessaire de connaître toutes les fonctions utilisées en interne et de les migrer.

Orienté objet utilise des variables pour créer une instance à partir de class, et vous pouvez voir quelles méthodes sont disponibles en regardant class. Et en l'abstruisant comme «classe», les fonctions dans le même contexte sont regroupées et plus faciles à gérer.

2-2. Encapsulation

L'orientation objet regroupe les fonctions et les données, ce qui est très pratique lorsque vous souhaitez traiter la même variable (données) avec de nombreuses fonctions.

class Person:
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height

    def describe(self):
        print("name: {}; age: {}; height: {}".format(self.name, self.age, self.height))

    def introduce(self):
        print("My name is {}, and height is {}, and age is {}. ".format(self.name, self.height, self.age))


bob = Person("Bob", 24, 170)
mary = Person("Mary", 10, 160)
bob.describe()
bob.introduce()
mary.describe()
mary.introduce()

Résultat de l'exécution:

name: Bob; age: 24; height: 170
My name is Bob, and height is 170, and age is 24.
name: Mary; age: 10; height: 160
My name is Mary, and height is 160, and age is 10.

Il existe deux façons de mettre en œuvre le processus ci-dessus d'une manière orientée processus. La première consiste à le faire passer comme un argument tel quel.

def description(name, age, height):
    print("Description: name is {}, age is {}, height is {}".format(name, age, height))


def introduction(name, age, height):
    print("My name is {}, and height is {}, and age is {}. ".format(name, height, age))


description("Bob", 24, 170)
description("Mary", 20, 160)
introduction("Bob", 24, 170)
introduction("Mary", 20, 160)

La méthode ci-dessus vous oblige à passer le même argument à chaque fois, ce qui est très ennuyeux quand il y a beaucoup d'arguments. L'autre est que vous n'avez pas à passer un argument à chaque fois.

bob = dict(name='Bob', age=24, height=170)
mary = dict(name='Mary', age=20, height=160)


def introduction(**kwargs):
    print("My name is {name}, and height is {age}, and age is {height}. ".format(**kwargs))


def description(**kwargs):
    print("Description: name is {name}, age is {age}, height is {height}".format(**kwargs))


introduction(**bob)
description(**bob)
introduction(**mary)
description(**mary)

Dans cette méthode, l'argument est stocké dans un dictionnaire, et le dictionnaire est décompressé et passé en argument. Cependant, si les trois clés «nom, âge, hauteur» n'existent pas dans le dictionnaire, une erreur se produira.

De cette manière, par rapport à une approche orientée processus, orientée objet encapsule le traitement et les données ensemble, de sorte que la logique du code a tendance à être plus propre.

2-3. Fonctionnement dynamique des objets

La réalisation d'une séquence dynamique d'actions d'un objet n'est pas adaptée à l'orientation processus.

class Individual:
    def __init__(self, energy=10):
        self.energy = energy

    def eat_fruit(self):
        self.energy += 1
        return self

    def eat_meat(self):
        self.energy += 2
        return self

    def run(self):
        self.energy -= 3
        return self


anyone = Individual()
print("energy: {}".format(anyone.energy))
anyone.eat_meat()
print("energy after eat_meat: {}".format(anyone.energy))
anyone.eat_fruit()
print("energy after eat_fruit: {}".format(anyone.energy))
anyone.run()
print("energy after run: {}".format(anyone.energy))
anyone.eat_meat().run()
print("energy after eat_meat and run: {}".format(anyone.energy))

Résultat de l'exécution:

energy: 10
energy after eat_meat: 12
energy after eat_fruit: 13
energy after run: 10
energy after eat_meat and run: 9

La «classe» de «l'individu» ci-dessus a un paramètre d'état interne appelé «énergie» et trois méthodes pour «manger des fruits», «manger de la viande» et «courir». Ensuite, nous définissons deux classes plus subdivisées, "garçon" et "fille".

class Boy(Individual):
    def daily_activity(self):
        self.eat_meat().eat_meat().run().eat_meat().eat_fruit().run().eat_meat()
        print("boy's daily energy: {}".format(self.energy))


class Girl(Individual):
    def daily_activity(self):
        self.eat_meat().eat_fruit()
        print("girl's daily energy: {}".format(self.energy))


bob = Boy()
bob.daily_activity()
mary = Girl()
mary.daily_activity()

Résultat de l'exécution:

boy's daily energy: 13
girl's daily energy: 13

Si le processus ci-dessus est implémenté de manière orientée processus, il est nécessaire de définir une variable dédiée appelée «énergie» et une fonction pour traiter chaque «énergie» pour chaque objet, ce qui est inévitablement redondant.

De plus, la structure de «sujet, verbe, objet» est relativement facile à comprendre. Dans l'exemple ci-dessus, vous pouvez comprendre que la séquence d'actions, d'abord ʻeat_meat () ʻet ensuiterun (), continue indéfiniment. S'il est réalisé de manière orientée processus, ce sera une longue phrase comme boy_energy = eat_meat (boy_energy); boy_energy = run (boy_energy); ... ou une structure hiérarchique comme ʻeat_meat (run (boy_energy)) `. Ce sera difficile à comprendre.

3. Concept orienté objet

J'ai brièvement présenté les caractéristiques de l'orientation objet. De là, nous entrerons dans un contenu un peu plus avancé. Il existe différents concepts dans l'orientation objet, et je vais les expliquer.

3-1. Classe

[Classe](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%A9%E3%82%B9_(%E3%82%B3%E3%83%B3%E3%] 83% 94% E3% 83% A5% E3% 83% BC% E3% 82% BF)) est un dessin de conception d'un objet avec les mêmes attributs (variables, données) et traitement (méthodes, fonctions). Les classes définissent des attributs et des actions communs pour les objets générés à partir d'elles. Dans les langages orientés processus, les variables sont classées par type, tandis que dans les langages orientés objet, les variables sont classées par classe. Et le type du langage orienté objet lui-même est également une classe.

À propos, Python 2 a une ancienne classe et une nouvelle classe, chacune ressemblant à ceci:

class oldStyleClass: # inherits from 'type'
    pass

class newStyleClass(object): # explicitly inherits from 'object'
    pass

Avec Python 3, toutes les classes utilisent par défaut de nouvelles classes, vous n'avez donc plus à hériter explicitement de ʻobject`.

3-2. Instance

[Instance](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82 % B9) est simplement [Object](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82 % AF% E3% 83% 88_ (% E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% Parfois appelé B0)), le [Constructeur] de la classe (https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%82%B9%E3%83 % 88% E3% 83% A9% E3% 82% AF% E3% 82% BF) et Initializer pour spécifier l'attribut Fait référence à une entité à laquelle une valeur a été attribuée.

3-3. Instanciation

[Instanciation](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3% 82% B9% E5% 8C% 96) fait référence à l'acte de création d'une instance à partir d'une classe qui est un dessin de conception.

3-4. Variables d'instance

[Variables d'instance](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3% 82% B9% E5% A4% 89% E6% 95% B0) fait référence aux variables affectées à chaque instance.

3-5. Variables de classe

Variables de classe Une variable partagée par une classe et ses instances.

3-6. Méthode

[Méthode](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89_(%E8%A8%88%E7%) AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6))) fait référence à une fonction qui appartient à une classe ou une instance.

3-7. Méthode statique

[Méthode statique](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89_(%E8%A8%88%) E7% AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6) #% E3% 82% A4% E3% 83% B3% E3% 82% B9% E3% 82% BF% E3% 83% B3% E3% 82% B9% E3% 83% A1% E3% 82% BD% E3% 83% 83% E3% 83% 89% E3% 81% A8% E3% 82% AF% E3% 83% A9% E3% 82% B9% E3% 83% A1% E3% 82% BD% E3% 83% 83% E3% 83% 89) fait référence à une méthode qui peut être appelée sans instanciation.

3-8. Méthode de classe

[Méthode de classe](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89_(%E8%A8%88%E7) % AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6) #% E3% 82% A4% E3% 83% B3% E3% 82% B9% E3% 82% BF% E3 % 83% B3% E3% 82% B9% E3% 83% A1% E3% 82% BD% E3% 83% 83% E3% 83% 89% E3% 81% A8% E3% 82% AF% E3% 83 % A9% E3% 82% B9% E3% 83% A1% E3% 82% BD% E3% 83% 83% E3% 83% 89) fait référence à une méthode qui opère sur une classe en tant qu'objet.

3-9. Membres

Member est l '[Namespace](https: :) de la classe ou de l'instance. //ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93) est un élément à stocker. Les espaces de noms incluent généralement des variables membres (variables de classe ou d'instance) et des fonctions membres (diverses méthodes).

3-10. Remplacer

[Remplacer](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82 % A4% E3% 83% 89) est la Classe enfant % A9% E3% 82% B9_ (% E8% A8% 88% E7% AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6)) (sous-classe / classe dérivée) est [parent Classe](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%BC%E3%83%91%E3%83%BC%E3%82%AF%E3%83% Hérité de A9% E3% 82% B9_ (% E8% A8% 88% E7% AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6)) (super classe / classe de base) Fait référence à l'acte d'écraser une méthode.

3-11. Encapsulation

Encapsulation est Il fait référence à l'action de regrouper des données et de les traiter dans un objet pour créer une frontière.

3-12. Héritage

[Héritage](https://ja.wikipedia.org/wiki/%E7%B6%99%E6%89%BF_(%E3%83%97%E3%83%AD%E3%82%B0%E3%] 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0))) fait référence à la conception d'une classe enfant qui hérite de la structure d'une classe existante. A une relation de is-a ou has-a C'est une architecture qui vous le permet.

3-13. Polymorphisme

[Polymorphisme](https://ja.wikipedia.org/wiki/%E3%83%9D%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%95%E3%82 % A3% E3% 82% BA% E3% 83% A0) (polymorphisme) se réfère principalement à la variété des classes enfants obtenues par les remplacements. Voici des exemples.

class Animal:
    def run(self):
        print('Animal is running...')


class Dog(Animal):
    def run(self):
        print('Dog is running...')


class Cat(Animal):
    def run(self):
        print('Cat is running...')


def run_twice(animal):
    animal.run()
    animal.run()

run_twice(Animal())
run_twice(Dog())
run_twice(Cat())

Résultat de l'exécution:

Animal is running...
Animal is running...
Dog is running...
Dog is running...
Cat is running...
Cat is running...

En d'autres termes, le processus de saisie d'une certaine classe peut fonctionner normalement sans aucune modification de la classe enfant ["Principe de remplacement de Riskov"](https://ja.wikipedia.org/wiki/ % E3% 83% AA% E3% 82% B9% E3% 82% B3% E3% 83% 95% E3% 81% AE% E7% BD% AE% E6% 8F% 9B% E5% 8E% 9F% E5 Par% 89% 87).

3-14. Surcharge de l'opérateur

La surcharge d'opérateur (https://en.wikipedia.org/wiki/Operator_overloading) fait référence à l'acte de définir par l'utilisateur la fonctionnalité d'un opérateur. En Python, toutes les classes sont des classes enfants de la classe ʻobject`, et chaque surcharge d'opérateur est réalisée par une méthode spéciale, donc c'est une sorte de polymorphisme dans la nature. La méthode spéciale pour la surcharge des opérateurs est la suivante.

class MyNum:
    def __init__(self,x):
        self.__x = x

    def __lt__(self, other):
        print("__lt__")
        return self.__x < other

    def __le__(self, other):
        print("__le__")
        return self.__x <= other

    def __eq__(self, other):
        print("__eq__")
        return self.__x == other

    def __ne__(self, other):
        print("__ne__")
        return self.__x != other

    def __gt__(self, other):
        print("__gt__")
        return self.__x > other

    def __ge__(self, other):
        print("__ge__")
        return self.__x >= other

x = MyNum(100)
x < 10
x <= 10
x == 10
x != 10
x > 10
x >= 10

Résultat de l'exécution:

__lt__
__le__
__eq__
__ne__
__gt__
__ge__

Ce qui précède est l'ajout du traitement «d'impression» au traitement arithmétique. Python a une bibliothèque de calculs numériques appelée «Numpy». Et la raison pour laquelle vous pouvez calculer le produit Adamal d'une matrice sous la forme «a * b» est que Python prend en charge la surcharge d'opérateurs.

3-15. Abstraction

[Résumé](https://ja.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E5%8C%96_(%E8%A8%88%E7%AE%97%E6] % A9% 9F% E7% A7% 91% E5% AD% A6))) fait référence à l'encapsulation et à la formation d'un concept en regroupant uniquement les données et processus fortement liés en objets. Par exemple, vous pouvez concevoir un animal comme une classe appelée «Animal», faire de l'état de l'animal une variable et faire du comportement de l'animal une méthode.

3-16. Dactylographie de canard et patch de singe

Ces deux concepts sont dérivés de la communauté Ruby et représentent la nature des langages dynamiques.

[Monkey Patch](https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%B3%E3%82%AD%E3%83%BC%E3%83%91%E3% 83% 83% E3% 83% 81) est un moyen d'étendre ou de modifier votre code au moment de l'exécution. Dans la programmation orientée objet de Python, il est utilisé comme terme pour changer dynamiquement les classes.

[Saisie de canard](https://ja.wikipedia.org/wiki/%E3%83%80%E3%83%83%E3%82%AF%E3%83%BB%E3%82%BF%E3 % 82% A4% E3% 83% 94% E3% 83% B3% E3% 82% B0) est la nature d'un langage de programmation orienté objet à typage dynamique, par exemple, pour exécuter une fonction telle que run_twice (animal). Je vais. Les langages statiquement typés doivent évaluer le type de l'argument et s'exécuter uniquement avec la classe ʻAnimalou ses dérivés. Cependant, les langages typés dynamiquement peuvent fonctionner normalement s'ils ont une méthode appeléerun ()` sans évaluation de type. "S'il marche comme un canard et sonne comme un canard, ce doit être un canard."

3-17. SOLID SOLID est un acronyme pour mémoriser les cinq principes de la conception logicielle dans le domaine de la programmation orientée objet. Les cinq principes sont Principe de responsabilité unique et [Principe d'ouverture et de fermeture](https://ja.wikipedia.org/ wiki /% E9% 96% 8B% E6% 94% BE /% E9% 96% 89% E9% 8E% 96% E5% 8E% 9F% E5% 89% 87), [Principe de remplacement de Riskov](https :: //ja.wikipedia.org/wiki/%E3%83%AA%E3%82%B9%E3%82%B3%E3%83%95%E3%81%AE%E7%BD%AE%E6%8F % 9B% E5% 8E% 9F% E5% 89% 87), [Principle of Interface Separation](https://ja.wikipedia.org/w/index.php?title=%E3%82%A4%E3% 83% B3% E3% 82% BF% E3% 83% BC% E3% 83% 95% E3% 82% A7% E3% 82% A4% E3% 82% B9% E5% 88% 86% E9% 9B% A2% E3% 81% AE% E5% 8E% 9F% E5% 89% 87 & action = edit & redlink = 1) et Principle of Dependency Reversal 9D% E5% AD% 98% E6% 80% A7% E9% 80% 86% E8% BB% A2% E3% 81% AE% E5% 8E% 9F% E5% 89% 87).

3-17-1. Principe de la responsabilité unique

Le principe de la responsabilité unique est le principe qu'une classe ne devrait avoir qu'une seule responsabilité. "Une responsabilité" est un peu vague, donc en pratique ce n'est pas une responsabilité unique lorsqu'il y a deux ou plusieurs motifs pour changer de classe. Par exemple, en supposant qu'il existe une classe «Rectangle» qui représente un rectangle, elle est utilisée dans deux modules, la fonction de dessin GUI et le calcul géométrique du rectangle. La classe «Rectangle» viole ici le principe de responsabilité unique.

3-17-2. Principe d'ouverture et de fermeture

Le principe d'ouverture et de clôture est que les nouvelles exigences devraient être étendues autant que possible plutôt que de modifier le code. En pratique, des abstractions sont souvent utilisées pour atteindre ce principe. Python décorateur est ouvert et fermé. Avec des fonctionnalités conformes, vous pouvez implémenter de nouvelles fonctionnalités sans modifier les méthodes, fonctions ou classes existantes.

3-17-3. Principe de remplacement de Riskov

Le principe de remplacement de Riskov est que là où la classe parente est utilisée, la classe enfant devrait également pouvoir la remplacer. En pratique, ce principe est réalisé en utilisant l'héritage et le polymorphisme. Par exemple, en tant que classe enfant de la classe Rectangle qui représente un rectangle, il existe une classeSquare qui provoque une erreur si la hauteur et la largeur ne correspondent pas. Et si une fonction ou une méthode prend la classe Rectangle comme entrée et donne une différence entre la hauteur et la largeur en interne, elle ne peut pas être remplacée par la classe Square, qui viole le principe de remplacement de Riskov.

3-17-4. Principe de séparation des interfaces

Le principe de l'isolation d'interface est que les clients ne doivent pas avoir de dépendances sur des méthodes inutilisées. C'est difficile à comprendre avec des mots, mais voyez l'exemple ci-dessous. Screen Shot 2020-10-17 at 23.31.01.png (Source: Principes, modèles et pratiques Agile en C #)

Cette figure montre la relation entre plusieurs classes. La classe Door a des méthodes associées aux portes telles que lock (), ʻun_lock () et ʻis_open (). Cette fois, si la porte est ouverte pendant un certain laps de temps, cela créera une «porte chronométrée» qui se fermera automatiquement. Ici, la fonction de mesure du temps est donnée à une classe appelée TimerClient, et Door hérite directement de TimerClient et acquiert cette fonction. Ensuite, TimedDoor, qui hérite deDoor, peut également acquérir la fonction de mesure du temps. Cependant, «Door» est une porte ordinaire et ne nécessite pas de fonction de mesure du temps, elle viole donc le principe de séparation d'interface.

La solution est de créer une méthode d'adaptateur ou une variable qui se connecte au TimerClient à l'intérieur du TimedDoor comme indiqué ci-dessous et hérite de Mixin. Il existe deux types de méthodes. Screen Shot 2020-10-17 at 23.46.32.png Screen Shot 2020-10-17 at 23.46.55.png (Source: Principes, modèles et pratiques Agile en C #)

3-17-5. Principe du renversement de la dépendance

Le principe de l'inversion de dépendance implique deux règles.

Ce principe concerne le découplage entre les modules. Voici des exemples. Screen Shot 2020-10-17 at 23.57.12.png (Source: Principes, modèles et pratiques Agile en C #)

Le module supérieur PolicyLayer dépend ici du module inférieurMechanismLayer, et le module inférieur MechanismLayer dépend du module de détail ʻUtilityLayer`. C'est un modèle qui viole le principe de l'inversion des dépendances.

En guise de solution, vous pouvez concevoir comme suit. Screen Shot 2020-10-18 at 0.00.17.png (Source: Principes, modèles et pratiques Agile en C #)

Maintenant, «PolicyLayer» dépend de l'interface abstraite «PolicyServiceInterface» au lieu du sous-module. Pour «PolicyServiceInterface», «MechanismLayer» implémente chaque interface.

Avec l'intervention de «PolicyServiceInterface», «PolicyLayer» et «MechanismLayer» sont compatibles sans dépendre l'un de l'autre. La même chose est vraie pour MechanismServiceInterface. Il est peu probable que l'interface abstraite change et son intervention découple chaque module.

3-18. GRASP GRASP est une politique de conception pour un système orienté objet appelé "General Responsibility Assignment Software Pattern". GRASP illustre neuf modèles: expert en information, générateur, contrôleur, faiblement couplé, hautement cohésif, polymorphe, purement artificiel, indirect et protection contre les variations.

3-18-1. Expert en information

Le modèle Information Expert indique que si vous avez une classe qui contient toutes les informations dont vous avez besoin pour accomplir une tâche, vous devez la laisser à cette classe. Par exemple, supposons que le système de site EC comporte deux classes, le panier «ShopCar» et le produit «SKU». Ensuite, nous implémenterons une fonction qui "n'autorise pas les produits en double dans le panier". Puisque SKUID est dans la classe SKU comme information pour juger si le même produit est le même, cette fonction n'est pas la classe ShopCar mais contient toutes les informations nécessaires selon le modèle de l'expert en information`SKU ʻIl devrait être implémenté dans la classe. Screen Shot 2020-10-18 at 21.30.32.png

3-18-2. Générateur

En supposant qu'il existe des classes A et B dans le modèle de générateur, B doit être amené à créer A lorsqu'une ou plusieurs des conditions suivantes sont remplies. Dans ce cas, B est le créateur de A.

Par exemple, dans le système du site EC, s'il existe une classe appelée order ʻOrder qui gère le produit SKU, la création de SKU doit être faite dans ʻOrder. Screen Shot 2020-10-18 at 21.32.06.png

3-18-3. Contrôleur

Le modèle de contrôleur indique que les événements système doivent être contrôlés par un objet appelé contrôleur. Ce contrôleur doit être une classe, un système ou un sous-système qui n'interagit pas avec l'interface utilisateur ou les interfaces.

Par exemple, "C" dans l'architecture "MVC" (https://ja.wikipedia.org/wiki/Model_View_Controller) utilisé pour Ruby on Rails Est une abréviation de contrôleur.

3-18-4. Accouplement desserré

La cohésion est une mesure de la force des dépendances entre chaque composant du système. Le modèle de couplage lâche indique que le système doit être conçu pour affaiblir les dépendances entre chaque composant. Afin d'affaiblir la dépendance, minimisez ʻimport` etc., resserrez l'autorité d'accès des membres de la classe, rendez la classe immuable % A4% E3% 83% 9F% E3% 83% A5% E3% 83% BC% E3% 82% BF% E3% 83% 96% E3% 83% AB) Il existe une méthode comme en faire un objet.

En prenant le système de site EC comme exemple, par exemple, lors de l'ajout d'une fonction pour calculer le prix total d'un produit, créer une nouvelle classe, etc., ʻimport`` SKU, et créer une méthode pour agréger le montant. Plutôt, l'ajouter à ʻOrder, qui a déjà une dépendance sur SKU, vous empêchera de créer des dépendances inutiles. Screen Shot 2020-10-18 at 21.33.12.png

3-18-5. Cohésion élevée

La cohésion est une mesure de la force de la relation entre les responsabilités (fonctions) d'un objet (module). Les modèles hautement cohérents devraient concentrer leurs responsabilités sur l'objet correctement.

En prenant le système de site EC comme exemple, créez la classe DAO des données de commande, et créez la méthodeSaveOrder () pour sauvegarder les données. Implémenter. Si vous souhaitez réaliser la fonction à enregistrer dans Excel et la fonction à enregistrer dans DB, au lieu de les implémenter collectivement dans ʻOrderDAO, implémentez différentes classes, héritez de ʻOrderDAO, et [Méthode virtuelle](https :: Il est préférable de remplacer SaveOrder () de //en.wikipedia.org/wiki/Virtual_function) (qui est implémentée comme une méthode abstraite en Python et sera appelée ci-après une méthode abstraite), mais ce sera plus cohérent. Screen Shot 2020-10-18 at 21.33.42.png

3-18-6. Polymorphisme

Le polymorphisme est un concept introduit dans [3-13. Polymorphisme](# 3-13-Polymorphisme), qui est modelé ici comme une règle de conception de système. Le modèle polymorphe implémente les parties variables de la classe en tant que méthodes abstraites, etc., et Polymorphisme % E3% 83% A2% E3% 83% BC% E3% 83% 95% E3% 82% A3% E3% 82% BA% E3% 83% A0), et sa réalisation concrète est une classe enfant Doit être implémenté dans.

Par exemple, créez une classe abstraite appelée «Shape» et implémentez une méthode abstraite de dessin appelée «Draw ()». Hériter de «Shape», créer respectivement des rectangles «Rectangle» et des cercles «Round», remplacer «Draw ()» en interne et réaliser chaque fonction de dessin est un dessin qui suit un motif polymorphe. Devenir. De cette façon, la prochaine fois que vous voudrez ajouter un diamant "Diamant", vous pourrez le créer de la même manière sans changer la structure du système. Screen Shot 2020-10-18 at 21.44.21.png

3-18-7. Pur artificiel

Lors de la conception d'un système, une cohésion élevée et un couplage lâche sont incohérents. Une grande cohésion subdivise les classes et concentre leurs responsabilités les unes sur les autres, mais si les classes ne coopèrent pas les unes avec les autres, elles ne fonctionneront pas correctement et deviendront inévitablement plus cohérentes.

Les artefacts purs créent des classes artificielles, ou classes abstraites, qui équilibrent la cohésion et la connectivité. Par exemple, dans le cas de la fonction de dessin d'une figure, nous ajouterons cette fois une fonction prenant en charge à la fois Windows et Linux. Puisque les appels système et la structure de chaque OS sont différents, la fonction de dessin Draw () doit également être implémentée d'une manière différente. Ici, en ajoutant la classe de base abstraite ʻAbstractShape`, le système peut être réalisé sans réduire la cohésion et la cohésion. Screen Shot 2020-10-18 at 22.07.19.png

3-18-8. Indirect

Le modèle indirect est une méthode de conception qui favorise une réduction de la connectivité entre les classes en fournissant un objet intermédiaire entre les deux classes. MVC Dans l'architecture, c'est un modèle indirect de mettre Contoroller entre les deux sans laisserModel interagir directement avec View. C'est un dessin. La classe abstraite d'interface au milieu introduite dans [3-17-4. Principe de séparation d'interface](# 3-17-4-Principe de séparation d'interface) est conçue avec la même idée.

3-18-9. Protection contre les fluctuations

Le schéma de protection contre les fluctuations est similaire au [3-17-2. Principe d'ouverture / fermeture](# 3-17-2-Principe d'ouverture / fermeture). Encapsulez les pièces instables avec une interface unifiée pour les protéger des fluctuations. Et lorsque des changements se produisent, nous ajoutons plutôt que nous changeons l'interface. Le but est de pouvoir étendre la fonctionnalité sans changer l'ancien code. Python [Decorator](https://ja.wikipedia.org/wiki/Decorator_%E3%83%91%E3%82%BF%E3] donné à titre d'exemple dans [3-17-2. Principe d'ouverture et de fermeture] En plus de ORM E3% 82% A7% E3% 82% AF% E3% 83% 88% E9% 96% A2% E4% BF% 82% E3% 83% 9E% E3% 83% 83% E3% 83% 94% E3% 83% B3% E3% 82% B0) est un modèle de protection contre les variations typique, la modification du DB n'affecte pas le côté client

3-19. Modèle de conception

[Modèle de conception](https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3% 82% BF% E3% 83% BC% E3% 83% B3_ (% E3% 82% BD% E3% 83% 95% E3% 83% 88% E3% 82% A6% E3% 82% A7% E3% 82 % A2)) est un savoir-faire de conception en programmation orientée objet. Contrairement aux principes de conception comme [SOLID](# 3-17-solid) et [GRASP](# 3-18-grasp) mentionnés ci-dessus, le modèle de conception est une expérience conçue par d'anciens développeurs. C'est comme une règle.

4. Bases de Python orientées objet

J'ai expliqué le concept d'orientation objet. Créons maintenant quatre classes pour voir la structure de base de la programmation orientée objet de Python.

4-1. Variables et méthodes de classe

Les classes Python ont des variables et des méthodes. Et il existe différents types de chacun.

Dans le code ci-dessous, les définitions de diverses variables et méthodes sont expliquées dans les commentaires.

from types import MethodType


class Animal:
    #C'est l'endroit pour définir les variables de classe
    the_name = "animal"  #Variable de classe

    def __init__(self, name, age):  #Initialiseur
        self.name = name  #Variable d'instance
        self.age = age

    #C'est ici que vous définissez la méthode
    def sleep(self):  #Méthode d'instance
        print("{} is sleeping".format(self.name))

    def eat(self, food):  #Méthode d'instance avec arguments
        print("{} is eating {}".format(self.name, food))

    @classmethod
    def speak(cls, adjective):  #Méthode de classe
        print("I am a {} {}".format(adjective, cls.the_name))

    @staticmethod
    def happening(person, do):  #Méthode statique
        print("{} is {}ing".format(person, do))


def drink_water(self):
    print("{} is drinking water".format(self.name))

Vérification:

adam = Animal(name="Adam", age=2)  #Instanciation
print('adam.the_name: {}'.format(adam.the_name))  #Appeler des variables de classe à partir d'une instance
#Résultat de l'exécution: adam.the_name: animal
print('Animal.the_name: {}'.format(Animal.the_name))  #Appeler des variables de classe depuis la classe
#Résultat de l'exécution: adam.name: Adam
print('adam.name: {}'.format(adam.name))  #Variables d'instance d'appel
#Résultat d'exécution: Animal.the_name: animal
adam.sleep()  #Appeler une méthode d'instance
#Résultat de l'exécution: Adam dort
adam.eat("meat")  #Appeler une méthode d'instance avec des arguments
#Résultat de l'exécution: Adam mange de la viande
adam.speak("happy")  #Appeler une méthode de classe à partir d'une instance
#Résultat de l'exécution: je suis un animal heureux
Animal.speak("sad")  #Appeler une méthode de classe à partir d'une classe
#Résultat de l'exécution: je suis un animal triste
adam.happening("Tim", "play")  #Appeler une méthode statique à partir d'une instance
#Résultat de l'exécution: Tim joue
Animal.happening("Mary", "watch")  #Appeler des méthodes statiques depuis la classe
#Résultat de l'exécution: Mary regarde
Animal.the_name = "Animal"  #Modifier les variables de classe
print('adam.the_name: {}'.format(adam.the_name))
#Résultat de l'exécution: adam.the_name: Animal
adam.the_name = "animal"  #Correction de l'instance
print('Animal.the_name: {}'.format(Animal.the_name))
#Résultat d'exécution: Animal.the_name: Animal
adam.age = 3  #Modifier les variables d'instance

#Méthode de baguage (patch de singe)
adam.drink_water = MethodType(drink_water, adam)  #Banding à une instance
adam.drink_water()
#Résultat de l'exécution: Adam boit de l'eau
print(adam.drink_water)
#Résultat de l'exécution:<bound method drink_water of <__main__.Animal object at 0x7ffd68064310>>
try:
    Animal.drink_water
except AttributeError as e:
    print(e)
#Résultat de l'exécution: objet de type'Animal' has no attribute 'drink_water'
Animal.drink_water = MethodType(drink_water, Animal)  #Baguage en classe
adam.drink_water()
#Résultat de l'exécution: Adam boit de l'eau
Animal.drink_water = drink_water  #Méthodes de baguage avec affectation directe
adam.drink_water()
#Résultat de l'exécution: Adam boit de l'eau

4-2. Propriétés

Créez une classe Dog qui hérite de ʻAnimal et regardez la propriété` et ses décorateurs associés. Ces décorateurs convertissent les méthodes en propriétés (variables) et présentent les deux avantages suivants.

En plus du décorateur, il existe également un moyen de réaliser le traitement ci-dessus avec la fonction «propriété».

from functools import cached_property


class Dog(Animal):  #Héritage de classe
    def eating(self):
        print("{} is eating".format(self.name))

    @property
    def running(self):
        if self.age >= 3 and self.age < 130:
            print("{} is running".format(self.name))
        elif self.age > 0 and self.age < 3:
            print("{} can't run".format(self.name))
        else:
            print("please input true age")

    @property  #Obtenir des variables privées
    def country(self):
        return self._country

    @country.setter  #Nom de la méthode.setter
    def country(self, value):  #Attribuer une valeur à une variable privée
        self._country = value

    @country.deleter  #Nom de la méthode.deleter
    def country(self):  #Supprimer la valeur d'une variable privée
        del self._country
        print("The attr country is deleted")

    #Atteindre la même fonction que le décorateur ci-dessus avec la fonction de propriété
    def get_city(self):
        return self._city

    def set_city(self, value):
        self._city = value

    def del_city(self, value):
        del self._city

    city = property(get_city, set_city, del_city, "I'm the 'city' property.")

    @cached_property  #Propriété en cache
    def official_name(self):
        return 'Mr.{} - the Dog'.format(self.name)

Vérification:

david = Dog("David", 2)
david.eating()
#Résultat de l'exécution: David mange
david.running  # ()Appeler sans
#Résultat de l'exécution: David peut't run
dean = Dog("Dean", 4)
dean.running
#Résultat de l'exécution: Dean est en cours d'exécution

#Méthode décoratrice
david.country = "America"
print(david.country)
#Résultat d'exécution: Amérique
del david.country
#Résultat de l'exécution: le pays attr est supprimé

#Méthode par fonction de propriété
david.city = "NewYork"
print(david.city)
#Résultat d'exécution: NewYork

#Propriété en cache
print(david.official_name)
#Résultat d'exécution: Mr.David - the Dog

4-3. Héritage / remplacement des variables et méthodes privées

Définissons la classe Cat et sa classe enfant BlackCat et regardons l'héritage / remplacement des variables et méthodes privées.

class Cat(Animal):
    def __init__(self, weight):  #De la classe parente__init__Passer outre
        self.__weight = weight
        self._weight = weight + 1
        self.weight = self._weight + 1

    def get_weight(self):
        print("My _weight is {}kg".format(self._weight))

    def get_real_weight(self):
        print("Actually my __weight is {}kg".format(self.__weight))


class BlackCat(Cat):
    def get_weight(self):  #Remplacer les méthodes de classe parent
        print("My weight is {}kg".format(self.weight))

    def get_real_weight(self):
        print("Actually my _weight is {}kg".format(self._weight))

    def get_actual_weight(self):
        print("My __weight is exactly {}kg".format(self.__weight))

Vérification:

cole = Cat(5)
print("Cole's weight: {}kg".format(cole.weight))
#Résultat de l'exécution: Cole's weight: 7kg

# _x est une variable privée qui n'est pas recommandée pour un usage externe, et son utilisation elle-même n'est pas restreinte.
print("Cole's _weight: {}kg".format(cole._weight))
#Résultat de l'exécution: Cole's _weight: 6kg

# __x est une variable privée dont l'utilisation de l'extérieur est interdite et son utilisation est restreinte._<class>__Peut être appelé de force sous la forme de x
print("Cole's __weight: {}kg".format(cole._Cat__weight))
#Résultat de l'exécution: Cole's __weight: 5kg
cole.get_real_weight()  #De l'intérieur avec méthode__x est disponible
#Résultat de l'exécution: en fait mon__weight is 5kg

cain = BlackCat(5)
cain.get_weight()
#Résultat de l'exécution: mon poids est de 7 kg

# _x n'est pas restreint, il peut donc être appelé à partir de classes enfants
cain.get_real_weight()
#Résultat de l'exécution: en fait mon_weight is 6kg

#De la variable privée de la classe parente__x ne peut pas être utilisé de manière simple depuis l'intérieur d'une classe enfant
try:
    cain.get_actual_weight()
except AttributeError as e:
    print(e)
#Résultat de l'exécution:'Blackcat' object has no attribute '_Blackcat__weight'

4-4. Héritage des membres du groupe

Définissons «Tiger» et «WhiteTiger» et voyons comment utiliser «super». super est une fonction pour appeler les variables et les méthodes de la classe parent dans la classe enfant.

class Tiger(Animal):   
    def speak(self):
        return "I'm a tiger not Lulu's song"

    def eat(self):
        return "{} is eating".format(self.name)


class WhiteTiger(Tiger):
    def __init__(self, name, age, height):
        super().__init__(name, age)
        self.height = height

    def speak(self):
        return super().speak().replace("tiger", 'white tiger')

    def eat(self):
        return super().eat()

Vérification:

tony = WhiteTiger("Tony", 10, 100)
print(tony.eat())
#Résultat de l'exécution: Tony mange
print(tony.speak())
#Résultat d'exécution: I'm a white tiger not Lulu's song

** 1. Redéfinissez les variables de la classe parente. ** **

def __init__(self, name, age, height):
    self.name = name
    self.age = age
    self.height = height

** 2. Appelez explicitement la classe parente «init». Si vous changez le nom de la classe parente, vous devrez réparer tout ce qui est appelé. ** **

def __init__(self, name, age, height):
    Tiger.__init__(self, name, age)
    self.height = height

5. Évolution orientée objet Python

Nous avons vu les formes de base de la programmation orientée objet en Python. Dans la pratique, le contenu de [4. Bases orientées objet Python](# 4-Bases orientées objet python) est presque suffisant. Cependant, si vous souhaitez réaliser des fonctions avancées, créer vos propres modules ou créer un beau système qui suit le modèle de conception, vous devez connaître un contenu un peu plus avancé.

5-1. Méthode spéciale

Comme je l'ai mentionné un peu dans [3-14. Operator Overload](# 3-14-Operator Overload), les classes Python ont deux traits de soulignement avant et après, comme «init». Il existe de nombreuses méthodes et variables appelées "méthodes spéciales", "méthodes magiques" ou " __dunder__ (dunder: double tiret bas) " .. Ces méthodes et variables sont communes à certains ou à tous les objets et peuvent fournir diverses fonctions.

import collections
import copy
import math
import operator
import pickle
import sys
import asyncio


class Dunder:
    def __abs__(self):
        # abs(Dunder());Appelé lors du calcul des valeurs absolues
        return self.x

    def __add__(self, other):
        # Dunder() + 123;Appelé lors de l'ajout
        return self.x + other

    async def __aenter__(self):
        # `__aenter__`Quand`__aexit__`Doit être mis en œuvre ensemble
        # async with Dunder() as coro;La valeur de retour est limitée à l'objet attendu
        await asyncio.sleep(1)

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        # `__aenter__`Quand`__aexit__`Doit être mis en œuvre ensemble
        # async with Dunder() as coro;La valeur de retour est limitée à l'objet attendu
        await asyncio.sleep(1)

    def __aiter__(self):
        # `__aiter__`Est`__anext__`Doit être implémenté avec
        # async for _ in Dunder();La valeur de retour est limitée à un objet itérable asynchrone
        return self

    def __and__(self, other):
        # Dunder() & 123;Appelé lors de l'exécution d'une opération logique sur le produit
        return self.x & other

    async def __anext__(self):
        # `__aiter__`Est`__anext__`Doit être implémenté avec
        # async for _ in Dunder();Devrait provoquer StopAsyncIteration lorsque l'élément est parti
        #La valeur de retour est limitée à l'objet attendu
        val = await self.readline()
        if val == b'':
            raise StopAsyncIteration
        return val

    def __await__(self):
        # await Dunder();La valeur de retour est limitée à l'itérateur
        return self.z  # `__next__`Quand`__iter__`Classe qui met en œuvre

    def __call__(self, *args, **kwargs):
        # Dunder()(); callable(Dunder()) == True;Peut être appelé comme une fonction
        return self.x

    def __init__(self, **kwargs):
        # Dunder(y=2);Initialiseur
        self.x = 1
        self.y = kwargs.get('y')
        self.z = [1, 2, 3]

    def __bool__(self):
        # bool(Dunder()) == True;Appelé lors d'opérations booléennes
        return True

    def __bytes__(self):
        # bytes(Dunder());Chaîne d'octets
        return bytes('123', encoding='UTF-8')

    def __ceil__(self):
        # math.ceil(Dunder());Appelé lors de l'arrondissement
        return math.ceil(self.x)

    def __class_getitem__(cls, item):
        # Dunder[int] == "Dunder[int]";Cette méthode devient automatiquement une méthode de classe
        return f"{cls.__name__}[{item.__name__}]"

    def __complex__(self):
        # complex(Dunder());Nombre complexe
        return complex(self.x)

    def __contains__(self, item):
        # item not in Dunder(); item in Dunder()
        return True if item in self.z else False

    def __copy__(self):
        # copy.copy(Dunder());Appelé lors de la réalisation d'une copie superficielle
        return copy.copy(self.z)

    def __deepcopy__(self, memodict={}):
        # copy.deepcopy(Dunder());Appelé lors de la création d'une copie complète
        return copy.deepcopy(self.z)

    def __del__(self):
        # dunder = Dunder(); del dunder;
        #Appelé lors de la suppression d'un objet. Prend également en charge le garbage collection
        del self

    def __delattr__(self, item):
        # del self.params;Appelé lors de la suppression d'une variable d'instance
        del self.item

    def __delete__(self, instance):
        # class Owner: dunder = Dunder()
        # del Owner().medusa;Méthode du descripteur
        #Appelé lors de la suppression en tant qu'attribut de la classe propriétaire
        del self.x

    def __delitem__(self, key):
        # del Dunder()['some_key']
        self.__dict__.pop(key)

    def __dir__(self):
        # dir(Dunder());Renvoie un objet itérable qui contient tous les attributs de l'objet
        return super().__dir__()

    def __divmod__(self, other):
        # divmod(Dunder(), 123);Obtenez le quotient et le reste de la division en même temps
        return divmod(self.x, other)

    def __enter__(self):
        # with Dunder() as dunder: pass
        return self

    def __eq__(self, other):
        # Dunder() == 123;Appelé lors de l'exécution d'une opération d'équivalence
        return self.x == other

    def __exit__(self, exc_type, exc_val, exc_tb):
        # with Dunder() as dunder: pass;Les arguments sont respectivement TypeError, ValueError et Traceback.
        return True

    def __float__(self):
        # float(Dunder());Faites-en une fraction flottante
        return float(self.x)

    def __floor__(self):
        # math.floor(Dunder());Tronquer le point décimal
        return math.floor(self.x)

    def __floordiv__(self, other):
        # Dunder() // 123;Appelé lors de la troncature et de la division
        return self.x // other

    def __format__(self, format_spec):
        # '{:x}'format(Dunder()); format(Dunder(), 'x')
        if format_spec == 'x':
            return '{}'.format(self.x)
        return '{}'.format(self.y)

    def __fspath__(self):
        # os.fspath(Dunder()) == '/var/www/html/mysite';Renvoie le chemin du système de fichiers
        return '/var/www/html/mysite'

    def __ge__(self, other):
        # Dunder() >= 123
        return self.x >= other

    def __get__(self, instance, owner):
        # class Test: dunder = Dunder();Méthode du descripteur
        # `Test().dunder`Ou`Test.dunder`Appelé quand tu le fais
        return self.x

    def __getattr__(self, item):
        # Dunder().a;Appelé lors de l'accès à des membres non définis
        return f'object has no attribute "{item}"'

    def __getattribute__(self, item):
        # Dunder().a;Appelé lors de l'accès à tous les membres, indéfini ou défini
        # `return self.x`Veuillez noter que cela entraînera une boucle infinie.
        return super().__getattribute__(item)

    def __getitem__(self, item):
        # Dunder()[item]
        return self.__dict__.get(item)

    def __getnewargs__(self):
        # pickle.loads(pickle.dumps(Dunder()));Quand unPickle`__new__`Peut définir les arguments passés à la méthode
        # Python 3.Utilisé lors de l'utilisation du protocole pickle 2 ou 3 avant 6
        # Python 3.Lors de l'utilisation du protocole de cornichon 2 ou 3 après 6`__getnewargs_ex__`Est utilisé
        #Pas appelé directement`__reduce__`Composez la méthode
        return (2 * self.x, )

    def __getstate__(self):
        # pickle.dumps(Dunder());Vous pouvez obtenir l'état de l'objet pendant le traitement Pickle
        #Pas appelé directement`__reduce__`Composez la méthode
        return self.__dict__.copy()

    def __gt__(self, other):
        # Dunder() > 123
        return self.x > 123

    def __hash__(self):
        # hash(Dunder());Appelé lors du calcul de la valeur de hachage
        return hash(self.x)

    def __iadd__(self, other):
        # dunder = Dunder(); dunder += 123; in-Appelé lors de l'ajout d'un lieu
        self.x += other
        return self

    def __iand__(self, other):
        # dunder = Dunder(); dunder &= 123; in-Appelé lors de l'exécution d'une opération produit logique de place
        self.x &= other
        return self

    def __ifloordiv__(self, other):
        # dunder = Dunder(); dunder //= 123; in-Appelé lors de la troncature et de la division du lieu
        self.x //= other
        return self

    def __ilshift__(self, other):
        # dunder = Dunder(); dunder <<= 123; in-Appelé lors du calcul du décalage de bit à gauche de la place
        self.x <<= other
        return self

    def __imatmul__(self, other):
        # dunder = Dunder(); dunder @= 123; in-Appelé lors d'opérations binaires sur place
        #Dans numpy, il est implémenté en tant que produit scalaire
        self.x @= other  #Fonction non implémentée dans la bibliothèque standard
        return self

    def __imod__(self, other):
        # dunder = Dunder(); dunder %= 123; in-Appelé lors de l'exécution d'une opération de reste sur place
        self.x %= other
        return self

    def __imul__(self, other):
        # dunder = Dunder(); dunder *= 123; in-Appelé lors de la multiplication de la place
        self.x *= 123
        return self

    def __index__(self):
        # slice(Dunder(), Dunder() * 2); bin(Dunder()); hex(Dunder()); oct(Dunder())
        # operator.index(Dunder());La valeur de retour est limitée aux entiers`operator.index`Appelé depuis une fonction
        #Nécessite également un entier`slice`、`bin()`、`hex()`、`oct()`Appelle cette méthode
        return self.x

    def __init_subclass__(cls, **kwargs):
        # class Test(Dunder, **kwargs): ...;Appelé lors de l'héritage
        super().__init_subclass__()
        cls.x = kwargs.get('x', 1)

    def __instancecheck__(self, instance):
        # class MetaClass(type):
        #     def __new__(cls, name, bases, namespace):
        #         return super().__new__(cls, name, bases, namespace)
        #
        #     def __instancecheck__(self, other):
        #         return True
        #
        # class Test(metaclass=MetaClass): ...
        # isinstance(int, Test) == True
        #Cette méthode n'est appelée que si elle est définie par type de classe (métaclasse)
        #Aussi,`type(other) == self`Sera vrai directement et ne sera pas appelé
        pass

    def __int__(self):
        # int(Dunder());Appelé lors de la conversion en entier
        return int(self.x)

    def __invert__(self):
        # ~Dunder();Appelé lors du calcul de l'inversion de bit
        return ~self.x

    def __ior__(self, other):
        # dunder = Dunder(); dunder |= 123; in-Appelé lors de l'exécution d'une opération de somme logique de place
        self.x |= other
        return self

    def __ipow__(self, other):
        # dunder = Dunder(); dunder ** 2; in-Appelé lors du calcul de la puissance du lieu
        self.x ** other
        return self

    def __irshift__(self, other):
        # dunder = Dunder(); dunder >>= 2; in-Appelé lors du calcul du décalage vers la droite de la place
        self.x >>= other
        return self

    def __isub__(self, other):
        # dunder = Dunder(); dunder -= 2; in-Appelé lors de la soustraction d'un lieu
        return self

    def __iter__(self):
        # dunder = iter(Dunder()); next(dunder);Méthode de création d'un objet itérable
        # `__next__`Doit être implémenté avec
        self._i = 0
        return self.z[self._i]  # self.z est défini comme une liste

    def __itruediv__(self, other):
        # dunder = Dunder(); dunder /= 123; in-Appelé lors de la division de la place
        self.x /= other
        return self

    def __ixor__(self, other):
        # dunder = Dunder(); dunder ^= 123; in-Appelé lors de l'exécution d'une opération de somme logique exclusive de place
        self.x ^= other
        return self

    def __le__(self, other):
        # dunder = Dunder(); dunder <= 123
        return self.x <= other

    def __len__(self):
        # len(Dunder())
        return len(self.z)

    def __lshift__(self, other):
        # Dunder() << 123;Appelé lors du calcul d'un petit décalage vers la gauche
        return self.x << other

    def __lt__(self, other):
        # Dunder() < 123
        return self.x < other

    def __matmul__(self, other):
        # Dunder() @ 123;Appelé lors d'opérations binaires
        return self.x @ other  #Fonction non implémentée dans la bibliothèque standard

    def __missing__(self, key):
        # class Dict(dict):
        #     def __missing__(self, key):
        #         return f'__missing__({key})'
        # dunder = Dict({'key': 1})
        # print(dunder['unk_key'])
        #Méthode appelée lorsque la clé n'existe pas dans le dictionnaire
        pass

    def __mod__(self, other):
        # Dunder() % 123;Appelé lors de l'exécution d'une opération de reste
        return self.x % other

    def __mro_entries__(self, bases):
        #Appelé lorsqu'un objet non-classe est spécifié dans la liste parente de la définition de classe
        #Une méthode pour corriger les relations d'héritage dans la mise en œuvre des annotations de type
        # https://www.python.org/dev/peps/pep-0560/#mro-entries
        pass

    def __mul__(self, other):
        # Dunder() * 123;Appelé lors de la multiplication
        return self.x * ohter

    def __ne__(self, other):
        # Dunder() != 123;Appelé lors de l'exécution d'opérations inégales
        return self.x != other

    def __neg__(self):
        # -Dunder();Appelé lors du calcul du numéro
        return -self.x

    def __new__(cls, *args, **kwargs):
        # Dunder();constructeur
        # __init__Créer self (l'instance elle-même) utilisée dans et d'autres méthodes d'instance
        return super().__new__(cls)

    def __next__(self):
        # dunder = iter(Dunder()); next(dunder);Méthode de création d'un objet itérable
        # `__iter__`Doit être implémenté avec
        self._i += 1
        return self.z[self._i]

    def __or__(self, other):
        # Dunder() | 132;Appelé lors de l'exécution d'une opération de somme logique
        return self.x | other

    def __pos__(self):
        # +Dunder();Appelé lors de la conversion en un nombre positif
        return +self.x

    def __post_init__(self):
        #Une méthode pour la classe de données`__init__`Seulement si est défini`__init__`Appelé après
        pass

    def __pow__(self, power, modulo=None):
        # Dunder() ** 123;Appelé lors du calcul de la puissance
        if modulo:
            return self.x ** power % modulo
        else:
            return self.x ** power

    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        # class MetaClass(type):
        #     def __new__(cls, name, bases, namespace):
        #         return super().__new__(cls, name, bases, namespace)
        #
        #     @classmethod
        #     def __prepare__(cls, name, bases, **kwargs):
        #         return dict()
        #
        # class Test(metaclass=MetaClass): ...
        # namespace = MetaClass.__prepare__(name, bases, **kwargs)
        #Appelé avant d'évaluer le corps de la classe, il retourne un objet dictionnaire (espace de noms) qui stocke les membres de la classe
        #Ordinaire`types.prepare_class`Utiliser avec
        #Cette méthode n'est appelée que si elle est définie comme une méthode de classe dans la métaclasse
        return collections.OrderedDict()

    def __radd__(self, other):
        # 123 + Dunder();Appelé lorsque l'opérateur reçoit une addition réfléchie
        return other + self.x

    def __rand__(self, other):
        # 123 & Dunder();Appelé lorsque l'opérateur exécute une opération produit logique reflétée
        return other & self.x

    def __rdiv__(self, other):
        # 123 / Dunder();Appelé lorsque l'opérateur effectue une division réfléchie
        return other / self.x

    def __rdivmod__(self, other):
        # divmod(123, Dunder());Obtenez le quotient et le reste de la division reflétés par l'opérateur en même temps
        return divmod(other, self.x)

    def __reduce__(self):
        # pickle.dumps(Dunder())
        # `__getstate__`、`__setstate__`、`__getnewargs__`Peut être utilisé pour contrôler le comportement de Pickle
        #Autant que possible`__reduce__`Pour définir la méthode ci-dessus sans définir directement
        #Rétrocompatible`__reduce_ex__`Est utilisé préférentiellement quand est défini
        return super().__reduce__() # return super().__reduce_ex__(protocol)

    def __repr__(self):
        # repr(Dunder());Renvoie une chaîne contenant une représentation imprimable de l'objet
        return super().__repr__()

    def __reversed__(self):
        # reversed(Dunder());Renvoie un objet itérateur inversé
        new_instance = copy.deepcopy(self)
        new_instance.z = new_instance.z[::-1]
        return new_instance

    def __rfloordiv__(self, other):
        # 123 // Dunder();Appelé lorsque l'opérateur effectue une division de troncature réfléchie
        return other // self.x

    def __rlshift__(self, other):
        # 123 << Dunder();Appelé lorsque l'opérateur calcule le décalage gauche du bit réfléchi
        return '__rlshift__'

    def __rmatmul__(self, other):
        # 123 @ Dunder();Appelé lorsque l'opérateur effectue une opération binaire réfléchie
        return other @ self.x  #Fonction non implémentée dans la bibliothèque standard

    def __rmod__(self, other):
        # 123 % Dunder();Appelé lorsque l'opérateur exécute une opération de reste réfléchi
        return other % self.x

    def __rmul__(self, other):
        # 123 * Dunder();Appelé lorsque l'opérateur effectue une multiplication réfléchie
        return other * self.x

    def __ror__(self, other):
        # 123 | Dunder();Appelé lorsque l'opérateur exécute une opération de somme logique réfléchie
        return other | self.x

    def __round__(self, n=None):
       # round(Dunder());Arrondi
        return round(self.x)

    def __rpow__(self, other):
        # 123 ** Dunder();Appelé lorsque l'opérateur calcule la puissance réfléchie
        return other ** self.x

    def __rrshift__(self, other):
        # 123 >> Dunder();Appelé lorsque l'opérateur calcule le décalage à droite du bit réfléchi
        return other >> self.x

    def __rshift__(self, other):
        # Dunder() >> 123;Appelé lors du calcul d'un décalage vers la droite
        return self.x >> other

    def __rsub__(self, other):
        # 123 - Dunder();Appelé lorsque l'opérateur reçoit une soustraction réfléchie
        return other - self.x

    def __rtruediv__(self, other):
        # 123 / Dunder();Appelé lorsque l'opérateur effectue une division réfléchie
        return other / self.x

    def __rxor__(self, other):
        # 123 ^ Dunder();Appelé lorsque l'opérateur calcule la somme logique exclusive reflétée
        return other ^ self.x

    def __set__(self, instance, value):
        # class Test: dunder = Dunder();Méthode du descripteur
        # `Test().dunder=123`Ou`Test.dunder=123`Appelé quand tu le fais
        instance.x = value

    def __set_name__(self, owner, name):
        #Attribution de nom de variable de descripteur
        # class Test: pass;Appelé automatiquement lors de la création de la classe propriétaire,
        # dunder = Dunder();Doit être appelé explicitement lors du regroupement ultérieur
        # Test.dunder = dunder
        # dunder.__set_name__(Test, 'dunder')
        #Un descripteur appelé dunder est utilisé dans l'espace de dénomination de la classe Test.'dunder'Affecter à
        owner.__dict__[name] = self

    def __setattr__(self, key, value):
        # dunder = Dunder(); dunder.x = 123;Appelé lors de la définition des attributs
        self.__dict__[key] = value

    def __setitem__(self, key, value):
        # dunder = Dunder(); dunder['x'] = 123; ;Appelé lors de la définition des attributs avec des indices
        self.__dict__[key] = value

    def __setstate__(self, state):
        # pickle.loads(pickle.dumps(Dunder()))
        #Quand unPickle`__getstate__`Vous pouvez utiliser l'état de l'objet obtenu dans
        #Pas appelé directement`__reduce__`Composez la méthode
        self.__dict__.update(state)

    def __sizeof__(self):
        # sys.getsizeof(Dunder());Renvoie la taille de l'objet
        return super().__sizeof__()

    def __str__(self):
        # str(Dunder())
        # print(Dunder())
        #Définir une représentation sous forme de chaîne d'un objet
        return f'{self.x}'

    def __sub__(self, other):
        # Dunder() - 123;Appelé lors d'une soustraction
        return self.x - other

    def __subclasscheck__(self, subclass):
        # class MetaClass(type):
        #     def __new__(cls, name, bases, namespace):
        #         return super().__new__(cls, name, bases, namespace)
        #
        #     def __subclasscheck__(self, subclass):
        #         return True
        #
        # class Test(metaclass=MetaClass): ...
        # issubclass(int, Test) == True
        #Cette méthode n'est appelée que si elle est définie par type de classe (métaclasse)
        return NotImplemented

    @classmethod
    def __subclasshook__(cls, subclass):
        # class Test: x = 1; #Définir les variables de classe
        # issubclass(Test, Dunder) == True
        #Cette méthode doit être définie comme une méthode de classe de la classe de base virtuelle
        if cls is Dunder:
            return hasattr(subclass, 'x')

    def __truediv__(self, other):
        # Dunder() // 123;Appelé lors de l'exécution de la division par troncature
        return self.x // other

    def __trunc__(self):
        # math.trunc(Dunder());Appelé lors de l'arrondi
        return int(self.x)

    def __xor__(self, other):
        # Dunder() ^ 123;Appelé lors de l'exécution d'une opération de somme logique exclusive
        return self.x ^ other

Ce qui précède sont des méthodes spéciales courantes. Vous n'avez pas à vous souvenir de tout, et je pense que c'est juste d'avoir quelque chose comme ça. Il existe également des attributs et des méthodes plus spéciaux.

attribut sens
__dict__ De l'objet(Inscriptible)Dictionnaire ou autre objet de mappage utilisé pour stocker les attributs. Variables de fonction intégrées()Vous pouvez vous référer au dictionnaire avec.
__class__ La classe à laquelle appartient l'instance de classe.
__bases__ Un tapple de la classe de base (classe parente) de l'objet de classe.
__name__ Le nom de la classe, de la fonction, de la méthode, du descripteur ou de l'instance du générateur.
__qualname__ Noms qualifiés pour les classes, fonctions, méthodes, descripteurs et instances de générateur.
__mro__ Cet attribut est un tuple de classes prises en compte lors de l'exploration de la classe de base (classe parente) lors de la résolution d'une méthode.
    mro() Cette méthode peut être remplacée par la métaclasse pour personnaliser l'ordre de résolution de méthode pour cette instance. Cette méthode est appelée lorsque la classe est instanciée et que le résultat est__mro__Il est stocké au format.
__subclasses__ Chaque classe contient une référence faible à sa propre sous-classe directe. Cette méthode retourne une liste de ces références qui sont actives.
__doc__ Chaîne de documentation de fonction, Aucune s'il n'y a pas de documentation. Il n'est pas hérité par les sous-classes.
__module__ Le nom du module dans lequel la fonction est définie. S'il n'y a pas de nom de module, ce sera None.
__defaults__ Un taple contenant la valeur par défaut de l'argument avec la valeur par défaut, Aucun s'il n'y a pas d'argument avec la valeur par défaut
__code__ Un objet de code qui représente le corps de la fonction compilée.
__globals__ Dictionnaire contenant des variables globales de fonctions(Référence à)est---Ce dictionnaire détermine l'espace de noms global du module dans lequel la fonction est définie.
__closure__ Aucune ou variables libres individuelles de la fonction(Variables autres que les arguments)Il s'agit d'un tuple constitué d'un groupe de cellules qui contraignent la valeur à. L'objet de cellule est une cellule d'attribut_A du contenu. Cela peut être utilisé pour obtenir la valeur de la cellule en plus de définir la valeur de la cellule.
__annotations__ Un dictionnaire contenant des informations d'annotation de type. La clé du dictionnaire est le nom du paramètre, s'il y a un commentaire sur la valeur de retour'return'C'est la clé.
__kwdefaults__ Un dictionnaire contenant les valeurs par défaut des paramètres de mots clés uniquement.
__slots__ Cette variable de classe peut se voir attribuer une chaîne, une itération ou une séquence de chaînes qui représente le nom de variable utilisé par l'instance.__slots__Réserve l'espace de stockage requis pour les variables déclarées pour chaque instance__dict__Quand__weakref__N'est pas généré automatiquement.
__weakref__ C'est un attribut principalement pour le garbage collection et stocke les références faibles.
__func__ Un attribut d'une méthode de classe qui renvoie un objet fonction qui est l'entité de la méthode.
__self__ Un attribut d'une méthode de classe qui renvoie l'objet auquel il appartient.
__isabstractmethod__ Dans la classe de base abstraite, c'est un attribut pour juger s'il s'agit d'une méthode abstraite.
__members__ Un attribut dédié aux classes d'énumération, un dictionnaire utilisé pour stocker chaque élément.

(Voir: Attributs spéciaux, [Standard Hierarchy](https://docs.python.org/ ja / 3 / reference / datamodel.html # the-standard-type-hierarchy), [\ _ \ _ slotss \ _ \ _](https://docs.python.org/ja/3/reference/datamodel.html? highlight = slots # slots)))

La plupart des attributs affichés dans le tableau appartiennent à Function Object. Puisque tout en Python est un objet, les fonctions sont également des objets. En dehors de ceux indiqués dans le tableau ci-dessus, certains sont utilisés dans certains modules.

Si vous voulez faire référence aux membres de la classe, vous pouvez utiliser ʻinspect.getmembers () en plus de dir () . Vous pouvez aussi restreindre uniquement les méthodes avec ʻinspect.getmembers (obj, inspect.ismethod) . Il y a d'autres fonctions dans le module inspect qui commencent par ʻis`, et vous pouvez les utiliser pour obtenir des membres spécifiques. Voir Documentation pour plus de détails.

5-2 Types et objets

«Type» et «objet» de Python ont une relation comme «poulet d'abord ou œuf d'abord». En d'autres termes, il n'est pas possible d'expliquer clairement lequel vient en premier. Et type et ʻobject` sont dans une relation symbiotique et apparaissent toujours en même temps.

Premièrement, Python est un langage de programmation «tout objet». Et, comme introduit dans [3. Concepts liés à l'orientation objet](# 3-Concepts liés à l'orientation objet), il existe principalement les deux types de relations suivants dans le cadre d'orientation d'objet.

La relation entre ces deux types est illustrée dans la figure ci-dessous.                                                   image.png

Ensuite, regardons type et ʻobject`.

print(object)
#Résultat de l'exécution:<class 'object'>
print(type)
#Résultat de l'exécution:<class 'type'>

Dans le monde Python, ʻobjectest l'aboutissement d'une relation d'héritage et est la classe parente de toutes les classes de données. D'autre part,type` est le sommet de la relation d'instance de classe et est la classe de type de tous les objets. La relation entre les deux peut être exprimée par "l'objet est une instance de type".

print(object.__class__)
#Résultat de l'exécution:<class 'type'>
print(object.__bases__)  #Puisqu'il s'agit du sommet de la relation d'héritage, il n'y a plus
#Résultat de l'exécution:()
print(type.__class__)  #le type lui-même est également une instance de type
#Résultat de l'exécution:<class 'type'>
print(type.__bases__)
#Résultat de l'exécution:(<class 'object'>,)

Ensuite, regardons les classes de données intégrées telles que list, dict et tuple.

print(list.__bases__)
#Résultat de l'exécution:(<class 'object'>,)
print(list.__class__)
#Résultat de l'exécution:<class 'type'>
print(dict.__bases__)
#Résultat de l'exécution:(<class 'object'>,)
print(dict.__class__)
#Résultat de l'exécution:<class 'type'>
print(tuple.__bases__)
#Résultat de l'exécution:(<class 'object'>,)
print(tuple.__class__)
#Résultat de l'exécution:<class 'type'>

De même, la classe parente est ʻobject, qui est une instance de type. Instancions et vérifions list`.

mylist = [1, 2, 3]
print(mylist.__class__)
#Résultat de l'exécution:<class 'list'>
print(mylist.__bases__)
#Résultat de l'exécution:
# ---------------------------------------------------------------------------
# AttributeError                            Traceback (most recent call last)
# <ipython-input-21-0b850541e51b> in <module>
# ----> 1 print(mylist.__bases__)
#
# AttributeError: 'list' object has no attribute '__bases__'

Il semble que la «liste» instanciée n'a pas de classe parente. Ensuite, définissons la classe nous-mêmes et examinons ses instances.

class C:  #En Python3, les classes héritent des objets par défaut
    pass
print(C.__bases__)
#Résultat de l'exécution:(<class 'object'>,)
c = C()
print(c.__class__)
#Résultat de l'exécution:<class '__main__.C'>
print(c.__bases__)
#Résultat de l'exécution:
# ---------------------------------------------------------------------------
# AttributeError                            Traceback (most recent call last)
# <ipython-input-30-bf9b854689d5> in <module>
# ----> 1 print(c.__bases__)
#
# AttributeError: 'C' object has no attribute '__bases__'

La classe parente n'existe pas non plus dans l'instance de classe C ici.

La figure ci-dessous montre les différentes relations jusqu'à présent. Ici, la ligne continue représente la relation d'héritage et la flèche pointe vers la classe parente. La ligne en pointillé représente la relation classe-instance et la flèche pointe vers la classe de type de l'instance. Screen Shot 2020-11-02 at 22.24.34.png

À partir de la vérification ci-dessus, nous sommes arrivés aux résultats suivants.

Alors, qu'est-ce qu'une classe hériterait de type?

class M(type):
    pass
print(M.__class__)
#Résultat de l'exécution:<class 'type'>
print(M.__bases__)
#Résultat de l'exécution:(<class 'type'>,)

La classe de type et la classe parente de la classe «M» sont ici «type». Dans la règle de la figure ci-dessus, il doit être placé dans la première colonne. Mais où doit être placée l'instance «M»?

class TM(metaclass=M):
    pass
print(TM.__class__)
#Résultat de l'exécution:<class '__main__.M'>
print(TM.__bases__)
#Résultat de l'exécution:(<class 'object'>,)

En fait, ce M est [Metaclass](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BF%E3%82%AF%E3%83%A9%E3%82" C'est une classe de la classe appelée% B9). Un TM créé à partir de la métaclasse M appartiendra à la" classe "dans la deuxième colonne de la figure ci-dessus. Je parlerai plus tard de l'utilisation des métaclasses.

Vous vous demandez peut-être pourquoi Python a besoin à la fois de type et de ʻobject. Par exemple, sans "type", la figure ci-dessus aurait deux colonnes, la première colonne serait la "classe de type" et la deuxième colonne serait "l'instance". Le langage de programmation orienté objet statique est à peu près une structure à deux colonnes. Python a une structure à trois colonnes car il crée dynamiquement des classes au moment de l'exécution. La deuxième colonne, ʻobject, est juste une instance de type, vous pouvez donc changer les méthodes et les attributs lors de l'exécution. Une structure à trois rangées est nécessaire pour obtenir cette propriété.

5-3. Métaclasse

5-3-1. La classe est un objet

La classe Python est empruntée à «small talk». Dans la plupart des langages de programmation orientés objet, une classe est un code qui décrit «comment créer un objet».

class ObjectCreater:
    pass
my_object = ObjectCreater()
print(my_object)
#Résultat de l'exécution:<__main__.ObjectCreater object at 0x7fbc76f9a970>

Mais encore une fois, les classes Python sont à la fois des classes et des objets. Lors de l'exécution du mot réservé class, Python crée un objet en mémoire. Dans le code ci-dessus, un objet appelé ʻObjectCreater a été créé. Cet objet "classe" peut créer un objet "instance". C'est le rôle de la «classe». Et comme il s'agit d'un objet, les opérations suivantes peuvent être effectuées sur ʻObjectCreater.

class ObjectCreator:
    pass


def echo(obj):
    print(obj)


echo(ObjectCreator)  #Passer comme argument

ObjectCreator.new_attr = 'foo'  #Augmenter les attributs
assert hasattr(ObjectCreator, 'new_attr') == True

ObjectCreatorMirror = ObjectCreator  #Attribuer à une autre variable

5-3-2. Création dynamique de classes

Les classes sont également des objets, vous devriez donc pouvoir les créer au moment de l'exécution comme n'importe quel autre objet. Tout d'abord, créons une fonction qui crée une classe en utilisant le mot réservé class.

def choose_class(name):
    if name == 'foo':
        class Foo:
            pass
        return Foo
    else:
        class Bar:
            pass
        return Bar


MyClass = choose_class('foo')
print(MyClass)
print(MyClass())

Résultat de l'exécution:

<class '__main__.choose_class.<locals>.Foo'>
<__main__.choose_class.<locals>.Foo object at 0x7fad2abc8340>

J'ai pu créer une classe avec des branches conditionnelles. Cependant, cette méthode n'est pas si «dynamique». Si la classe est également un objet, il doit y avoir quelque chose pour créer la classe. En fait, ce "quelque chose" est le "type" introduit dans [5.2 Types et objets](# 5-2-Types et objets).

Comme la plupart des gens l'ont utilisé, Python a une fonction appelée type.

print(type(1))
#Résultat de l'exécution:<class 'int'>
print(type('1'))
#Résultat de l'exécution:<class 'str'>
print(type(ObjectCreatorMirror))
#Résultat de l'exécution:<class 'type'>

Cependant, type a une autre fonctionnalité. C'est la possibilité de créer des classes au moment de l'exécution. La raison pour laquelle une fonction a deux fonctions est qu'il existe une ancienne classe qui hérite de type dans Python 2 comme introduit dans [3-1. Class](# 3-1-class). Je vais. Pour une compatibilité ultérieure, type a deux fonctions.

MyShinyClass = type("MyShinyClass", (), {})
print(MyShinyClass)
print(MyShinyClass())

Résultat de l'exécution:

<class '__main__.MyShinyClass'>
<__main__.MyShinyClass object at 0x7f9cd02bddc0>

Lors de la création d'une classe avec type, nous avons besoin de trois arguments.

Ensuite, regardons de plus près comment utiliser type.

class Foo:
    bar = True

    def echo_bar(self):
        print(self.bar)

Si vous créez une classe avec la même structure que ci-dessus avec type, ce sera comme suit.

def echo_bar(self):
    print(self.bar)


Foo = type('Foo', (), {'bar': True, 'echo_bar': echo_bar})

Créez une classe avec une relation d'héritage.

class FooChild(Foo):
    pass

Si vous le faites avec type, ce sera comme suit.

FooChild = type('FooChild', (Foo, ), {})

5-3-3. Définition de métaclasse

[Metaclass](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BF%E3%82%AF], comme dans [5-2-Types and Objects] % E3% 83% A9% E3% 82% B9) est la classe de la classe, qui est la classe qui crée la classe. J'ai expliqué que «type» est le parent de toutes les métaclasses, et vous pouvez créer des métaclasses en héritant de «type». »Cependant,« type »lui-même est également une métaclasse. La relation entre les métaclasses, les classes et les instances est illustrée dans la figure ci-dessous.                      image.png

La fonction type est une métaclasse spéciale. En fait, lors de la création d'une classe en utilisant class, Python utilise type dans les coulisses. Par conséquent, tous les «objets» sont des instances de «type».

x = 30
print(x.__class__)
#Résultat de l'exécution:<class 'int'>
print(x.__class__.__class__)
#Résultat de l'exécution:<class 'type'>

type est une métaclasse intégrée. J'ai expliqué comment créer ma propre métaclasse dans [5-2. Types and Objects](5-2-Types and Objects), mais c'est comme suit.

class Meta(type):
    pass


class Foo(metaclass=Meta):
    pass

5-3-4. Comment utiliser la métaclasse

Le but de l'utilisation des métaclasses est d'effectuer automatiquement une certaine personnalisation lorsque vous créez une classe. Par exemple, dans un module, vous pouvez créer une métaclasse comme celle-ci lorsque vous souhaitez mettre en majuscule les noms d'attributs de toutes les classes.

class UpperAttrMetaClass(type):
    # __new__Est un constructeur qui crée une instance self
    # __init__Est un initialiseur qui initialise l'instance créée self
    def __new__(cls, new_class_name,
                new_class_parents, new_class_attr):
        uppercase_attr = {}
        for name, val in new_class_attr.items():
            #Hors méthodes spéciales
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val
        return type.__new__(cls, new_class_name, new_class_parents, new_class_attr)
        #Identique à ci-dessous
        # return super().__new__(cls, new_class_name, new_class_parents, new_class_attr)

Les métaclasses peuvent être utilisées pour la vérification du type de données (https://en.wikipedia.org/wiki/Type_introspection), les contrôles d'héritage, etc. L'introduction de métaclasses peut rendre votre code un peu plus compliqué, mais le rôle des métaclasses est simple. Tout ce que vous avez à faire est d'interrompre le processus de création de la classe par défaut, d'apporter des modifications et de renvoyer la classe modifiée.

De plus, la bibliothèque standard de Python a un module appelé types, qui fournit des fonctions liées aux métaclasses et à la génération de classes.

types.prepare_class (nom, bases = (), kwds = None) est une fonction qui choisit la métaclasse appropriée pour la nouvelle classe que vous êtes sur le point de créer. La valeur de retour de la fonction sera un tuple de metaclass, namespace, kwds. types.new_class (nom, bases = (), kwds = None, exec_body = None) est la fonction qui crée la nouvelle classe, et ʻexec_bodyreçoit la fonction de rappel pour construire l'espace de noms de la nouvelle classe. .. Par exemple, ʻexec_body = lambda ns: collections.OrderedDict ()peut être utilisé pour construire un espace de noms en utilisant un dictionnaire ordonné (non requis depuis Python 3.6).

import types


class A(type):
    expected_ns = {}

    def __new__(cls, name, bases, namespace):
        return type.__new__(cls, name, bases, namespace)

    @classmethod
    def __prepare__(cls, name, bases, **kwargs):
        expected_ns.update(kwargs)
        return expected_ns


B = types.new_class("B", (object,))
C = types.new_class("C", (object,), {"metaclass": A})

#Au bas de la chaîne d'héritage de la métaclasse se trouve A, pas de type
meta, ns, kwds = types.prepare_class("D", (B, C), {"metaclass": type, 'x': 1})

assert meta is A  #Vous pouvez voir que la métaclasse A en bas de la chaîne d'héritage est sélectionnée
assert ns is expected_ns  #D'un__prepare__Peut être confirmé qui est utilisé
print(kwds)  #Vous pouvez voir que l'argument de mot-clé de métaclasse a été supprimé (car il a renvoyé la métaclasse appropriée comme valeur de retour)
#Résultat de l'exécution:{'x': 1}

ORM est un exemple pratique de métaclasse. Prenons l'ORM de Django comme exemple.

from django.db import models


class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()


guy = Person.objects.get(name='bob')
print(guy.age)  # output is 35

L'ORM de Django est très facile à utiliser comme mentionné ci-dessus. Django utilise des métaclasses pour implémenter des requêtes complexes pour les bases de données et plus encore. Je présenterai un exemple d'implémentation d'ORM plus tard, donc pour plus de détails sur Django ORM, voir django.db.models.base.ModelBase Voir /base.py#L72).

5-4. Descripteur

5-4-1. Principes de base des descripteurs

Dans [4-2. Properties](# 4-2-Properties), nous avons vu le décorateur property. property peut non seulement donner à une méthode l'apparence d'une variable d'instance, mais il peut également vérifier les affectations de valeurs et ainsi de suite.

class Student:
    def __init__(self, name, score):
        self.name = name
        self._score = score

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            print('Please input an int')
            return
        self._score = value

Il y a deux problèmes avec la vérification de la valeur avec le décorateur property.

Le descripteur est une solution à ce problème. Les descripteurs permettent de personnaliser la navigation, l'enregistrement et la suppression des attributs d'objet. Si vous implémentez l'un des éléments «get», «set» et «delete» dans la classe, il devient un descripteur. Lorsqu'il est utilisé, le descripteur doit être défini comme une variable de classe de la classe propriétaire.

Il existe deux types de descripteurs:

5-4-2. Descripteur de non-données et descripteur de données

Créons un simple descripteur pour obtenir le nombre de fichiers dans le répertoire.

import os


class DirectorySize:
    def __get__(self, instance, owner):
        return len(os.listdir(instance.dirname))


class Directory:
    size = DirectorySize()  #descripteur

    def __init__(self, dirname):
        self.dirname = dirname


debug = Directory('debug')
print(debug.size)

En plus de self, la méthode de descripteur __get__ prend deux arguments: sa propre classe ʻowner et son instance ʻinstance. Instanciez le descripteur DirectorySize dans la classe Directory et placez-le dans la variable de classe size. Et quand vous appelez size, la méthode __get__ de DirectorySize est appelée.

print(vars(debug))
#Résultat de l'exécution:{'dirname': 'debug'}

Comme vous pouvez le voir dans le code ci-dessus, le descripteur sans données n'existe pas dans l'espace de noms d'instance.

Ensuite, implémentons un descripteur de données qui implémente __get__ et __set__.

import logging


logging.basicConfig(level=logging.INFO)


class LoggedAgeAccess:
    def __get__(self, instance, owner):
        value = instance._age
        logging.info('Accessing %r giving %r', 'age', value)
        return value

    def __set__(self, instance, value):
        logging.info('Updating %r to %r', 'age', value)
        instance._age = value


class Person:
    age = LoggedAgeAccess()  #descripteur

    def __init__(self, name, age):
        self.name = name  #Variable d'instance ordinaire
        self.age = age  #Appeler le descripteur

    def birthday(self):
        self.age += 1  # __get__Quand__set__Les deux sont appelés


mary = Person('Mary M', 30)
mary.age
mary.birthday()

Résultat de l'exécution:

INFO:root:Updating 'age' to 30
INFO:root:Accessing 'age' giving 30
INFO:root:Accessing 'age' giving 30
INFO:root:Updating 'age' to 31

La méthode du descripteur «set» reçoit une instance de la classe propriétaire «instance» et une valeur «valeur» à affecter au descripteur. Attribuer une valeur au descripteur appelle la méthode __set__. Et la même chose est vraie lors de l'initialisation de __init__.

print(vars(mary))
#Résultat de l'exécution:{'name': 'Mary M', '_age': 31}

Dans le descripteur de données, lorsque vous affectez une valeur à une variable d'instance, elle apparaît dans l'espace de noms.

Le descripteur a une méthode appelée «set_name». Vous pouvez obtenir le nom de la variable assigné au descripteur (ʻagedans l'exemple ci-dessus), et vous pouvez également le modifier. L'exemple ci-dessous est un ORM simple implémenté avec un descripteur de données utilisant des métaclasses etset_name`.

import sqlite3


conn = sqlite3.connect('entertainment.db')


class MetaModel(type):
    def __new__(cls, clsname, bases, attrs):
        table = attrs.get('table')
        if table:
            col_names = [k for k in attrs.keys() if k != 'table' and not k.startswith('__')]
            #Donner un type de données factice
            col_names_with_type = [f'{c} {attrs[c].datatype} PRIMARY KEY' if attrs[c].is_primary_key
                                   else f'{c} {attrs[c].datatype}'
                                   for c in col_names ]
            #Créer une table
            create_table = f"CREATE TABLE IF NOT EXISTS {table} ({', '.join(col_names_with_type)});"
            conn.execute(create_table)
            conn.commit()
            attrs['_columns_'] = col_names  #Stocke les noms de colonne pour chaque modèle

        return super().__new__(cls, clsname, bases, attrs)


class Model(metaclass=MetaModel):
    def __init__(self, *col_vals):
        self.col_vals = col_vals  #Stocke la valeur de chaque colonne de l'enregistrement
        cols = self._columns_
        table = self.table
        pk = self.primary_key
        pk_val = self.primary_key_value = col_vals[cols.index(pk)]
        record = conn.execute(f'SELECT * FROM {table} WHERE {pk}=?;',
                              (pk_val,)).fetchone()
        if not record:
            params = ', '.join(f':{c}' for c in cols)
            conn.execute(f"INSERT INTO {table} VALUES ({params});", col_vals)
            conn.commit()
        else:
            params = ', '.join(f"{c}=?" for c in cols)
            upate_col_vals = col_vals + (pk_val,)
            conn.execute(f"UPDATE {table} SET {params} WHERE {pk}=?;", upate_col_vals)


class Field:
    def __init__(self, datatype, primary_key=False):
        self.datatype = datatype
        self.is_primary_key = primary_key

    def __set_name__(self, owner, name):
        if self.is_primary_key:
            owner.primary_key = name
        self.fetch = f'SELECT {name} FROM {owner.table} WHERE {owner.primary_key}=?;'
        self.store = f'UPDATE {owner.table} SET {name}=? WHERE {owner.primary_key}=?;'

    def __get__(self, instance, owner):
        return conn.execute(self.fetch, [instance.primary_key_value]).fetchone()[0]

    def __set__(self, instance, value):
        conn.execute(self.store, [value, instance.primary_key_value])
        conn.commit()
        if self.is_primary_key:
            instance.primary_key_value = value


class MovieModel(Model):
    table = 'Movie'
    title = Field(datatype='TEXT', primary_key=True)
    director = Field(datatype='TEXT')
    year = Field(datatype='INTEGER')


class MusicModel(Model):
    table = 'Music'
    title = Field(datatype='TEXT', primary_key=True)
    artist = Field(datatype='TEXT')
    year = Field(datatype='INTEGER')
    genre = Field(datatype='TEXT')


star_wars = MovieModel('Star Wars', 'George Lucas', 1977)
print(f'{star_wars.title} released in {star_wars.year} by {star_wars.director}')
star_wars.director = 'J.J. Abrams'
print(star_wars.director)
country_roads = MusicModel('Country Roads', 'John Denver', 1973, 'country')
print(f'{country_roads.title} is a {country_roads.genre} song of {country_roads.artist}')

Résultat de l'exécution:

Star Wars released in 1977 by George Lucas
J.J. Abrams
Country Roads is a country song of John Denver

De cette manière, ORM peut être facilement implémenté en combinant des métaclasses et des descripteurs de données. Bien sûr, il n'y a aucune restriction que les deux doivent être utilisés, par exemple le Field de Django n'utilise pas de descripteurs. L'ORM proprement dit est équipé de fonctions plus complexes telles que l'évaluation de type et la mise en cache au niveau de la couche application afin de réduire autant que possible le nombre de communications avec la base de données.

5-4-3. Mécanisme du descripteur

Dans [5-1. Méthode spéciale](# 5-1-Méthode spéciale), j'ai mentionné «getattribute». __getattribute__ est une méthode qui a la fonction de" appelée lors de l'accès à tous les membres de la classe, qu'elle soit indéfinie ou définie ", et elle est appelée comme bx pour une classe utilisant un descripteur. Est remplacé par un processus comme type (b) .__ dict __ ['x'] .__ get__ (b, type (b)).

class Descriptor:
    def __get__(self, instance, owner):
        return self._x

    def __set__(self, instance, value):
        self._x = value


class B:
    x = Descriptor()

    def __init__(self, x):
        self.x = x

    def __getattribute__(self, key):
        attr = type(self).__dict__[key]
        if hasattr(attr, '__get__'):
            return attr.__get__(self, type(self))
        else:
            return attr

Par conséquent, si vous personnalisez __getattribute__, vous ne pourrez pas utiliser le descripteur. Et, comme vous pouvez vous y attendre, les descripteurs de données qui implémentent __get__ et __set__ vérifient les affectations de variables, de sorte qu'ils écrasent toujours les variables d'instance. Dans l'exemple ci-dessus, même si «b = B (1); b.x = 2», «b.x» reste un descripteur. D'un autre côté, un descripteur sans données qui implémente uniquement «get» ne vérifie pas l'affectation des variables, donc si vous mettez à jour la variable de classe directement, le descripteur sera écrasé.

5-4-4. Comment utiliser le descripteur

En fait, vous pouvez utiliser des descripteurs pour obtenir les mêmes fonctionnalités que les décorateurs property, classmethod et staticmethod introduits dans [4. Bases orientées objet Python](# 4-Bases orientées objet python).

5-4-4-1. property property peut être implémenté comme suit.

class Property:
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

Lorsque le descripteur ci-dessus est utilisé pour somemethod sous la forme de decorator @ Property, il s'agit en fait du processus desomemethod = Property (somemethod). Ensuite, affectez le premier argument «fget» à «self.fget» de «Property» pour créer une instance. Ensuite, dans la méthode setter de l'instance Property créée avec @ somemethod.setter, assignez fset à l'argument d'instance. Vous pouvez ensuite attribuer fdel à l'instance également avec @ somemethod.deleter. Ce flux est le même que [4-2. Propriétés](# 4-2-Propriétés).

5-4-4-2. Méthode et fonction

J'ai brièvement présenté MethodType dans [4-1. Class Variables and Methods](# 4-1-Class Variables and Methods). La même fonctionnalité peut être implémentée dans le code Python comme suit:

class MethodType:
    def __init__(self, func, obj):
        self.__func__ = func
        self.__self__ = obj

    def __call__(self, *args, **kwargs):
        func = self.__func__
        obj = self.__self__
        return func(obj, *args, **kwargs)

Et vous pouvez créer un descripteur qui transforme une fonction en une méthode à l'intérieur de la classe comme ceci.

class Function:
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return MethodType(self, obj)

Dans [5-1. Méthode spéciale](# 5-1-Méthode spéciale), j'ai expliqué que "ʻinstance.method .__ func__ renvoie l'objet fonction qui est la substance de la méthode". Cependant, lors d'un accès avec ʻinstance.method, un objet méthode est retourné. Ce comportement peut être simulé avec le descripteur ci-dessus.

5-4-4-3. Méthode de classe et méthode statique

Ces deux décorateurs sont très faciles à implémenter en utilisant le MethodType ci-dessus. Premièrement, classmethod peut être implémenté comme suit:

class ClassMethod:
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, cls=None):
        if cls is None:
            cls = type(obj)
        if hasattr(obj, '__get__'):
            return self.f.__get__(cls)
        return MethodType(self.f, cls)

Lorsqu'il est utilisé sous la forme «@ ClassMethod», il donne «somemethod = ClassMethod (somemethod)», qui vous permet de regrouper «une méthode» sur une classe au lieu d'une instance.

Ensuite, regardons la méthode statique.

class StaticMethod:
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f

L'entité de la méthode statique de Python «staticmethod» est une fonction ordinaire. L'utilisation de StaticMethod ci-dessus sous la forme de @ StaticMethod entraîne égalementsomemethod = StaticMethod (somemethod), en stockant simplement la fonction somemethod dans la variable d'instance de descripteur self.f, et la classe et Empêche son regroupement à une instance. Et quand il est appelé, il renvoie «self.f» tel quel.

5-4-4-5. types.DynamicClassAttribute Moins connue est la bibliothèque Python standard, qui a un descripteur appelé types.DynamicClassAttribute. L'utilisation est la même que la propriété. Ce descripteur est exactement le même qu'une propriété normale lorsqu'il est accédé à partir d'une instance, et sa fonctionnalité change uniquement lorsqu'il est accédé à partir d'une classe. Lorsqu'on accède depuis une classe, si __getattr__ n'est pas défini dans la méthode __getattr__ de la classe, il sera transféré vers la métaclasse __getattr__.

from types import DynamicClassAttribute


class EnumMeta(type):
    def __new__(cls, name, bases, namespace):
        reserved_names = ('name', 'value', 'values')
        enum_namespace = namespace.copy()
        enum_namespace['_member_map_'] = {}
        enum_namespace['_member_map_']['values'] = []
        for k, v in namespace.items():
            if not (k in reserved_names or k.startswith('_')):
                member_namespace = namespace.copy()
                member_namespace.update({"_name_": k, "_value_": v})
                member_cls = super().__new__(cls, name, bases, member_namespace)
                enum_namespace['_member_map_']['values'].append(v)
                enum_namespace['_member_map_'][k] = member_cls()
                enum_namespace[k] = enum_namespace['_member_map_'][k]

        return super().__new__(cls, name, bases, enum_namespace)

    def __getattr__(self, item):
        return self._member_map_[item]


class Enum(metaclass=EnumMeta):
    @DynamicClassAttribute
    def name(self):
        return self._name_

    @DynamicClassAttribute
    def value(self):
        return self._value_

    @DynamicClassAttribute
    def values(self):
        return self._values_


class Color(Enum):
    red = 1
    blue = 2
    green = 3


print(Color.red.value)
#Résultat d'exécution: 1
Color.red._values_ = [1]
print(Color.red.values)  #Valeurs d'instance
#Résultat de l'exécution:[1]
print(Color.values)  #Valeurs de classe
#Résultat de l'exécution:[1, 2, 3]

Ce qui précède est un type d'énumération simple que j'ai fait. Nous parlerons plus en détail des types d'énumération plus tard. Chaque variable de classe de la classe ʻEnum ici a été convertie en une instance de ʻEnum par la métaclasse ʻEnumMeta. Et avec types.DynamicClassAttribute, les valeursde la classe et lesvaleursde l'instance pourraient coexister sans interférer les unes avec les autres. De cette façon, il est plus facile d'utilisertypes.DynamicClassAttribute` lorsque vous souhaitez obtenir un comportement différent pour les classes et les instances.

5-4-4-5. __slots__ Python a un attribut spécial __slots__ qui peut empêcher les classes existantes d'ajouter de nouveaux attributs dans les patchs de singe. L'utilisation est la suivante.

class Student:
    __slots__ = ('name', 'age')


student = Student()

student.name = 'Mike'
student.age = 20

student.grade = 'A'
#Résultat de l'exécution:
# Traceback (most recent call last):
#   File "oop.py", line 10, in <module>
#     student.grade = 'A'
# AttributeError: 'Student' object has no attribute 'grade'

Selon la documentation officielle (https://docs.python.org/3/reference/datamodel.html#notes-on-using-slots), cette fonctionnalité a également été rendue possible par des descripteurs. Bien que non implémentée ici, une telle fonction peut être réalisée en combinant une métaclasse et un descripteur.

5-5. Classe de base abstraite

Bien qu'un peu abordées dans [3-18-7. Pure Artificials](# 3-18-7-Pure Artificials), les classes de base abstraites sont une arme très puissante dans la programmation orientée objet. Vous pouvez utiliser une classe de base abstraite pour déterminer si une classe fournit une interface particulière. La bibliothèque standard de Python a un module appelé ʻabc` qui fournit les outils nécessaires pour une classe de base abstraite, ce qui vous permet de créer des classes de base abstraites, des méthodes abstraites, etc.

5-5-1. Interface

Le terme «interface» a été répété plusieurs fois dans [3. Object-Oriented Concepts](# 3-Object-Oriented Concepts). L'interface est un concept très important dans le domaine du génie logiciel. Une interface bien connue est [API (Application Programming Interface)](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%97%E3%83%AA%E3% 82% B1% E3% 83% BC% E3% 82% B7% E3% 83% A7% E3% 83% B3% E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0% E3% 82% A4% E3% 83% B3% E3% 82% BF% E3% 83% 95% E3% 82% A7% E3% 83% BC% E3% 82% B9). Les interfaces en orientation objet font référence à celles au niveau objet.

Cependant, contrairement à Java et C ++, Python n'a pas de classe d'interface intégrée. Il existe plusieurs façons d'obtenir les mêmes fonctionnalités qu'une interface en Python.

5-5-1-1. Interface par classe de base virtuelle

Les classes de base virtuelles n'ont pas de relation d'héritage explicite, mais elles imposent des contraintes d'interface. En Python, vous pouvez utiliser des métaclasses pour implémenter des classes de base virtuelles.

class RESTAPIMeta(type):
    def __instancecheck__(cls, instance):
        return cls.__subclasscheck__(type(instance))

    def __subclasscheck__(cls, subclass):
        return (hasattr(subclass, 'get') and
                callable(subclass.get) and
                hasattr(subclass, 'post') and
                callable(subclass.post))


class RESTAPIInterface(metaclass=RESTAPIMeta):
    ...


class ItemList:
    def get(self, id):
        pass

    def post(self, id):
        pass


class UserList:
    def get(self, id):
        pass


print(issubclass(ItemList, RestAPIInterface))
#Résultat de l'exécution: True
print(ItemList.__mro__)
#Résultat de l'exécution:(<class '__main__.ItemList'>, <class 'object'>)
print(issubclass(UserList, RestAPIInterface))
#Résultat de l'exécution: Faux

Ce qui précède est un exemple de classe de base virtuelle qui définit API REST. Implémenté pour déterminer une classe avec les méthodes get et post comme classe enfant dans __subclasscheck__ de la métaclasse RESTAPIMeta. Cela vous permet de mettre certaines restrictions sur l'interface de la classe sans héritage explicite.

5-5-1-2. Interface avec la classe de base abstraite

Implémentons la classe de base virtuelle ci-dessus en utilisant le module ʻabc`. Les classes de base abstraites peuvent être créées au format «class MyClass (abc.ABC)» ou «class MyClass (metaclass = abc.ABCMeta)».

import abc


class RestAPIInterface(metaclass=abc.ABCMeta):
    @classmethod
    def __subclasshook__(cls, subclass):
        return (hasattr(subclass, 'get') and
                callable(subclass.get) and
                hasattr(subclass, 'post') and
                callable(subclass.post))


class ItemList:
    def get(self, id):
        pass

    def post(self, id):
        pass


class UserList:
    def get(self, id):
        pass


print(issubclass(ItemList, RestAPIInterface))
#Résultat de l'exécution: True
print(ItemList.__mro__)
#Résultat de l'exécution:(<class '__main__.ItemList'>, <class 'object'>)
print(issubclass(UserList, RestAPIInterface))
#Résultat de l'exécution: Faux

La méthode __subclasshook__ est implémentée comme méthode de classe de la classe d'instance créée à partir de ʻabc.ABCMeta, et quand vous appelez ʻissubclass, elle fonctionne comme un hook.

Vous pouvez également utiliser ʻABCMeta.register` pour enregistrer des sous-classes virtuelles.

import abc


class RestAPIInterface(metaclass=abc.ABCMeta):
    ...


class UserList:
    def get(self, id):
        pass


RestAPIInterface.register(UserList)
print(issubclass(UserList, RestAPIInterface))
#Résultat de l'exécution: True

Il peut également être utilisé comme décorateur.

import abc


class RestAPIInterface(metaclass=abc.ABCMeta):
    ...


@RestAPIInterface.register
class UserList:
    def get(self, id):
        pass


print(issubclass(UserList, RestAPIInterface))

Vous pouvez également obtenir le jeton de cache de la classe de base abstraite actuelle avec ʻabc.get_cache_token () . Ce jeton change à chaque fois que ʻABCMeta.register est exécuté, il peut donc être utilisé pour la vérification d'équivalence.

import abc


class RestAPIInterface(metaclass=abc.ABCMeta):
    ...


class UserList:
    def get(self, id):
        pass


token_old = abc.get_cache_token()
RestAPIInterface.register(UserList)
token_new = abc.get_cache_token()

print(f'{token_old} >>> {token_new}')
#Résultat d'exécution: 36>>> 37
5-5-1-2. Méthode abstraite

Étant donné que l'interface est jusqu'à présent une classe de base virtuelle, il n'y a pas de relation d'héritage et les restrictions sur les classes enfants sont faibles. Si vous n'implémentez pas d'interface spécifique, vous devez utiliser une classe de base abstraite et une méthode abstraite ensemble au lieu d'une classe de base virtuelle lorsque vous souhaitez réaliser une fonction qui provoque une erreur.

import abc


class RestAPIInterface(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def get(self, id):
        raise NotImplementedError

    @abc.abstractmethod
    def post(self, id):
        raise NotImplementedError


class ItemList(RestAPIInterface):
    def get(self, id):
        pass

    def post(self, id):
        pass


class UserList(RestAPIInterface):
    def get(self, id):
        pass


item_list = ItemList()
user_list = UserList()

Résultat de l'exécution:

Traceback (most recent call last):
  File "resource.py", line 29, in <module>
    user_list = UserList()
TypeError: Can't instantiate abstract class UserList with abstract methods post

De plus, ʻabc.abstractmethod peut être utilisé avec classmethod, staticmethod, property`, etc.

import abc


class Model(abc.ABC):
    @classmethod
    @abc.abstractmethod
    def select_all(cls):
        ...

    @staticmethod
    @abc.abstractmethod
    def show_db_name(age):
        ...

    @property
    @abc.abstractmethod
    def row_id(self):
        ...

En implémentant le descripteur __isabstractmethod__ lors de son utilisation avec le descripteur utilisé sous la forme d'un décorateur comme introduit dans [5-4-4. Comment utiliser le descripteur](# 5-4-4-Comment utiliser le descripteur) , ʻAbc.abstractmethod` peuvent être utilisés ensemble.

import abc


class StaticMethod:
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f

    @property
    def __isabstractmethod__(self):
        return getattr(self.f, '__isabstractmethod__', False)


class Model(abc.ABC):
    @StaticMethod
    @abc.abstractmethod
    def show_db_name():
        ...


class ItemModel(Model):
    pass


item_model = ItemModel()
#Résultat de l'exécution:
Traceback (most recent call last):
  File "oop.py", line 27, in <module>
    item_model = ItemModel()
TypeError: Can't instantiate abstract class ItemModel with abstract methods show_db_name

Et les méthodes abstraites Python ne sont pas seulement des interfaces, elles peuvent être héritées avec super pour obtenir le contenu de la méthode.

5-5-1-3. Classe de base abstraite de conteneur

La bibliothèque standard collections.abc fournit une classe de base abstraite pour les structures de données intégrées (conteneurs) Python.

ABC Classe héritière Méthode abstraite méthode de mixin
Container __contains__
Hashable __hash__
Iterable __iter__
Iterator Iterable __next__ __iter__
Reversible Iterable __reversed__
Generator Iterator send、throw close、__iter__ 、__next__
Sized __len__
Callable __call__
Collection Sized、Iterable、
Container
__contains__、
__iter__ 、__len__
Sequence Reversible、
Collection
__getitem__、
__len__
__contains__、__iter__、__reversed__、
index、count
MutableSequence Sequence __getitem__、
__setitem__、
__delitem__、
__len__、insert
Méthodes héritées de Sequence et
append、reverse、extend、pop、remove、
__iadd__
ByteString Sequence __getitem__、
__len__
Méthodes héritées de la séquence
Set Collection __contains__、
__iter__、__len__
__le__、__lt__、__eq__、__ne__、__gt__、
__ge__、__and__、__or__、__sub__、__xor__、
isdisjoint
MutableSet Set __contains__、
__iter__、__len__、
add、discard
Méthodes héritées de Set et clear, pop,
remove、__ior__、__iand__、__ixor__、
__isub__
Mapping Collection __getitem__、
__iter__、__len__
__contains__、keys、items、values、get、
__eq__、__ne__
MutableMapping Mapping __getitem__、
__setitem__、
__delitem__、
__iter__、__len__
Méthodes héritées de Mapping, pop,
popitem、clear、update、setdefault
MappingView Sized __len__
ItemView MappingView、Set __contains__、__iter__
KeysView MappingView、Set __contains__、__iter__
ValuesView MappingView、
Collection
__contains__、__iter__
Awaitable __await__
Coroutine Awaitable send、throw close
AsyncIterable __aiter__
AsyncIterator AsyncIterable __anext__ __aiter__
AsyncGenerator AsyncIterator asend、athrow aclose、__aiter__、__anext__

(Voir: collections.abc - Classes de base abstraites pour conteneurs)

L'utilisation est la même que celle d'une classe de base abstraite normale.

from collections.abc import Set


class ListBasedSet(Set):
    def __init__(self, iterable):
        self.elements = []
        for value in iterable:
            if value not in self.elements:
                self.elements.append(value)

    def __str__(self):
        return repr(self.elements)

    def __iter__(self):
        return iter(self.elements)

    def __contains__(self, value):
        return value in self.elements

    def __len__(self):
        return len(self.elements)


s1 = ListBasedSet('abcdef')
s2 = ListBasedSet('defghi')
overlap = s1 & s2  # __and__Est hérité, donc l'ensemble de produits peut être calculé tel quel
print(overlap)
#Résultat de l'exécution:['d', 'e', 'f']

Ce qui précède est une implémentation d'un ensemble basé sur une liste. Un ensemble normal de Python est implémenté à l'aide d'une table de hachage comme un dictionnaire, il est donc plus rapide qu'une liste en termes de calcul du temps, mais il a un peu plus de calcul spatial, donc cette implémentation est lorsque vous souhaitez économiser la consommation de mémoire. Tu peux l'utiliser.

5-6. Type d'énumération

Type d'énumération est un ensemble fini de variables (formellement identifiants). Il s'agit d'une structure de données abstraite regroupée sous la forme. Cela n'a rien à voir avec la programmation orientée objet. Par exemple, le langage de programmation orienté processus C prend également en charge les types d'énumération. Cependant, dans les langages de programmation orientés objet tels que Java et Python, les types d'énumération sont implémentés sous la forme d'objets de classe.

La raison pour laquelle les types d'énumération sont nécessaires est qu'il existe un grand nombre de données limitées à une certaine plage finie dans le monde réel. Par exemple, un jour correspond à sept types de données finies limitées à une base hebdomadaire. De même, il existe 12 types de données pour chaque mois. Dans les logiciels, il existe d'innombrables ensembles d'états finis tels que les noms de couleurs CSS, les codes HTTP, les valeurs booléennes et les descripteurs de fichiers. Les représenter comme des énumérations plutôt que des nombres incompréhensibles améliore la lisibilité du code et rend la logique plus propre.

La bibliothèque standard Python fournit un module pour créer un type d'énumération appelé ʻenum`. Voyons d'abord l'utilisation de base.

import enum


class Color(enum.Enum):
    red = 1
    green = 2
    blue = 3


print(Color.red)
#Résultat d'exécution: Couleur.red
print(Color['red'])
#Résultat d'exécution: Couleur.red
print(Color(1))
#Résultat d'exécution: Couleur.red
print(Color.red.value)
#Résultat d'exécution: 1
print(Color.red.name)
#Résultat d'exécution: rouge

for color in Color:
    print(color)
#Résultat de l'exécution:
# Color.red
# Color.green
# Color.blue

Contrairement aux variables de classe, le type d'énumération est un objet itérable.

Color.red = 4
#Résultat de l'exécution:
# Traceback (most recent call last):
#   File "oop.py", line 26, in <module>
#     Color.red = 4
#   File "/Users/kaito/opt/miniconda3/lib/python3.8/enum.py", line 383, in __setattr__
#     raise AttributeError('Cannot reassign members.')
# AttributeError: Cannot reassign members.

Les membres du type énumération ne peuvent pas être modifiés en externe.

print(Color.red is Color.green)
#Résultat de l'exécution: Faux
print(Color.red == Color.green)
#Résultat de l'exécution: Faux
red = Color.red
print(Color.red == red)
#Résultat de l'exécution: True
print(Color.red < Color.green)
#Résultat de l'exécution:
# Traceback (most recent call last):
#   File "oop.py", line 30, in <module>
#     print(Color.red < Color.green)
# TypeError: '<' not supported between instances of 'Color' and 'Color'

L'énumération ʻenum.Enum prend uniquement en charge les évaluations de correspondance et d'équivalence entre les membres. Si vous souhaitez comparer d'autres valeurs, vous pouvez utiliser ʻenum.IntEnum.

import enum


class Color(enum.IntEnum):
    red = 1
    green = 2
    blue = 3
    purple = enum.auto()  #incrémentation automatique de la valeur


print(Color.purple > Color.blue)
#Résultat de l'exécution: True

De plus, si vous souhaitez réaliser la combinaison de membres dans l'opération d'enchères, vous pouvez utiliser ʻenum.Flag`.

import enum


class Color(enum.Flag):
    red = enum.auto()
    green = enum.auto()
    blue = enum.auto()
    purple = enum.auto()


print(Color.__members__)
#Résultat de l'exécution:
# {'red': <Color.red: 1>, 'green': <Color.green: 2>, 'blue': <Color.blue: 4>, 'purple': <Color.purple: 8>}
print(Color.purple | Color.blue)
#Résultat d'exécution: Couleur.purple|blue
print(Color.purple | 2)
#Résultat de l'exécution:
# Traceback (most recent call last):
#   File "oop.py", line 13, in <module>
#     print(Color.purple | 2)
# TypeError: unsupported operand type(s) for |: 'Color' and 'int'

ʻEnum.Flag prend en charge les opérations d'enchères entre les membres, mais ne peut pas calculer avec des valeurs entières. Pour ce faire, vous devez utiliser ʻenum.IntFlag.

import enum


class Color(enum.IntFlag):
    red = enum.auto()
    green = enum.auto()
    blue = enum.auto()
    purple = enum.auto()


print(Color.__members__)
#Résultat de l'exécution:
# {'red': <Color.red: 1>, 'green': <Color.green: 2>, 'blue': <Color.blue: 4>, 'purple': <Color.purple: 8>}
print(Color.purple | Color.blue)
#Résultat d'exécution: Couleur.purple|blue
print(Color.purple | 2)
#Résultat d'exécution: Couleur.purple|green

ʻEnum.IntFlag` traite les membres comme des valeurs entières.

Ensuite, regardons de plus près le type d'énumération ordinaire ʻenum.Enum`.

import enum


class MessageResult(enum.Enum):
    SUCCESS = 1
    INVALID_MESSAGE = 2
    INVALID_PARAMETER = 3
    BAD_MESSAGE = 2


print(MessageResult(2))
#Résultat de l'exécution: MessageResult.INVALID_MESSAGE


class MessageResult(enum.Enum):
    SUCCESS = 1
    INVALID_MESSAGE = 2
    INVALID_PARAMETER = 3
    INVALID_MESSAGE = 4

#Résultat de l'exécution:
# Traceback (most recent call last):
#   File "oop.py", line 14, in <module>
#     class MessageResult(enum.Enum):
#   File "oop.py", line 18, in MessageResult
#     INVALID_MESSAGE = 4
#   File "/Users/kaito/opt/miniconda3/lib/python3.8/enum.py", line 99, in __setitem__
#     raise TypeError('Attempted to reuse key: %r' % key)
# TypeError: Attempted to reuse key: 'INVALID_MESSAGE'

Le type d'énumération garantit l'unicité du membre «nom», mais ne le contraint pas à «valeur».

import enum


@enum.unique
class MessageResult(enum.Enum):
    SUCCESS = 1
    INVALID_MESSAGE = 2
    INVALID_PARAMETER = 3
    BAD_MESSAGE = 2

#Résultat de l'exécution:
# Traceback (most recent call last):
#   File "oop.py", line 4, in <module>
#     class MessageResult(enum.Enum):
#   File "/Users/kaito/opt/miniconda3/lib/python3.8/enum.py", line 865, in unique
#     raise ValueError('duplicate values found in %r: %s' %
# ValueError: duplicate values found in <enum 'MessageResult'>: BAD_MESSAGE -> INVALID_MESSAGE

Appliquer ʻenum.unique` à une classe en tant que décorateur garantit également l'unicité de "value".

import enum


MessageResult = enum.Enum(
    value='MessageResult',
    names=('SUCCESS INVALID_MESSAGE INVALID_PARAMETER'),
)

print(MessageResult.__members__)
#Résultat de l'exécution:
# {'SUCCESS': <MessageResult.SUCCESS: 1>, 'INVALID_MESSAGE': <MessageResult.INVALID_MESSAGE: 2>, 'INVALID_PARAMETER': <MessageResult.INVALID_PARAMETER: 3>}

Vous pouvez également créer dynamiquement des types d'énumération avec l''API fonctionnelle 'au lieu du codage en dur. Si vous passez une chaîne séparée par des espaces comme argument names, les nombres seront attribués automatiquement.

import enum


MessageResult = enum.Enum(
    value='MessageResult',
    names=(('SUCCESS', 3),
           ('INVALID_MESSAGE', 2),
           ('INVALID_PARAMETER', 1))
)

print(MessageResult.__members__)
#Résultat de l'exécution:
# {'SUCCESS': <MessageResult.SUCCESS: 3>, 'INVALID_MESSAGE': <MessageResult.INVALID_MESSAGE: 2>, 'INVALID_PARAMETER': <MessageResult.INVALID_PARAMETER: 1>}

Vous pouvez spécifier la «valeur» pour chaque membre en passant un objet itérable en couches comme argument «names».

import enum


class Message(enum.Enum):
    DB_SAVE_SUCCESS = ('Saved successfully', 201)
    INTERNEL_ERROR = ('Internal error happened', 500)
    DB_DELETE_SUCCESS = ('Deleted successfully', 200)
    DB_ITEM_NOT_FOUND = ('Item not found', 404)

    def __init__(self, message, code):
        self.message = message
        self.code = code

    @property
    def ok(self):
        if str(self.code).startswith('2'):
            return True
        return False


print(Message.DB_SAVE_SUCCESS)
#Résultat de l'exécution: Message.DB_SAVE_SUCCESS
print(Message.DB_DELETE_SUCCESS.ok)
#Résultat de l'exécution: True
print(Message.DB_ITEM_NOT_FOUND.ok)
#Résultat de l'exécution: Faux

Les membres du type énumération peuvent utiliser n'importe quel type de données, pas uniquement des valeurs entières. De plus, si vous implémentez l'initialiseur __init__, les valeurs des membres seront transmises à __init__ lors de l'évaluation de la classe. Et vous pouvez utiliser des tapples pour transmettre plusieurs variables.

Cependant, __init__ ne peut pas personnaliser les valeurs des membres. Si vous souhaitez personnaliser les membres, vous devez implémenter le constructeur __new__.

import enum


class Coordinate(bytes, enum.Enum):
    def __new__(cls, value, label, unit):
        obj = bytes.__new__(cls, [value])
        obj._value_ = value
        obj.label = label
        obj.unit = unit
        return obj

    PX = (0, 'P.X', 'km')
    PY = (1, 'P.Y', 'km')
    VX = (2, 'V.X', 'km/s')
    VY = (3, 'V.Y', 'km/s')


print(Coordinate.PY.label, Coordinate.PY.value, Coordinate.PY.unit)
#Résultat d'exécution: P.Y 1 km
print(Coordinate.PY)
#Résultat de l'exécution: Coordonner.PY

Ce qui précède est un exemple de la documentation officielle, qui est une énumération d'objets binaires qui stocke les valeurs des membres et d'autres informations ensemble.

Les types d'énumération sont des classes, vous pouvez donc implémenter des méthodes ou des danseurs en interne. Cependant, les types d'énumération présentent de nombreuses différences par rapport aux classes ordinaires. Premièrement, les types d'énumération sont implémentés dans des métaclasses spéciales, où les membres (variables de classe) sont des instances de la classe. Par conséquent, «new» et «init» fonctionnent pendant l'évaluation de la classe, pas au moment de l'instanciation. Ensuite, le type d'énumération a des attributs spéciaux.

À propos, il semble que l'attribut avec un trait de soulignement avant et après s'appelle _sunder_.

5-7. Classe de données

Jusqu'à présent, nous avons vu la plupart de la programmation orientée objet de Python. Ce n'est pas seulement un problème avec Python, c'est un problème avec les langages de programmation orientés objet en général, mais la définition de classe est très compliquée. En Python, dans la plupart des cas, la définition de «init» est le minimum requis lors de la création d'une classe. Vous devrez peut-être également implémenter d'autres méthodes spéciales. En plus de cela, il y a des moments où vous devez créer un grand nombre de classes similaires. Une telle redondance de code s'appelle le code Boilerplate (https://en.wikipedia.org/wiki/Boilerplate_code).

Vous pouvez utiliser types.SimpleNamespace comme moyen simple de créer une classe.

import types

bbox = types.SimpleNamespace(x=100, y=50, w=20, h=20)

print(bbox)
#Résultat de l'exécution: espace de noms(h=20, w=20, x=100, y=50)
print(bbox==bbox)
#Résultat de l'exécution: True

Si vous implémentez la même fonction que types.SimpleNamespace en Python, ce sera comme suit.

class SimpleNamespace:
    def __init__(self, /, **kwargs):
        self.__dict__.update(kwargs)

    def __repr__(self):
        items = (f"{k}={v!r}" for k, v in self.__dict__.items())
        return "{}({})".format(type(self).__name__, ", ".join(items))

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

Ils ont implémenté __init__, __repr__ et __eq__, mais lorsque vous voulez implémenter __hash__ et d'autres méthodes spéciales pour les opérations de comparaison, cela est assez gênant, et au contraire, la lisibilité du code peut diminuer. Il y a du sexe. types.SimpleNamespace sert uniquement à créer des classes simples (espaces de noms).

Certains développeurs de bibliothèques tiers ont remarqué un problème avec le code de la plaque chauffante et ont développé un outil qui simplifie les définitions de classe. attrs en fait partie. L'utilisation de base est la suivante.

from attr import attrs, attrib


@attrs
class Person:
    name = attrib(type=str)
    sex = attrib(type=str)
    age = attrib(type=int, default=0)


mary = Person('Mary', 'F', 18)
print(mary)
#Résultat de l'exécution: personne(name='Mary', sex='F', age=18)
print(mary == mary)
#Résultat de l'exécution: True

ʻAttrs peut être utilisé sous la forme de décorateurs de classe. Ici, nous avons défini trois variables d'instance et défini ʻage sur l'argument par défaut. ʻAttrsdéfinit automatiquementinit et repr`. Il définit également «eq», «ne», «lt», «__ le__», «gt» et «__ ge__», et la cible de comparaison est un tuple de variables d'instance.

Je n'entrerai pas dans les détails, mais ʻattrsest un outil plus avancé quetypes.SimpleNamespace`, avec beaucoup de fonctionnalités et un outil puissant pour la programmation orientée objet de Python.

Pour éliminer le code de la plaque chauffante, il existe un module appelé dataclasses introduit à partir de Python 3.7 en tant que mouvement officiel. Ce module fournit des fonctionnalités similaires à ʻattrs`, telles que la génération de méthodes spéciales.

from dataclasses import dataclass
from typing import ClassVar
from functools import cached_property

import boto3


@dataclass
class S3Image:
    bucket: str
    key: str
    img_id: int = 1
    client: ClassVar = boto3.client('s3')  #Variable de classe

    @cached_property
    def image_url(self, http_method: str) -> str:
        return self.client.generate_presigned_url(...)


item_image_1 = S3Image('Image', 'ItemImage')
print(item_image_1)
#Résultat de l'exécution: S3Image(bucket='Image', key='ItemImage', img_id=1)
print(item_image_1 == item_image_1)
#Résultat de l'exécution: True

Les classes de données peuvent définir des variables de membre de classe sous la forme d'annotations de type Python. Et il implémente «init» et «repr». Contrairement à ʻattrs, les méthodes spéciales pour les opérations de comparaison implémentent uniquement eqpar défaut. Et la méthode que vous souhaitez implémenter peut être définie comme un argument du décorateur de classedataclass`. La valeur par défaut est la suivante.

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class C:
    ...

Le rôle de chaque argument est presque explicite.

De plus, il existe une fonction appelée «champ» dans la classe de données, et vous pouvez contrôler chaque champ.

from dataclasses import dataclass, field
from typing import List


@dataclass
class C:
    mylist: List[int] = field(default_factory=list)


c = C()
c.mylist += [1, 2, 3]

field prend les arguments suivants et renvoie un objet Field.

Vous pouvez obtenir tous les objets champ de la classe de données sous forme de taples avec la fonction fields.

from dataclasses import fields


print(fields(c))
#Résultat de l'exécution:
# (Field(name='mylist',type=typing.List[int],default=<dataclasses._MISSING_TYPE object at 0x7f8aa098a9a0>,
# default_factory=<class 'list'>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD),)

ʻAsdict et ʻasuple convertissent une instance d'une classe de données en un dictionnaire et un tapple.

@dataclass
class Point:
    x: int
    y: int


@dataclass
class Points:
    pl: List[Point]


p = Point(10, 20)
assert asdict(p) == {'x': 10, 'y': 20}
assert astuple(p) == ((10, 20))

ps = Points([Point(0, 0), Point(10, 4)])
assert asdict(ps) == {'pl': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}  #Traité récursivement

Il existe également une fonction appelée make_dataclass qui crée dynamiquement une classe de données.

from dataclasses import dataclass, field, make_dataclass


C = make_dataclass('C',
                   [('x', int),
                     'y',
                    ('z', int, field(default=5))],
                   namespace={'add_one': lambda self: self.x + 1})

#Identique à ci-dessous
@dataclass
class C:
    x: int
    y: 'typing.Any'
    z: int = 5

    def add_one(self):
        return self.x + 1

Une fonction «replace» est également fournie pour modifier une instance de la classe de données et créer un nouvel objet du même type.

from dataclasses import dataclass, replace


@dataclass
class Point:
    x: int
    y: int

    def __post_init__(self):
        print('__post_init__')


p1 = Point(1, 2)
#Résultat de l'exécution:__post_init__
p2 = replace(p1, x=2)
#Résultat de l'exécution:__post_init__
print(p2)
#Résultat de l'exécution: Point(x=2, y=2)

replace appelle __init __ pour créer une nouvelle instance, mais si __post_init__ est défini, il est appelé après __init __.

De plus, la fonction «is_dataclass» pour déterminer la classe de données est également fournie. «True» est renvoyé uniquement lorsqu'il est utilisé pour une classe de données ou une instance d'une classe de données.

Sommaire

À partir de l'historique orienté objet, nous avons examiné chaque détail des concepts associés à la POO, la programmation orientée objet de Python. Cependant, la programmation orientée objet est si profonde qu'il reste encore beaucoup à apprendre. Je ne pouvais pas expliquer le modèle de conception en détail cette fois. Je voudrais l'introduire dans un autre article.

référence

Data Model Built-in Functions inspect — Inspect live objects types — Dynamic type creation and names for built-in types Descriptor HowTo Guide abc — Abstract Base Classes collections.abc — Abstract Base Classes for Containers enum — Support for enumerations dataclasses — Data Classes What are metaclasses in Python? Types et objets Python (lien rompu)

Recommended Posts

Compréhension complète de la programmation orientée objet de Python
Compréhension complète de la fonction numpy.pad
Comprendre le mémo de la programmation collective des connaissances
Un mémorandum sur l'utilisation de la fonction d'entrée de Python
[Python] Une compréhension approximative du module de journalisation
[Python] Une compréhension approximative des itérables, des itérateurs et des générateurs
[PyTorch] Un peu de compréhension de CrossEntropyLoss avec des formules mathématiques
[Note] Début de la programmation
Recrutement de maîtres de programmation
Simulation de comp gacha
Mémorandum elasticsearch_dsl
Créez instantanément un diagramme de données 2D à l'aide de matplotlib de python
[Pour les débutants] Un résumé en mots des langages de programmation populaires (version 2018)