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.
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.
verbe (sujet, objet)
. Le verbe est le principal, et le sujet et l'objet sont passés en arguments.** 2. Méthode de définition **
class
. Dans cette classe
, définissez diverses méthodes (qui peuvent être comprises comme des fonctions). Créez ensuite une instance de class
et appelez la méthode à partir de cette instance. est un entier, qui est une instance de
class appelée ʻint
. Les instances entières peuvent utiliser des méthodes ʻint` telles que l'addition et la soustraction.class str
.** 3. Méthode d'appel ** En pratique, lorsque vous souhaitez faire de même pour plusieurs objets:
class
et ses classes
, crée une instance de class
pour tous les objets et appelle la méthode à partir de cette instance.Lorsqu'il existe de nombreux processus de ce type
classe
, et les méthodes qui peuvent être utilisées par les objets sont explicites.Ensuite, examinons de plus près les avantages de l'orientation objet à travers des exemples de code Python.
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.
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.
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.
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.
[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`.
[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.
[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.
[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.
Variables de classe Une variable partagée par une classe et ses instances.
[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.
[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.
[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.
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).
[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.
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.
[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.
[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).
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.
[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.
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ée
run ()` 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).
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.
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.
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.
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. (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.
(Source: Principes, modèles et pratiques Agile en C #)
Le principe de l'inversion de dépendance implique deux règles.
Ce principe concerne le découplage entre les modules. Voici des exemples. (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. (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.
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.
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
.
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.
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.
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.
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.
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.
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.
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
[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.
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.
propriété
qui définit la propriétésuper
pour hériter des membres de la classeLes 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
cls
.MethodType
ou une affectation directe.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.
()
comme les variables d'instance.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
@ property
convertit une méthode en une variable.propriété
. Le quatrième argument, la chaîne "" Je suis la propriété'city '. ", Est dans la documentation et se trouve dans
Dog.city .__ doc__`.@ cached_property
est une propriété
qui met en cache les valeurs implémentées dans Python 3.8. Lors du traitement d'une variable avec une grande quantité de calcul, si elle est mise en cache, un recalcul n'est pas nécessaire, ce qui conduit à une amélioration des performances.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'
weight
est une variable ordinaire et peut être utilisée en externe.from module import *
.<classe> ._ <classe> __ x
. Il doit être utilisé lorsque vous souhaitez éviter les conflits d'attributs dus à l'héritage.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
return super (). eat ()
renvoie simplement la méthode ʻeatde la classe parent, vous n'avez pas besoin d'utiliser
super sauf si vous définissez la méthode ʻeat
dans la classe enfant.super () .__ init__ (nom, âge)
exécute l'initialiseur de classe parent __init__
. Sans lui, vous ne pouvez pas appeler «self.name» et «self.age». Il y a plusieurs façons d'écrire l'équivalent de super () .__ init__ (nom, âge)
comme suit.** 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
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é.
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.
«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.
snake
qui hérite de reptile
, vous pouvez dire" le serpent est une sorte de reptile ". Si vous voulez faire référence à la classe parente, vous pouvez utiliser __base__
.snake
appelée Squasher
et dire" Squasher est une instance de snake ". Le snake
ici est défini comme la classe de type du Squasher
. Si vous voulez voir la classe de type d'une instance, vous pouvez utiliser la fonction __class__
outype ()
.La relation entre ces deux types est illustrée dans la figure ci-dessous.
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.
À partir de la vérification ci-dessus, nous sommes arrivés aux résultats suivants.
type
sont des classes qui héritent de ʻobject et ʻobject
, qui sont des" classes "dans l'orientation objet de Python.type
, c'est-à-dire une autre instance d'une" classe ", est une classe qui n'a pas __bases__
, et ce sont des" instances "dans l'orientation objet de Python.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.
type
est le parent de toutes les métaclasses, et vous pouvez créer des métaclasses en héritant de type
.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é.
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
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, ), {})
[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.
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
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).
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
.
property
et x.setter
aux variables, et plus vous avez de variables, plus votre code sera redondant.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:
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 et
set_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.
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é.
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).
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.
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 les
valeursde l'instance pourraient coexister sans interférer les unes avec les autres. De cette façon, il est plus facile d'utiliser
types.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.
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.
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.
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.
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
É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.
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.
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.
__members__
: Un mappage de nom_membre: membre
, en lecture seule._name_
: nom du membre_Value_
: La valeur du membre, __new__
, peut être définie ou modifiée._missing_
: une fonction de recherche utilisée lorsqu'une valeur n'est pas trouvée; elle peut être remplacée._ignore_
: Dans une liste ou une chaîne, les variables de classe qui correspondent aux éléments qu'elle contient ne seront plus converties en membres._order_
: Un attribut de classe pour maintenir l'ordre des membres; par exemple, si vous le définissez avec _order_ = 'rouge vert bleu'
, définir les membres dans un ordre différent provoquera une erreur._generate_next_value
: Utilisé pour API fonctionnelle
et ʻenum.auto` pour obtenir la valeur appropriée pour un membre; elle peut être remplacée.À propos, il semble que l'attribut avec un trait de soulignement avant et après s'appelle _sunder_
.
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 automatiquement
init 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é que
types.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 classe
dataclass`. 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.
est un indicateur pour l'ajout automatique de
ne,
lt,
__ le__,
gt et
ge, qui sont des méthodes spéciales pour les opérations de comparaison autres que
eq. L'argument ʻeq
doit également être vrai pour être vrai. est un indicateur qui contrôle le comportement de
hash. ʻUnsafe_hash = True
ajoute automatiquement la méthode __hash__
, mais cela peut poser des problèmes quand ce n'est pas un objet immuable. Par défaut, lorsque ʻeqet
Frozen sont tous les deux True, la méthode
hash est ajoutée automatiquement, donc aucun contrôle de drapeau n'est requis. Si ʻeq = True
, Frozen = False
, il hérite de la classe parent __hash__
, et si ʻeq = False,
Frozen = True, il est mis à
hash = None. De plus, ʻunsafe_hash = True
ne peut pas être défini avec __hash__
défini.gelé
est un indicateur qui contrôle l'attribution de valeurs aux champs (membres). Si «True», bloquez l'affectation et rendez-la en lecture seule.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
.
default
est un argument qui fournit la valeur par défaut du champ. default
et default_factory
ne peuvent pas coexister.default_factory
prend un objet qui peut être appelé sans argument et fournit la fabrique par défaut pour le champ. Par exemple, l'exemple ci-dessus fournit «list», qui crée la structure de données par défaut pour le champ. est un drapeau pour inclure des champs dans
__init __`.repr
est un indicateur indiquant si __repr__
contient des champs.compare
est un indicateur pour inclure des champs dans des méthodes spéciales pour des opérations de comparaison telles que __eq__
et __gt__
.hash
est un indicateur indiquant si __hash__
contient des champs. Si hash = None
, utilisez compare
pour calculer la valeur de hachage. Officiellement, «hash = None» est recommandé.metadata
reçoit un mappage ou None
et, dans le cas du mappage, est enveloppé dans un dictionnaire en lecture seule types.MappingProxyType
. Il est principalement utilisé par des modules tiers.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.
À 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.
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