Gewinner des Turing-Preises 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) sagt oft [Objektorientierte Programmierung](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) heißt der Vater. Die Person selbst hat das Erfindungsrecht mehrfach an [öffentlichen Orten] erklärt (https://www.youtube.com/watch?v=oKg1hTOQXoY&t=634s&ab_channel=JeffGonis). Kay verachtet jedoch moderne objektorientierte Sprachen wie "C ++" und "Java". Diese Sprachen sind von der Sprache "Simula 67" geerbt, und ich habe "[Smalltalk](https: // ja. Kay glaubt, dass es nichts mit "wikipedia.org/wiki/Smalltalk)" zu tun hat.
Der Name objektorientiert stammt sicherlich von Alan Kay. Die in C ++ und Java verwendete moderne Objektorientierung unterscheidet sich jedoch erheblich vom Original. Kay selbst erkennt diese Sprachen nicht als Nachfolger an. Was für eine Sprache ist laut Kay Simula 67, die Muttergesellschaft von C ++ und Java? Schauen wir uns nun einen einfachen Beispielcode an.
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;
Es ist eine Klasse mit zwei Variablen. Ich kenne die Grammatik nicht, aber die Kommentare geben Ihnen eine allgemeine Vorstellung davon, wie sie aussieht. Simula 67 ist, wie der Name schon sagt, eine Programmiersprache, die 1967 eingeführt und 1973 veröffentlicht wurde. Smalltalk hingegen ist die Sprache, die in den 1980er Jahren veröffentlicht wurde. Die erste Version (Smalltalk-72) wurde 1975 angekündigt.
Bei class
handelt es sich nicht um objektorientierte Programmierung, aber die class
von Simula 67 besteht aus" Instanz "," Vererbung "," Methode "und" [späte Bindung](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 ist definitiv eine objektorientierte Sprache.
Das objektorientierte Design des Simula 67 ist jedoch auch nicht original. 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) (Gewinner des Turing Award 1980) ein Artikel /resources/text/algol/ACM_Algol_bulletin/1061032/p39-hoare.pdf) wurde angekündigt. Das Konzept der "Rekordklasse" wurde dem Papier vorgelegt. Hoa schrieb ein Beispiel in der Sprache ALGOL.
record class person;
begin integer date of birth;
Boolean male;
reference father, mother, youngest offspring, elder sbling (person)
end;
Komplexer Datentyp, ähnlich der C-Sprache Struktur Richtig.
Und 1966, an einer Sommerschule, Hoa [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) und [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 Ich traf% 83% 80% E3% 83% BC% E3% 83% AB). Es waren diese beiden, die später Simula 67 machten. Hoa teilte die Idee der "Rekordklasse" mit zwei Personen. Laut Herrn Dar hatte Herr Hoa zu diesem Zeitpunkt bereits das Konzept der "Vererbung" entwickelt und sie unterrichtet. 2001 gewannen Kristen Nigard und Orjohan Dahl den Turing Award für ihren Beitrag zur Objektorientierung. Es war zwei Jahre früher als Alan Kay.
Einführung von Simula 67. Sie verstehen auch, dass Simula 67 die erste objektorientierte Sprache der Welt ist. Also ist das Smalltalk, das Kay gefälscht hat? Zusammenfassend ist es nicht. Lisp Für die Sprache "Alles ist eine Liste" hat Smalltalk erstmals das Konzept "Alles ist ein Objekt" erstellt. .. Darüber hinaus interpretiert Smalltalk alle Ausdrücke, einschließlich Operatoren, als "Nachrichten" an Objekte. Smalltalk ist die Programmiersprache, die die Objektorientierung verbessert hat. In den 1980er Jahren produzierte Smalltalk eine objektorientierte Programmiersprache. Unter ihnen sind C ++, das noch lebt und gut ist. Darüber hinaus Functional Programming Das Lisp-Lager, der Urheber der Sprache, half auch beim "Common Lisp Object System".
Schließlich wurde 1996 Java, der Höhepunkt des modernen objektorientierten Programmierparadigmas, angekündigt. Dies ist ein wichtiger Meilenstein in der objektorientierten Geschichte. Java selbst hat nichts in der Objektorientierung erfunden, aber es absorbiert die bisher hervorragenden Konzepte und auch JVM Hervorragende Multi-Plattform-Leistung von E6% 83% B3% E3% 83% 9E% E3% 82% B7% E3% 83% B3) und 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% Mit A7% E3% 83% B3) ist es immer noch eine der Top 3 Programmiersprachen der Welt.
Einführung des Ursprungs der Objektorientierung. Aber was ist überhaupt objektorientiert? Bevor ich zum Hauptthema komme, möchte ich dies anhand eines einfachen Beispiels erläutern.
Objektorientiert ist häufig prozessorientiert ([Prozedurale Programmierung](https://ja.wikipedia.org/wiki/%E6%89%8B%E7%B6%9A%E3%81%8D%E5%9E%8B%] Es wird auch mit E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0)) verglichen. Der folgende Code repräsentiert die prozessorientierten und objektorientierten Formen.
a = 0
# a+Ich möchte 3 Funktionen realisieren
#Prozessorientiert
sum(a, 3)
#Objektorientierung
a.sum(3)
Sie könnten denken, dass es nur anders geschrieben ist. Tatsächlich kann die objektorientierte Programmierung die Logik Ihres Codes verdeutlichen. Und je größer das Programm, desto leistungsfähiger ist es. Schauen wir uns als nächstes die Unterschiede im obigen Code genauer an.
** 1. Syntax ** Sie können die Syntax von Funktionsaufrufen in Wortreihenfolge interpretieren.
** 2. Definitionsmethode **
** 3. Aufrufmethode ** In der Praxis, wenn Sie dasselbe für mehrere Objekte tun möchten:
class
und seine class
Methoden, erstellt eine Instanz von class
für alle Objekte und ruft die Methode von dieser Instanz aus auf.Wenn es viele solche Prozesse gibt
Schauen wir uns als nächstes die Vorteile der Objektorientierung anhand von Python-Codebeispielen genauer an.
Definieren Sie drei Klassen: Vogel, Hund und Fisch.
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))
Erstellen Sie eine Instanz.
bob = Bird("Bob")
john = Bird("John")
david = Dog("David")
fabian = Fish("Fabian")
Rufen Sie dann die Methode move aller Instanzen auf.
bob.move()
john.move()
david.move()
fabian.move()
Ausführungsergebnis:
The bird named Bob is flying
The bird named John is flying
The dog named David is running
The fish named Fabian is swimming
Wenn Sie eine Instanz erstellen, müssen Sie Parameter übergeben. Dieser Parameter sind die Daten, die ein Objekt von anderen Objekten unterscheiden. Zum Beispiel ist der "Name" des Objekts "Bob" "Bob" und der "Name" von "John" ist "John". Obwohl die Instanz aus derselben "Klasse" erstellt wurde, handelt es sich um ein anderes Objekt und dasselbe. Selbst wenn Sie die Methode ausführen, ist das Ergebnis anders.
Außerdem geben verschiedene class`` move
Methoden unterschiedliche Ergebnisse aus. Zum Beispiel gibt move
of Bird`` Der Vogel mit dem Namen ...
aus und Dog
gibt Der Hund mit dem Namen ...
aus. Die "move" -Methode bedeutet "move", und jede Tierklasse kann verschoben werden. Wenn Sie sie also als dieselbe "move" -Methode implementieren, wird die Benutzeroberfläche vereinheitlicht und ist leichter zu merken.
Bei prozessorientierter Implementierung kann dies folgendermaßen aussehen:
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)
Wenn ein Objekt namens "Bob" ankommt, kann nicht entschieden werden, ob es "move_bird" oder "move_dog" sein soll, ohne vorher zu klären, ob es sich um einen "Vogel" oder einen "Hund" handelt. In einem tatsächlichen Programm ist es üblich, nicht nur "Verschieben", sondern Dutzende von Arten von Verarbeitungsfunktionen zu implementieren. Mit zunehmender Anzahl von Funktionen wird es äußerst schwierig, die Entsprechung mit Variablen zu klären. Darüber hinaus können diese Funktionen andere Funktionen intern aufrufen. Wenn Sie diese Funktion in anderen Programmen wiederverwenden, müssen Sie alle intern verwendeten Funktionen ermitteln und migrieren.
Objektorientiert verwendet Variablen, um eine Instanz aus "Klasse" zu erstellen, und Sie können sehen, welche Methode verfügbar ist, indem Sie "Klasse" betrachten. Durch die Zusammenfassung als "Klasse" werden Funktionen im selben Kontext zusammengefasst und einfacher zu verwalten.
Objektorientiert bündelt Funktionen und Daten zusammen. Dies ist sehr praktisch, wenn Sie dieselbe Variable (Daten) mit vielen Funktionen verarbeiten möchten.
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()
Ausführungsergebnis:
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.
Es gibt zwei Möglichkeiten, den obigen Prozess prozessorientiert zu implementieren. Eine ist, es als Argument so zu übergeben, wie es ist.
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)
Bei der obigen Methode müssen Sie jedes Mal dasselbe Argument übergeben, was bei vielen Argumenten sehr ärgerlich ist. Das andere ist, dass Sie nicht jedes Mal ein Argument übergeben müssen.
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)
Diese Methode speichert die Argumente in einem Wörterbuch und entpackt das Wörterbuch als Argument. Wenn die drei Schlüssel "Name, Alter, Höhe" jedoch nicht im Wörterbuch vorhanden sind, tritt ein Fehler auf.
Auf diese Weise kapselt objektorientiert im Vergleich zu prozessorientierten Objekten Verarbeitung und Daten zusammen, sodass die Logik des Codes tendenziell sauberer ist.
Die Realisierung einer dynamischen Abfolge von Aktionen eines Objekts ist für die Prozessorientierung ungeeignet.
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))
Ausführungsergebnis:
energy: 10
energy after eat_meat: 12
energy after eat_fruit: 13
energy after run: 10
energy after eat_meat and run: 9
Die "Klasse" des obigen "Individuums" hat einen internen Zustandsparameter namens "Energie" und drei Methoden zum "Essen von Obst", "Essen von Fleisch" und "Laufen". Als nächstes definieren wir zwei weitere unterteilte "Klassen", "Junge" und "Mädchen".
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()
Ausführungsergebnis:
boy's daily energy: 13
girl's daily energy: 13
Wenn die obige Verarbeitung prozessorientiert implementiert ist, ist es notwendig, eine dedizierte Variable namens "Energie" und eine Funktion zum Verarbeiten jeder "Energie" für jedes Objekt zu definieren, was unvermeidlich redundant ist.
Auch die Struktur von "Subjekt, Verb, Objekt" ist relativ leicht zu verstehen. Im obigen Beispiel können Sie verstehen, dass die Abfolge der Aktionen, zuerst "eat_meat ()" und dann "run ()", für immer fortgesetzt wird. Wenn es prozessorientiert realisiert wird, ist es ein langer Satz wie "boy_energy = eat_meat (boy_energy); boy_energy = run (boy_energy); ..." oder eine hierarchische Struktur wie "eat_meat (run (boy_energy)"). Es wird schwer zu verstehen sein.
Ich habe kurz die Merkmale der Objektorientierung vorgestellt. Von hier aus werden wir auf etwas fortgeschrittenere Inhalte eingehen. Es gibt verschiedene Konzepte in der Objektorientierung, und ich werde diese erklären.
[Klasse](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)) ist eine Konstruktionszeichnung eines Objekts mit denselben Attributen (Variablen, Daten) und Verarbeitung (Methoden, Funktionen). Klassen definieren allgemeine Attribute und Aktionen für daraus generierte Objekte. In prozessorientierten Sprachen werden Variablen nach Typ kategorisiert, während in objektorientierten Sprachen Variablen nach Klassen kategorisiert werden. Und der Typ der objektorientierten Sprache selbst ist auch eine Klasse.
Python 2 hat übrigens eine alte und eine neue Klasse, von denen jede so aussieht:
class oldStyleClass: # inherits from 'type'
pass
class newStyleClass(object): # explicitly inherits from 'object'
pass
In Python 3 verwenden alle Klassen standardmäßig neue Klassen, sodass Sie "Objekt" nicht mehr explizit erben müssen.
[Instanz](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82 % B9) ist einfach [Objekt](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% Manchmal auch B0 genannt)), der [Konstruktor] der Klasse (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) und Initializer, um das Attribut anzugeben Bezieht sich auf eine Entität, der ein Wert zugewiesen wurde.
[Instanziierung](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) bezieht sich auf das Erstellen einer Instanz aus einer Klasse, die eine Konstruktionszeichnung ist.
[Instanzvariablen](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) bezieht sich auf die Variablen, die jeder Instanz zugewiesen sind.
Klassenvariablen Eine von einer Klasse und ihren Instanzen gemeinsam genutzte Variable.
[Methode](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))) bezieht sich auf eine Funktion, die zu einer Klasse oder Instanz gehört.
[Statische Methode](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) bezieht sich auf eine Methode, die ohne Instanziierung aufgerufen werden kann.
[Klassenmethode](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) bezieht sich auf eine Methode, die eine Klasse als Objekt bearbeitet.
Mitglied ist der [Namespace](https: :) der Klasse oder Instanz. //ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93) ist ein zu speicherndes Element. Der Namespace enthält normalerweise Mitgliedsvariablen (Klassen- oder Instanzvariablen) und Mitgliedsfunktionen (verschiedene Methoden).
[Override](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) ist die Kinderklasse % A9% E3% 82% B9_ (% E8% A8% 88% E7% AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6)) (Unterklasse / abgeleitete Klasse) ist [Elternteil Klasse](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%BC%E3%83%91%E3%83%BC%E3%82%AF%E3%83% Geerbt von A9% E3% 82% B9_ (% E8% A8% 88% E7% AE% 97% E6% A9% 9F% E7% A7% 91% E5% AD% A6)) (Superklasse / Basisklasse) Bezieht sich auf das Überschreiben einer Methode.
Kapselung ist Es bezieht sich auf das Gruppieren von Daten und Verarbeiten in einem Objekt, um eine Grenze zu erstellen.
[Vererbung](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))) bezieht sich auf das Entwerfen einer untergeordneten Klasse, die die Struktur einer vorhandenen Klasse erbt. Hat eine Beziehung von is-a oder has-a Es ist eine Architektur, die es Ihnen ermöglicht.
[Polymorphismus](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) (Polymorphismus) bezieht sich hauptsächlich auf die Vielfalt der durch Überschreibungen erreichten Kinderklassen. Das Folgende sind Beispiele.
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())
Ausführungsergebnis:
Animal is running...
Animal is running...
Dog is running...
Dog is running...
Cat is running...
Cat is running...
Mit anderen Worten, der Prozess der Eingabe einer bestimmten Klasse kann normal ablaufen, ohne dass Änderungen an der untergeordneten Klasse "Riskovs Ersetzungsprinzip" vorgenommen werden. % 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 Um% 89% 87).
Das Überladen von Operatoren (https://en.wikipedia.org/wiki/Operator_overloading) bezieht sich auf den Vorgang des Benutzerdefinierens der Funktionalität eines Operators. In Python sind alle Klassen untergeordnete Klassen der Objektklasse, und jede Operatorüberladung wird durch eine spezielle Methode realisiert, sodass es sich um eine Art Polymorphismus handelt. Die spezielle Methode zur Überlastung des Bedieners lautet wie folgt.
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
Ausführungsergebnis:
__lt__
__le__
__eq__
__ne__
__gt__
__ge__
Das Obige ist die Hinzufügung der "Druck" -Verarbeitung zur arithmetischen Verarbeitung. Python hat eine numerische Berechnungsbibliothek namens "Numpy". Und Sie können das Adamal-Produkt einer Matrix in der Form "a * b" berechnen, da Python das Überladen von Operatoren unterstützt.
[Zusammenfassung](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))) bezieht sich auf die Kapselung und Bildung eines Konzepts, indem nur stark verwandte Daten und Prozesse in Objekte gruppiert werden. Sie können beispielsweise ein Tier als Klasse mit dem Namen "Tier" entwerfen, den Zustand des Tieres als Variable festlegen und das Verhalten des Tieres als Methode festlegen.
Diese beiden Konzepte stammen aus der Ruby-Community und repräsentieren die Natur dynamischer Sprachen.
[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) ist eine Möglichkeit, Ihren Code zur Laufzeit zu erweitern oder zu ändern. In Pythons objektorientierter Programmierung wird es als Begriff für dynamisch wechselnde Klassen verwendet.
[Ententypisierung](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) ist die Natur einer dynamisch typisierten objektorientierten Programmiersprache, um beispielsweise eine Funktion wie "run_twice (animal)" auszuführen. Ich werde. Statisch typisierte Sprachen müssen den Typ ihrer Argumente bewerten und sich nur mit der Klasse "Animal" oder ihren Ableitungen ausführen. Dynamisch typisierte Sprachen können jedoch normal ausgeführt werden, wenn sie eine Methode namens "run ()" ohne Typauswertung haben. "Wenn es wie eine Ente geht und wie eine Ente klingt, muss es eine Ente sein."
3-17. SOLID SOLID ist eine Abkürzung für das Auswendiglernen der fünf Prinzipien des Software-Designs im Bereich der objektorientierten Programmierung. Die fünf Prinzipien sind Prinzip der Einzelverantwortung und Prinzip des Öffnens und Schließens Wiki /% E9% 96% 8B% E6% 94% BE /% E9% 96% 89% E9% 8E% 96% E5% 8E% 9F% E5% 89% 87), [Riskovs Ersatzprinzip](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), [Prinzip der Schnittstellentrennung](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) und [Prinzip der Abhängigkeitsumkehrung](https://ja.wikipedia.org/wiki/%E4%BE% 9D% E5% AD% 98% E6% 80% A7% E9% 80% 86% E8% BB% A2% E3% 81% AE% E5% 8E% 9F% E5% 89% 87).
Das Prinzip der Einzelverantwortung ist das Prinzip, dass eine Klasse nur eine Verantwortung haben sollte. "Eine Verantwortung" ist etwas vage, daher ist es in der Praxis keine einzige Verantwortung, wenn es zwei oder mehr Motive für einen Klassenwechsel gibt. Angenommen, es gibt eine Klasse "Rechteck", die ein Rechteck darstellt, wird sie beispielsweise in zwei Modulen verwendet: GUI-Zeichenfunktion und Berechnung der Rechteckgeometrie. Die "Rechteck" -Klasse verstößt hier gegen das Prinzip der Einzelverantwortung.
Das Prinzip der Offenheit und Schließung besteht darin, dass neue Anforderungen so weit wie möglich erweitert werden sollten, anstatt den Code zu ändern. In der Praxis werden häufig Abstraktionen verwendet, um dieses Prinzip zu erreichen. Python Dekorateur ist offen und geschlossen. Mit konformen Features können Sie neue Features implementieren, ohne vorhandene Methoden, Funktionen oder Klassen zu ändern.
Das Ersetzungsprinzip von Riskov lautet, dass die untergeordnete Klasse bei Verwendung der Elternklasse auch diese ersetzen kann. In der Praxis wird dieses Prinzip durch Vererbung und Polymorphismus erreicht. Als untergeordnete Klasse der Klasse "Rechteck", die ein Rechteck darstellt, gibt es beispielsweise eine "Quadrat" -Klasse, die einen Fehler verursacht, wenn Höhe und Breite nicht übereinstimmen. Und wenn eine Funktion oder Methode die Klasse "Rechteck" als Eingabe verwendet und intern einen Unterschied zwischen Höhe und Breite ergibt, kann sie nicht durch die Klasse "Quadrat" ersetzt werden, was gegen das Riskov-Ersetzungsprinzip verstößt.
Das Prinzip der Schnittstellenisolation besteht darin, dass Clients keine Abhängigkeiten von nicht verwendeten Methoden haben sollten. Es ist schwer in Worten zu verstehen, aber siehe das folgende Beispiel. (Quelle: Agile Prinzipien, Muster und Praktiken in C #)
Diese Abbildung zeigt die Beziehung zwischen mehreren Klassen. Die Klasse "Door" verfügt über Methoden, die Türen zugeordnet sind, wie "lock ()", "un_lock ()" und "is_open ()". Wenn die Tür dieses Mal für einen bestimmten Zeitraum geöffnet ist, wird diesmal eine "zeitgesteuerte Tür" erstellt, die sich automatisch schließt. Hier wird die Zeitmessfunktion einer Klasse namens "TimerClient" zugewiesen, und "Door" erbt direkt "TimerClient" und erwirbt diese Funktion. Dann kann TimedDoor
, dasDoor
erbt, auch die Zeitmessfunktion erwerben. "Tür" ist jedoch eine gewöhnliche Tür und erfordert keine Zeitmessfunktion, so dass sie gegen das Prinzip der Schnittstellentrennung verstößt.
Die Lösung besteht darin, eine Adaptermethode oder -variable zu erstellen, die wie unten gezeigt eine Verbindung zum TimerClient
innerhalb der TimedDoor
herstellt und Mixin erbt. Es gibt zwei Arten von Methoden.
(Quelle: Agile Prinzipien, Muster und Praktiken in C #)
Das Prinzip der Abhängigkeitsumkehr beinhaltet zwei Regeln.
Dieses Prinzip dient zur Entkopplung zwischen Modulen. Das Folgende sind Beispiele. (Quelle: Agile Prinzipien, Muster und Praktiken in C #)
Das obere Modul "PolicyLayer" hängt hier vom unteren Modul "MechanismLayer" ab, und das untere Modul "MechanismLayer" hängt vom Detailmodul "UtilityLayer" ab. Dies ist ein Muster, das gegen das Prinzip der Abhängigkeitsumkehr verstößt.
Als Lösung können Sie wie folgt entwerfen. (Quelle: Agile Prinzipien, Muster und Praktiken in C #)
Jetzt hängt "PolicyLayer" von der abstrakten Schnittstelle "PolicyServiceInterface" anstelle des Submoduls ab. Für "PolicyServiceInterface" implementiert "MechanismLayer" jede Schnittstelle.
Mit dem Eingreifen von "PolicyServiceInterface" sind "PolicyLayer" und "MechanismLayer" kompatibel, ohne voneinander abhängig zu sein. Gleiches gilt für MechanismServiceInterface
. Es ist unwahrscheinlich, dass sich die abstrakte Schnittstelle ändert, und ihr Eingriff entkoppelt jedes Modul.
3-18. GRASP GRASP ist eine Entwurfsrichtlinie für ein objektorientiertes System namens "General Responsibility Assignment Software Pattern". GRASP veranschaulicht neun Muster: Informationsexperte, Generator, Controller, lose gekoppelt, hochkohäsiv, polymorph, rein künstlich, indirekt und Schutz vor Variationen.
Das Information Expert Pattern besagt, dass Sie eine Klasse, die alle Informationen enthält, die Sie zur Erfüllung einer Aufgabe benötigen, dieser Klasse überlassen sollten. Angenommen, das EC-Site-System hat zwei Klassen, den Warenkorb "ShopCar" und das Produkt "SKU". Anschließend implementieren wir eine Funktion, die "keine doppelten Produkte im Warenkorb zulässt". Da sich "SKUID" in der Klasse "SKU" befindet, um zu beurteilen, ob dasselbe Produkt dasselbe ist, ist diese Funktion nicht die Klasse "ShopCar", sondern verfügt über alle erforderlichen Informationen gemäß dem Informationsexpertenmuster "SKU" `Es sollte in der Klasse implementiert werden.
Unter der Annahme, dass das Generatormuster Klasse A und Klasse B enthält, sollte B dazu gebracht werden, A zu erstellen, wenn eine oder mehrere der folgenden Bedingungen erfüllt sind. In diesem Fall ist B der Schöpfer von A.
Wenn Ihr EC-Standortsystem beispielsweise eine Klasse namens "Bestellung" hat, die das Produkt "SKU" verwaltet, sollten Sie "SKU" in "Bestellung" erstellen.
Das Controller-Muster besagt, dass Systemereignisse von einem Objekt gesteuert werden sollten, das als Controller bezeichnet wird. Dieser Controller sollte eine Klasse, ein System oder ein Subsystem sein, die nicht mit der Benutzeroberfläche oder den Schnittstellen interagieren.
Beispiel: "C" in der Architektur "MVC" (https://ja.wikipedia.org/wiki/Model_View_Controller) für Ruby on Rails Ist eine Abkürzung für Controller.
Die Kohäsivität ist ein Maß für die Stärke der Abhängigkeiten zwischen den einzelnen Komponenten des Systems. Das lose Kopplungsmuster besagt, dass das System so ausgelegt sein sollte, dass die Abhängigkeiten zwischen den einzelnen Komponenten geschwächt werden. Um die Abhängigkeit zu schwächen, den Import usw. zu minimieren, die Zugriffsberechtigung der Klassenmitglieder zu verschärfen und die Klasse [unveränderlich] zu machen (https://ja.wikipedia.org/wiki/%E3%82) % A4% E3% 83% 9F% E3% 83% A5% E3% 83% BC% E3% 82% BF% E3% 83% 96% E3% 83% AB) Es gibt eine Methode, wie es zu einem Objekt zu machen.
Wenn Sie beispielsweise das EC-Standortsystem als Beispiel hinzufügen, um eine Funktion zur Berechnung des Gesamtpreises eines Produkts hinzuzufügen, erstellen Sie eine neue Klasse, "SKU", "Import", und erstellen Sie eine Methode zum Aggregieren des Betrags. Wenn Sie es zu einer "Bestellung" hinzufügen, die bereits von der "SKU" abhängig ist, können Sie keine unnötigen Abhängigkeiten erstellen.
Die Kohäsivität ist ein Maß für die Stärke der Beziehung zwischen Verantwortlichkeiten (Funktionen) eines Objekts (Moduls). Das sehr zusammenhängende Muster besagt, dass die Verantwortung richtig auf das Objekt ausgerichtet sein sollte.
Erstellen Sie am Beispiel des EC-Standortsystems die Klasse "DAO](https://ja.wikipedia.org/wiki/Data_Access_Object)" OrderDAO "der Bestelldaten und erstellen Sie die Methode" SaveOrder () "zum Speichern der Daten. Implementieren. Wenn Sie die in Excel zu speichernde Funktion und die in DB zu speichernde Funktion realisieren möchten, implementieren Sie verschiedene Klassen, erben Sie "OrderDAO" und [Virtual Method](https: :), anstatt sie gemeinsam in "OrderDAO" zu implementieren. Es ist besser, "SaveOrder ()" von //en.wikipedia.org/wiki/Virtual_function zu überschreiben (das in Python als abstrakte Methode implementiert ist und im Folgenden als abstrakte Methode bezeichnet wird), aber es ist zusammenhängender.
Polymorphismus ist ein Konzept, das in [3-13. Polymorphismus](# 3-13-Polymorphismus) eingeführt wurde und hier als Regel des Systemdesigns strukturiert ist. Das polymorphe Muster implementiert die variablen Teile der Klasse als abstrakte Methoden usw. und Polymorphismus. % E3% 83% A2% E3% 83% BC% E3% 83% 95% E3% 82% A3% E3% 82% BA% E3% 83% A0), und seine konkrete Umsetzung ist eine Kinderklasse Sollte implementiert werden in.
Erstellen Sie beispielsweise eine abstrakte Klasse mit dem Namen "Shape" und implementieren Sie eine abstrakte Methode zum Zeichnen mit dem Namen "Draw ()". Das Erben von "Form", das Erstellen von Rechtecken "Rechteck" und Kreisen "Rund", das Überschreiben von "Draw ()" intern und das Realisieren jeder Zeichenfunktion wird gemäß dem polymorphen Muster entworfen. Werden. Auf diese Weise können Sie das nächste Mal, wenn Sie einen Diamanten "Diamant" hinzufügen möchten, diesen auf dieselbe Weise erstellen, ohne die Systemstruktur zu ändern.
Beim Entwurf eines Systems sind hohe Kohäsivität und lose Kopplung inkonsistent. Ein hoher Zusammenhalt unterteilt die Klassen und konzentriert ihre Verantwortlichkeiten aufeinander. Wenn die Klassen jedoch nicht zusammenarbeiten, funktionieren sie nicht richtig und werden unweigerlich zusammenhängender.
Reine Artefakte erzeugen künstliche Klassen oder abstrakte Klassen, die Zusammenhalt und Konnektivität in Einklang bringen. Im Fall der Zeichenfunktion einer Figur fügen wir diesmal eine Funktion hinzu, die sowohl Windows als auch Linux unterstützt. Da die Systemaufrufe und die Struktur jedes Betriebssystems unterschiedlich sind, muss die Zeichenfunktion "Draw ()" auch auf andere Weise implementiert werden. Hier kann durch Hinzufügen der abstrakten Basisklasse "AbstractShape" das System realisiert werden, ohne die Kohäsivität und die Kohäsivität zu verringern.
Das indirekte Muster ist eine Entwurfsmethode, die die Verringerung der Konnektivität zwischen Klassen fördert, indem ein Zwischenobjekt zwischen den beiden Klassen bereitgestellt wird. MVC In der Architektur ist es ein indirektes Muster, "Contoroller" dazwischen zu setzen, ohne "Model" direkt mit "View" interagieren zu lassen. Es ist ein Design. Die in [3-17-4. Prinzip der Schnittstellentrennung](# 3-17-4-Prinzip der Schnittstellentrennung) eingeführte abstrakte Schnittstellenklasse in der Mitte wurde mit der gleichen Idee entworfen.
Das Muster des Schutzes vor Schwankungen ähnelt [3-17-2. Öffnungs- / Schließprinzip](# 3-17-2-Öffnungs- / Schließprinzip). Kapselung instabiler Teile mit einer einheitlichen Schnittstelle zum Schutz vor Schwankungen. Und wenn Änderungen auftreten, fügen wir die Schnittstelle hinzu, anstatt sie zu ändern. Der Zweck besteht darin, die Funktionalität erweitern zu können, ohne den alten Code zu ändern. Python [Decorator](https://ja.wikipedia.org/wiki/Decorator_%E3%83%91%E3%82%BF%E3] als Beispiel in [3-17-2. Prinzip des Öffnens und Schließens] Zusätzlich zu% 83% BC% E3% 83% B3) [ORM](https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%96%E3%82%B8% 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) ist ein typisches Variationsschutzmuster. Das Ändern der Datenbank wirkt sich nicht auf die Clientseite aus
[Entwurfsmuster](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)) ist Design-Know-how in der objektorientierten Programmierung. Im Gegensatz zu den oben erwähnten Entwurfsprinzipien wie [SOLID](Nr. 3-17-fest) und [GRASP](Nr. 3-18-Griff) ist das Entwurfsmuster eine Erfahrung, die von früheren Entwicklern entwickelt wurde. Es ist wie eine Regel.
Ich erklärte das Konzept der Objektorientierung. Erstellen wir nun vier Klassen, um die Grundstruktur der objektorientierten Programmierung von Python zu sehen.
super
zum Erben von KlassenmitgliedernPython-Klassen haben Variablen und Methoden. Und es gibt verschiedene Arten von jedem.
Im folgenden Code werden die Definitionen verschiedener Variablen und Methoden in Kommentaren erläutert.
from types import MethodType
class Animal:
#Hier können Sie Klassenvariablen definieren
the_name = "animal" #Klassenvariable
def __init__(self, name, age): #Initialisierer
self.name = name #Instanzvariable
self.age = age
#Hier definieren Sie die Methode
def sleep(self): #Instanzmethode
print("{} is sleeping".format(self.name))
def eat(self, food): #Instanzmethode mit Argumenten
print("{} is eating {}".format(self.name, food))
@classmethod
def speak(cls, adjective): #Klassenmethode
print("I am a {} {}".format(adjective, cls.the_name))
@staticmethod
def happening(person, do): #Statische Methode
print("{} is {}ing".format(person, do))
def drink_water(self):
print("{} is drinking water".format(self.name))
Überprüfung:
adam = Animal(name="Adam", age=2) #Instanziierung
print('adam.the_name: {}'.format(adam.the_name)) #Rufen Sie Klassenvariablen aus einer Instanz auf
#Ausführungsergebnis: Adam.the_name: animal
print('Animal.the_name: {}'.format(Animal.the_name)) #Rufen Sie Klassenvariablen aus der Klasse auf
#Ausführungsergebnis: Adam.name: Adam
print('adam.name: {}'.format(adam.name)) #Instanzvariablen aufrufen
#Ausführungsergebnis: Tier.the_name: animal
adam.sleep() #Rufen Sie eine Instanzmethode auf
#Ausführungsergebnis: Adam schläft
adam.eat("meat") #Rufen Sie eine Instanzmethode mit Argumenten auf
#Ausführungsergebnis: Adam isst Fleisch
adam.speak("happy") #Rufen Sie eine Klassenmethode aus einer Instanz auf
#Ausführungsergebnis: Ich bin ein glückliches Tier
Animal.speak("sad") #Rufen Sie eine Klassenmethode aus einer Klasse auf
#Ausführungsergebnis: Ich bin ein trauriges Tier
adam.happening("Tim", "play") #Rufen Sie eine statische Methode aus einer Instanz auf
#Ausführungsergebnis: Tim spielt
Animal.happening("Mary", "watch") #Rufen Sie statische Methoden aus der Klasse auf
#Ausführungsergebnis: Mary schaut zu
Animal.the_name = "Animal" #Klassenvariablen ändern
print('adam.the_name: {}'.format(adam.the_name))
#Ausführungsergebnis: Adam.the_name: Animal
adam.the_name = "animal" #Fix von Instanz
print('Animal.the_name: {}'.format(Animal.the_name))
#Ausführungsergebnis: Tier.the_name: Animal
adam.age = 3 #Instanzvariablen ändern
#Methodenbanding (Affenpflaster)
adam.drink_water = MethodType(drink_water, adam) #Binden an eine Instanz
adam.drink_water()
#Ausführungsergebnis: Adam trinkt Wasser
print(adam.drink_water)
#Ausführungsergebnis:<bound method drink_water of <__main__.Animal object at 0x7ffd68064310>>
try:
Animal.drink_water
except AttributeError as e:
print(e)
#Ausführungsergebnis: Objekt eingeben'Animal' has no attribute 'drink_water'
Animal.drink_water = MethodType(drink_water, Animal) #Binden im Unterricht
adam.drink_water()
#Ausführungsergebnis: Adam trinkt Wasser
Animal.drink_water = drink_water #Banderolierungsmethoden mit direkter Zuordnung
adam.drink_water()
#Ausführungsergebnis: Adam trinkt Wasser
cls
verweist.MethodType
oder direkter Zuweisung erreicht werden.Erstellen Sie eine "Hund" -Klasse, die von "Tier" erbt, und sehen Sie sich die "Eigenschaft" und die zugehörigen Dekorateure an. Diese Dekorateure konvertieren Methoden in Eigenschaften (Variablen) und haben die folgenden zwei Vorteile.
()
wie Instanzvariablen aufgerufen werden.Neben dem Dekorateur gibt es auch eine Möglichkeit, die obige Verarbeitung mit der Funktion "Eigenschaft" zu erreichen.
from functools import cached_property
class Dog(Animal): #Klassenvererbung
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 #Private Variablen abrufen
def country(self):
return self._country
@country.setter #Methodenname.setter
def country(self, value): #Weisen Sie einer privaten Variablen einen Wert zu
self._country = value
@country.deleter #Methodenname.deleter
def country(self): #Löschen Sie den Wert in eine private Variable
del self._country
print("The attr country is deleted")
#Erzielen Sie mit der Eigenschaftsfunktion dieselbe Funktion wie der oben genannte Dekorateur
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 #Zwischengespeicherte Eigenschaft
def official_name(self):
return 'Mr.{} - the Dog'.format(self.name)
Überprüfung:
david = Dog("David", 2)
david.eating()
#Ausführungsergebnis: David isst
david.running # ()Anruf ohne
#Ausführungsergebnis: David kann't run
dean = Dog("Dean", 4)
dean.running
#Ausführungsergebnis: Dean läuft
#Dekorationsmethode
david.country = "America"
print(david.country)
#Ausführungsergebnis: Amerika
del david.country
#Ausführungsergebnis: Das attr-Land wird gelöscht
#Methode nach Eigenschaftsfunktion
david.city = "NewYork"
print(david.city)
#Ausführungsergebnis: NewYork
#Zwischengespeicherte Eigenschaft
print(david.official_name)
#Ausführungsergebnis: Mr..David - the Dog
@ property
konvertiert eine Methode in eine Variable.property
erreicht werden. Das vierte Argument, die Zeichenfolge "Ich bin die" Stadt "-Eigenschaft", befindet sich in der Dokumentation und ist in "Dog.city .__ doc__" zu finden.@ cached_property
ist eine Eigenschaft
, die die in Python 3.8 implementierten Werte zwischenspeichert. Wenn eine Variable mit einem hohen Rechenaufwand zwischengespeichert wird, ist keine Neuberechnung erforderlich, was zu einer Leistungsverbesserung führt.Definieren wir die Cat-Klasse und ihre untergeordnete Klasse BlackCat und betrachten die Vererbung / Überschreibung privater Variablen und Methoden.
__init__
.class Cat(Animal):
def __init__(self, weight): #Von der Elternklasse__init__Überschreiben
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): #Überschreiben Sie übergeordnete Klassenmethoden
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))
Überprüfung:
cole = Cat(5)
print("Cole's weight: {}kg".format(cole.weight))
#Ausführungsergebnis: Cole's weight: 7kg
# _x ist eine private Variable, die für die externe Verwendung nicht empfohlen wird, und ihre Verwendung selbst ist nicht eingeschränkt.
print("Cole's _weight: {}kg".format(cole._weight))
#Ausführungsergebnis: Cole's _weight: 6kg
# __x ist eine private Variable, deren Verwendung von außen verboten ist und deren Verwendung eingeschränkt ist._<class>__Kann zwangsweise in Form von x aufgerufen werden
print("Cole's __weight: {}kg".format(cole._Cat__weight))
#Ausführungsergebnis: Cole's __weight: 5kg
cole.get_real_weight() #Von innen mit Methode__x ist verfügbar
#Ausführungsergebnis: Eigentlich mein__weight is 5kg
cain = BlackCat(5)
cain.get_weight()
#Ausführungsergebnis: Mein Gewicht beträgt 7 kg
# _x ist nicht eingeschränkt, daher kann es von untergeordneten Klassen aufgerufen werden
cain.get_real_weight()
#Ausführungsergebnis: Eigentlich mein_weight is 6kg
#Von der privaten Variablen der übergeordneten Klasse__x kann nicht einfach innerhalb einer untergeordneten Klasse verwendet werden
try:
cain.get_actual_weight()
except AttributeError as e:
print(e)
#Ausführungsergebnis:'Blackcat' object has no attribute '_Blackcat__weight'
weight
ist eine gewöhnliche Variable und kann extern verwendet werden.from module import *
importiert.Definieren wir "Tiger" und "WhiteTiger" und sehen, wie man "Super" verwendet. super
ist eine Funktion zum Aufrufen der Variablen und Methoden der übergeordneten Klasse in der untergeordneten Klasse.
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()
Überprüfung:
tony = WhiteTiger("Tony", 10, 100)
print(tony.eat())
#Ausführungsergebnis: Tony isst
print(tony.speak())
#Ausführungsergebnis: I.'m a white tiger not Lulu's song
return super (). eat ()
gibt nur die eat
-Methode der übergeordneten Klasse zurück. Sie müssen super
nicht verwenden, es sei denn, Sie definieren die eat
-Methode in der untergeordneten Klasse.super () .__ init__ (Name, Alter)
führt den Initialisierer der übergeordneten Klasse __init__
aus. Ohne sie können Sie nicht "self.name" und "self.age" aufrufen. Es gibt verschiedene Möglichkeiten, das Äquivalent von "super () .__ init__ (Name, Alter)" wie folgt zu schreiben.** 1. Definieren Sie die Variablen der übergeordneten Klasse neu. ** **.
def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height
** 2. Rufen Sie die übergeordnete Klasse explizit "init" auf. Wenn Sie den Namen der übergeordneten Klasse ändern, müssen Sie alles korrigieren, was aufgerufen wird. ** **.
def __init__(self, name, age, height):
Tiger.__init__(self, name, age)
self.height = height
Wir haben die Grundformen der objektorientierten Programmierung in Python gesehen. In der Praxis ist der Inhalt von [4. Objektorientierte Python-Grundlagen](objektorientierte Grundlagen von # 4-Python) fast ausreichend. Wenn Sie jedoch erweiterte Funktionen realisieren, eigene Module erstellen oder ein schönes System erstellen möchten, das dem Entwurfsmuster folgt, müssen Sie etwas erweiterte Inhalte kennen.
Wie ich in [3-14. Operator Overload](# 3-14-Operator Overload) ein wenig erwähnt habe, haben Python-Klassen vorher und nachher zwei Unterstriche, wie z. B. __init__
. Es gibt viele Methoden und Variablen, die als "spezielle Methoden", "magische Methoden" oder "dunder" (dbader bezeichnet werden: doppelter Unterstrich). .. Diese Methoden und Variablen sind einigen oder allen Objekten gemeinsam und können verschiedene Funktionen bereitstellen.
import collections
import copy
import math
import operator
import pickle
import sys
import asyncio
class Dunder:
def __abs__(self):
# abs(Dunder());Wird bei der Berechnung von Absolutwerten aufgerufen
return self.x
def __add__(self, other):
# Dunder() + 123;Wird beim Hinzufügen aufgerufen
return self.x + other
async def __aenter__(self):
# `__aenter__`Wann`__aexit__`Muss zusammen implementiert werden
# async with Dunder() as coro;Der Rückgabewert ist auf das erwartete Objekt beschränkt
await asyncio.sleep(1)
async def __aexit__(self, exc_type, exc_val, exc_tb):
# `__aenter__`Wann`__aexit__`Muss zusammen implementiert werden
# async with Dunder() as coro;Der Rückgabewert ist auf das erwartete Objekt beschränkt
await asyncio.sleep(1)
def __aiter__(self):
# `__aiter__`Ist`__anext__`Muss implementiert werden mit
# async for _ in Dunder();Der Rückgabewert ist auf ein asynchrones iterierbares Objekt beschränkt
return self
def __and__(self, other):
# Dunder() & 123;Wird aufgerufen, wenn eine logische Produktoperation ausgeführt wird
return self.x & other
async def __anext__(self):
# `__aiter__`Ist`__anext__`Muss implementiert werden mit
# async for _ in Dunder();Sollte StopAsyncIteration verursachen, wenn das Element weg ist
#Der Rückgabewert ist auf das erwartete Objekt beschränkt
val = await self.readline()
if val == b'':
raise StopAsyncIteration
return val
def __await__(self):
# await Dunder();Der Rückgabewert ist auf den Iterator beschränkt
return self.z # `__next__`Wann`__iter__`Klasse, die implementiert
def __call__(self, *args, **kwargs):
# Dunder()(); callable(Dunder()) == True;Kann wie eine Funktion aufgerufen werden
return self.x
def __init__(self, **kwargs):
# Dunder(y=2);Initialisierer
self.x = 1
self.y = kwargs.get('y')
self.z = [1, 2, 3]
def __bool__(self):
# bool(Dunder()) == True;Wird beim Ausführen von Booleschen Operationen aufgerufen
return True
def __bytes__(self):
# bytes(Dunder());Byte-String
return bytes('123', encoding='UTF-8')
def __ceil__(self):
# math.ceil(Dunder());Wird beim Aufrunden aufgerufen
return math.ceil(self.x)
def __class_getitem__(cls, item):
# Dunder[int] == "Dunder[int]";Diese Methode wird automatisch zu einer Klassenmethode
return f"{cls.__name__}[{item.__name__}]"
def __complex__(self):
# complex(Dunder());Komplexe Zahl
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());Wird beim Erstellen einer flachen Kopie aufgerufen
return copy.copy(self.z)
def __deepcopy__(self, memodict={}):
# copy.deepcopy(Dunder());Wird beim Erstellen einer tiefen Kopie aufgerufen
return copy.deepcopy(self.z)
def __del__(self):
# dunder = Dunder(); del dunder;
#Wird beim Löschen eines Objekts aufgerufen. Unterstützt auch die Speicherbereinigung
del self
def __delattr__(self, item):
# del self.params;Wird beim Löschen einer Instanzvariablen aufgerufen
del self.item
def __delete__(self, instance):
# class Owner: dunder = Dunder()
# del Owner().medusa;Deskriptormethode
#Wird beim Löschen als Attribut der Eigentümerklasse aufgerufen
del self.x
def __delitem__(self, key):
# del Dunder()['some_key']
self.__dict__.pop(key)
def __dir__(self):
# dir(Dunder());Gibt ein iterierbares Objekt zurück, das alle Attribute des Objekts enthält
return super().__dir__()
def __divmod__(self, other):
# divmod(Dunder(), 123);Holen Sie sich den Quotienten und den Rest der Division gleichzeitig
return divmod(self.x, other)
def __enter__(self):
# with Dunder() as dunder: pass
return self
def __eq__(self, other):
# Dunder() == 123;Wird aufgerufen, wenn eine Äquivalenzoperation ausgeführt wird
return self.x == other
def __exit__(self, exc_type, exc_val, exc_tb):
# with Dunder() as dunder: pass;Die Argumente sind TypeError, ValueError bzw. Traceback.
return True
def __float__(self):
# float(Dunder());Machen Sie es zu einer schwimmenden Fraktion
return float(self.x)
def __floor__(self):
# math.floor(Dunder());Schneiden Sie den Dezimalpunkt ab
return math.floor(self.x)
def __floordiv__(self, other):
# Dunder() // 123;Wird beim Abschneiden und Teilen aufgerufen
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';Gibt den Dateisystempfad zurück
return '/var/www/html/mysite'
def __ge__(self, other):
# Dunder() >= 123
return self.x >= other
def __get__(self, instance, owner):
# class Test: dunder = Dunder();Deskriptormethode
# `Test().dunder`Oder`Test.dunder`Wird angerufen, wenn Sie dies tun
return self.x
def __getattr__(self, item):
# Dunder().a;Wird beim Zugriff auf undefinierte Mitglieder aufgerufen
return f'object has no attribute "{item}"'
def __getattribute__(self, item):
# Dunder().a;Wird aufgerufen, wenn auf alle Mitglieder zugegriffen wird, undefiniert oder definiert
# `return self.x`Bitte beachten Sie, dass dies zu einer Endlosschleife führt.
return super().__getattribute__(item)
def __getitem__(self, item):
# Dunder()[item]
return self.__dict__.get(item)
def __getnewargs__(self):
# pickle.loads(pickle.dumps(Dunder()));Wenn unPickle`__new__`Kann die an die Methode übergebenen Argumente definieren
# Python 3.Wird verwendet, wenn das Pickle-Protokoll 2 oder 3 vor 6 verwendet wird
# Python 3.Bei Verwendung von Pickle-Protokoll 2 oder 3 nach 6`__getnewargs_ex__`Wird genutzt
#Nicht direkt angerufen`__reduce__`Machen Sie die Methode
return (2 * self.x, )
def __getstate__(self):
# pickle.dumps(Dunder());Sie können den Status des Objekts während der Pickle-Verarbeitung abrufen
#Nicht direkt angerufen`__reduce__`Machen Sie die Methode
return self.__dict__.copy()
def __gt__(self, other):
# Dunder() > 123
return self.x > 123
def __hash__(self):
# hash(Dunder());Wird bei der Berechnung des Hashwerts aufgerufen
return hash(self.x)
def __iadd__(self, other):
# dunder = Dunder(); dunder += 123; in-Wird beim Hinzufügen eines Platzes aufgerufen
self.x += other
return self
def __iand__(self, other):
# dunder = Dunder(); dunder &= 123; in-Wird aufgerufen, wenn eine logische Produktoperation durchgeführt wird
self.x &= other
return self
def __ifloordiv__(self, other):
# dunder = Dunder(); dunder //= 123; in-Wird beim Abschneiden und Teilen der Stelle aufgerufen
self.x //= other
return self
def __ilshift__(self, other):
# dunder = Dunder(); dunder <<= 123; in-Wird bei der Berechnung der Bitverschiebung nach links aufgerufen
self.x <<= other
return self
def __imatmul__(self, other):
# dunder = Dunder(); dunder @= 123; in-Wird aufgerufen, wenn Binäroperationen vor Ort ausgeführt werden
#In numpy wird es als Punktprodukt implementiert
self.x @= other #Funktion in Standardbibliothek nicht implementiert
return self
def __imod__(self, other):
# dunder = Dunder(); dunder %= 123; in-Wird aufgerufen, wenn eine Restoperation vor Ort ausgeführt wird
self.x %= other
return self
def __imul__(self, other):
# dunder = Dunder(); dunder *= 123; in-Wird beim Multiplizieren des Ortes aufgerufen
self.x *= 123
return self
def __index__(self):
# slice(Dunder(), Dunder() * 2); bin(Dunder()); hex(Dunder()); oct(Dunder())
# operator.index(Dunder());Der Rückgabewert ist auf Ganzzahlen beschränkt`operator.index`Wird von einer Funktion aufgerufen
#Benötigt auch eine Ganzzahl`slice`、`bin()`、`hex()`、`oct()`Ruft diese Methode auf
return self.x
def __init_subclass__(cls, **kwargs):
# class Test(Dunder, **kwargs): ...;Wird bei Vererbung aufgerufen
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
#Diese Methode wird nur aufgerufen, wenn sie durch den Klassentyp (Metaklasse) definiert ist.
#Ebenfalls,`type(other) == self`Wird direkt wahr sein und wird nicht aufgerufen
pass
def __int__(self):
# int(Dunder());Wird beim Konvertieren in eine Ganzzahl aufgerufen
return int(self.x)
def __invert__(self):
# ~Dunder();Wird bei der Berechnung der Bitinversion aufgerufen
return ~self.x
def __ior__(self, other):
# dunder = Dunder(); dunder |= 123; in-Wird aufgerufen, wenn eine logische Summenoperation des Ortes ausgeführt wird
self.x |= other
return self
def __ipow__(self, other):
# dunder = Dunder(); dunder ** 2; in-Wird bei der Berechnung der Ortsmacht aufgerufen
self.x ** other
return self
def __irshift__(self, other):
# dunder = Dunder(); dunder >>= 2; in-Wird bei der Berechnung der Bit-Rechtsverschiebung nach rechts aufgerufen
self.x >>= other
return self
def __isub__(self, other):
# dunder = Dunder(); dunder -= 2; in-Wird beim Subtrahieren des Ortes aufgerufen
return self
def __iter__(self):
# dunder = iter(Dunder()); next(dunder);Methode zum Erstellen eines iterierbaren Objekts
# `__next__`Muss implementiert werden mit
self._i = 0
return self.z[self._i] # self.z ist als Liste definiert
def __itruediv__(self, other):
# dunder = Dunder(); dunder /= 123; in-Wird beim Teilen des Ortes aufgerufen
self.x /= other
return self
def __ixor__(self, other):
# dunder = Dunder(); dunder ^= 123; in-Wird aufgerufen, wenn eine exklusive logische Summenoperation des Ortes ausgeführt wird
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;Wird bei der Berechnung einer Bitverschiebung nach links aufgerufen
return self.x << other
def __lt__(self, other):
# Dunder() < 123
return self.x < other
def __matmul__(self, other):
# Dunder() @ 123;Wird bei binären Operationen aufgerufen
return self.x @ other #Funktion in Standardbibliothek nicht implementiert
def __missing__(self, key):
# class Dict(dict):
# def __missing__(self, key):
# return f'__missing__({key})'
# dunder = Dict({'key': 1})
# print(dunder['unk_key'])
#Methode, die aufgerufen wird, wenn der Schlüssel nicht im Wörterbuch vorhanden ist
pass
def __mod__(self, other):
# Dunder() % 123;Wird aufgerufen, wenn eine Restoperation ausgeführt wird
return self.x % other
def __mro_entries__(self, bases):
#Wird aufgerufen, wenn in der übergeordneten Liste der Klassendefinition ein Nichtklassenobjekt angegeben ist
#Eine Methode zum Korrigieren von Vererbungsbeziehungen bei der Implementierung von Typanmerkungen
# https://www.python.org/dev/peps/pep-0560/#mro-entries
pass
def __mul__(self, other):
# Dunder() * 123;Wird beim Multiplizieren aufgerufen
return self.x * ohter
def __ne__(self, other):
# Dunder() != 123;Wird aufgerufen, wenn ungleiche Operationen ausgeführt werden
return self.x != other
def __neg__(self):
# -Dunder();Wird bei der Berechnung der Nummer angerufen
return -self.x
def __new__(cls, *args, **kwargs):
# Dunder();Konstrukteur
# __init__Erstellen Sie self (die Instanz selbst), die in und anderen Instanzmethoden verwendet wird
return super().__new__(cls)
def __next__(self):
# dunder = iter(Dunder()); next(dunder);Methode zum Erstellen eines iterierbaren Objekts
# `__iter__`Muss implementiert werden mit
self._i += 1
return self.z[self._i]
def __or__(self, other):
# Dunder() | 132;Wird aufgerufen, wenn eine logische Summenoperation ausgeführt wird
return self.x | other
def __pos__(self):
# +Dunder();Wird beim Konvertieren in eine positive Nummer aufgerufen
return +self.x
def __post_init__(self):
#Eine Methode für die Datenklasse`__init__`Nur wenn definiert`__init__`Nachher angerufen
pass
def __pow__(self, power, modulo=None):
# Dunder() ** 123;Wird bei der Berechnung der Leistung aufgerufen
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)
#Wird vor der Auswertung des Klassenkörpers aufgerufen und gibt ein Wörterbuchobjekt (Namespace) zurück, in dem Klassenmitglieder gespeichert sind
#Normal`types.prepare_class`Benutzen mit
#Diese Methode wird nur aufgerufen, wenn sie in der Metaklasse als Klassenmethode definiert ist
return collections.OrderedDict()
def __radd__(self, other):
# 123 + Dunder();Wird aufgerufen, wenn der Bediener einen reflektierten Zusatz erhält
return other + self.x
def __rand__(self, other):
# 123 & Dunder();Wird aufgerufen, wenn der Bediener eine reflektierte logische Produktoperation ausführt
return other & self.x
def __rdiv__(self, other):
# 123 / Dunder();Wird aufgerufen, wenn der Bediener eine reflektierte Division durchführt
return other / self.x
def __rdivmod__(self, other):
# divmod(123, Dunder());Erhalten Sie den Quotienten und den Rest der Division, die gleichzeitig vom Bediener reflektiert werden
return divmod(other, self.x)
def __reduce__(self):
# pickle.dumps(Dunder())
# `__getstate__`、`__setstate__`、`__getnewargs__`Kann verwendet werden, um das Verhalten von Pickle zu steuern
#So viel wie möglich`__reduce__`Definieren der obigen Methode ohne direkte Definition
#Rückwärtskompatibel`__reduce_ex__`Wird bevorzugt verwendet, wenn definiert ist
return super().__reduce__() # return super().__reduce_ex__(protocol)
def __repr__(self):
# repr(Dunder());Gibt eine Zeichenfolge zurück, die eine druckbare Darstellung des Objekts enthält
return super().__repr__()
def __reversed__(self):
# reversed(Dunder());Gibt ein invertiertes Iteratorobjekt zurück
new_instance = copy.deepcopy(self)
new_instance.z = new_instance.z[::-1]
return new_instance
def __rfloordiv__(self, other):
# 123 // Dunder();Wird aufgerufen, wenn der Bediener eine reflektierte Kürzungsaufteilung durchführt
return other // self.x
def __rlshift__(self, other):
# 123 << Dunder();Wird aufgerufen, wenn der Bediener die Verschiebung des reflektierten Bits nach links berechnet
return '__rlshift__'
def __rmatmul__(self, other):
# 123 @ Dunder();Wird aufgerufen, wenn der Bediener eine reflektierte Binäroperation ausführt
return other @ self.x #Funktion in Standardbibliothek nicht implementiert
def __rmod__(self, other):
# 123 % Dunder();Wird aufgerufen, wenn der Bediener eine reflektierte Restoperation ausführt
return other % self.x
def __rmul__(self, other):
# 123 * Dunder();Wird aufgerufen, wenn der Operator eine reflektierte Multiplikation durchführt
return other * self.x
def __ror__(self, other):
# 123 | Dunder();Wird aufgerufen, wenn der Bediener eine reflektierte logische Summenoperation empfängt
return other | self.x
def __round__(self, n=None):
# round(Dunder());Rundung
return round(self.x)
def __rpow__(self, other):
# 123 ** Dunder();Wird aufgerufen, wenn der Bediener die reflektierte Leistung berechnet
return other ** self.x
def __rrshift__(self, other):
# 123 >> Dunder();Wird aufgerufen, wenn der Bediener die Verschiebung des reflektierten Bits nach rechts berechnet
return other >> self.x
def __rshift__(self, other):
# Dunder() >> 123;Wird aufgerufen, wenn eine Bitverschiebung nach rechts berechnet wird
return self.x >> other
def __rsub__(self, other):
# 123 - Dunder();Wird aufgerufen, wenn der Bediener eine reflektierte Subtraktion erhält
return other - self.x
def __rtruediv__(self, other):
# 123 / Dunder();Wird aufgerufen, wenn der Bediener eine reflektierte Division durchführt
return other / self.x
def __rxor__(self, other):
# 123 ^ Dunder();Wird aufgerufen, wenn der Operator die reflektierte exklusive logische Summe berechnet
return other ^ self.x
def __set__(self, instance, value):
# class Test: dunder = Dunder();Deskriptormethode
# `Test().dunder=123`Oder`Test.dunder=123`Wird angerufen, wenn Sie dies tun
instance.x = value
def __set_name__(self, owner, name):
#Zuweisung des Deskriptorvariablennamens
# class Test: pass;Wird beim Erstellen der Eigentümerklasse automatisch aufgerufen.
# dunder = Dunder();Muss beim späteren Binden explizit aufgerufen werden
# Test.dunder = dunder
# dunder.__set_name__(Test, 'dunder')
#Im Namensraum der Testklasse wird ein Deskriptor namens dunder verwendet.'dunder'Zuweisen
owner.__dict__[name] = self
def __setattr__(self, key, value):
# dunder = Dunder(); dunder.x = 123;Wird beim Festlegen von Attributen aufgerufen
self.__dict__[key] = value
def __setitem__(self, key, value):
# dunder = Dunder(); dunder['x'] = 123; ;Wird beim Festlegen von Attributen mit Indizes aufgerufen
self.__dict__[key] = value
def __setstate__(self, state):
# pickle.loads(pickle.dumps(Dunder()))
#Wenn unPickle`__getstate__`Sie können den Status des Objekts verwenden, das in erhalten wurde
#Nicht direkt angerufen`__reduce__`Machen Sie die Methode
self.__dict__.update(state)
def __sizeof__(self):
# sys.getsizeof(Dunder());Gibt die Größe des Objekts zurück
return super().__sizeof__()
def __str__(self):
# str(Dunder())
# print(Dunder())
#Definieren Sie eine Zeichenfolgendarstellung eines Objekts
return f'{self.x}'
def __sub__(self, other):
# Dunder() - 123;Wird beim Subtrahieren aufgerufen
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
#Diese Methode wird nur aufgerufen, wenn sie durch den Klassentyp (Metaklasse) definiert ist.
return NotImplemented
@classmethod
def __subclasshook__(cls, subclass):
# class Test: x = 1; #Klassenvariablen definieren
# issubclass(Test, Dunder) == True
#Diese Methode muss als Klassenmethode der virtuellen Basisklasse definiert werden
if cls is Dunder:
return hasattr(subclass, 'x')
def __truediv__(self, other):
# Dunder() // 123;Wird beim Durchführen einer Kürzungsaufteilung aufgerufen
return self.x // other
def __trunc__(self):
# math.trunc(Dunder());Wird beim Runden aufgerufen
return int(self.x)
def __xor__(self, other):
# Dunder() ^ 123;Wird aufgerufen, wenn eine exklusive logische Summenoperation ausgeführt wird
return self.x ^ other
Die oben genannten sind übliche Spezialmethoden. Sie müssen sich nicht an alles erinnern, und ich denke, es ist genau richtig, so etwas zu haben. Es gibt auch einige spezielle Attribute und Methoden.
__dict__ | ()() |
__class__ | Bedeutung Ein Wörterbuch oder ein anderes Zuordnungsobjekt, in dem die beschreibbaren Attribute eines Objekts gespeichert werden. Sie können mit den integrierten Funktionsvariablen auf dieses Wörterbuch verweisen. Die Klasse, zu der die Klasseninstanz gehört. |
__bases__ | Ein Tapple der Basisklasse (übergeordnete Klasse) des Klassenobjekts. |
__name__ | Der Name der Klasse, Funktion, Methode, Deskriptor oder Generatorinstanz. |
__qualname__ | Qualifizierte Namen für Klassen, Funktionen, Methoden, Deskriptoren und Generatorinstanzen. |
__mro__ | Dieses Attribut ist ein Tupel von Klassen, die beim Erkunden der Basisklasse (übergeordnete Klasse) beim Auflösen einer Methode berücksichtigt werden. |
mro() | Diese Methode kann von der Metaklasse überschrieben werden, um die Reihenfolge der Methodenauflösung für diese Instanz anzupassen. Diese Methode wird aufgerufen, wenn die Klasse instanziiert wird und das Ergebnis ist__mro__Es ist gespeichert in. |
__subclasses__ | Jede Klasse enthält einen schwachen Verweis auf ihre eigene direkte Unterklasse. Diese Methode gibt eine Liste der aktiven Referenzen zurück. |
__doc__ | Funktionsdokumentationszeichenfolge, Keine, wenn keine Dokumentation vorhanden ist. Es wird nicht von Unterklassen geerbt. |
__module__ | Der Name des Moduls, in dem die Funktion definiert ist. Wenn es keinen Modulnamen gibt, ist es Keine. |
__defaults__ | Ein Taple, das den Standardwert für das Argument mit dem Standardwert enthält. Keine, wenn kein Argument mit dem Standardwert vorhanden ist |
__code__ | Ein Codeobjekt, das den Hauptteil der kompilierten Funktion darstellt. |
__globals__ | Wörterbuch mit globalen Variablen von Funktionen(Bezug auf)ist---Dieses Wörterbuch bestimmt den globalen Namespace des Moduls, in dem die Funktion definiert ist. |
__closure__ | Keine oder einzelne freie Variablen der Funktion(Andere Variablen als Argumente)Es ist ein Tupel, das aus einer Gruppe von Zellen besteht, an die Werte gebunden sind. Zellenobjekt ist Attributzelle_Hat Inhalt. Zusätzlich zum Festlegen von Zellenwerten kann dies auch zum Abrufen von Zellenwerten verwendet werden. |
__annotations__ | Ein Wörterbuch mit Informationen zu Typanmerkungen. Der Schlüssel zum Wörterbuch ist der Parametername, wenn der Rückgabewert kommentiert wird'return'Ist der Schlüssel. |
__kwdefaults__ | Ein Wörterbuch mit Standardwerten für Nur-Schlüsselwort-Parameter. |
__slots__ | Dieser Klassenvariablen kann eine Zeichenfolge, eine iterierbare Zeichenfolge oder eine Folge von Zeichenfolgen zugewiesen werden, die den von der Instanz verwendeten Variablennamen darstellen.__slots__Reserviert den erforderlichen Speicherplatz für die für jede Instanz deklarierten Variablen__dict__Wann__weakref__Wird nicht automatisch generiert. |
__weakref__ | Es ist ein Attribut hauptsächlich für die Speicherbereinigung und speichert schwache Referenzen. |
__func__ | Ein Attribut einer Klassenmethode, das ein Funktionsobjekt zurückgibt, das die Entität der Methode ist. |
__self__ | Ein Attribut einer Klassenmethode, die das Objekt zurückgibt, zu dem es gehört. |
__isabstractmethod__ | In der abstrakten Basisklasse ist es ein Attribut, um zu beurteilen, ob es sich um eine abstrakte Methode handelt. |
__members__ | Ein Attribut für Aufzählungsklassen, ein Wörterbuch, in dem jedes Element gespeichert wird. |
(Siehe: Spezielle Attribute, [Standardhierarchie](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)))
Die meisten in der Tabelle angezeigten Attribute gehören Funktionsobjekt. Da alles in Python ein Objekt ist, sind Funktionen auch Objekte. Außer den in der obigen Tabelle aufgeführten werden einige in bestimmten Modulen verwendet.
Wenn Sie auf Klassenmitglieder verweisen möchten, können Sie zusätzlich zu dir () `inspect.getmembers () verwenden. Sie können auch nur die Methoden mit "inspect.getmembers (obj, inspect.ismethod)" eingrenzen. Es gibt andere Funktionen im Inspektionsmodul, die mit "is" beginnen und mit denen Sie bestimmte Mitglieder abrufen können. Weitere Informationen finden Sie unter Dokumentation.
Pythons Typ
und Objekt
haben eine Beziehung wie" ob Huhn zuerst kommt oder Ei zuerst kommt ". Mit anderen Worten, es ist nicht möglich, klar zu erklären, welches zuerst kommt. Und "Typ" und "Objekt" stehen in einer symbiotischen Beziehung und erscheinen immer zur gleichen Zeit.
Erstens ist Python eine Programmiersprache "Alles Objekt". Und wie in [3. Konzepte zur Objektorientierung](# 3 - Konzepte zur Objektorientierung) vorgestellt, gibt es im Rahmen der Objektorientierung hauptsächlich die folgenden zwei Arten von Beziehungen.
__base__
verwenden.__class__
oder type ()
verwenden.Die Beziehung zwischen diesen beiden Typen ist in der folgenden Abbildung dargestellt.
Als nächstes schauen wir uns "Typ" und "Objekt" an.
print(object)
#Ausführungsergebnis:<class 'object'>
print(type)
#Ausführungsergebnis:<class 'type'>
In der Python-Welt ist "Objekt" der Höhepunkt einer Vererbungsbeziehung und die übergeordnete Klasse aller Datenklassen. Andererseits ist "Typ" der Scheitelpunkt der Klasseninstanzbeziehung und die Typklasse aller Objekte. Die Beziehung zwischen den beiden kann ausgedrückt werden als "Objekt ist eine Instanz vom Typ".
print(object.__class__)
#Ausführungsergebnis:<class 'type'>
print(object.__bases__) #Da es die Spitze der Vererbungsbeziehung ist, gibt es keine mehr
#Ausführungsergebnis:()
print(type.__class__) #Typ selbst ist auch eine Instanz von Typ
#Ausführungsergebnis:<class 'type'>
print(type.__bases__)
#Ausführungsergebnis:(<class 'object'>,)
Schauen wir uns als nächstes die integrierten Datenklassen wie "list", "dict" und "tuple" an.
print(list.__bases__)
#Ausführungsergebnis:(<class 'object'>,)
print(list.__class__)
#Ausführungsergebnis:<class 'type'>
print(dict.__bases__)
#Ausführungsergebnis:(<class 'object'>,)
print(dict.__class__)
#Ausführungsergebnis:<class 'type'>
print(tuple.__bases__)
#Ausführungsergebnis:(<class 'object'>,)
print(tuple.__class__)
#Ausführungsergebnis:<class 'type'>
In ähnlicher Weise ist die übergeordnete Klasse "Objekt", eine Instanz vom Typ "Typ". Lassen Sie uns die Liste instanziieren und überprüfen.
mylist = [1, 2, 3]
print(mylist.__class__)
#Ausführungsergebnis:<class 'list'>
print(mylist.__bases__)
#Ausführungsergebnis:
# ---------------------------------------------------------------------------
# AttributeError Traceback (most recent call last)
# <ipython-input-21-0b850541e51b> in <module>
# ----> 1 print(mylist.__bases__)
#
# AttributeError: 'list' object has no attribute '__bases__'
Es scheint, dass die instanziierte "Liste" keine übergeordnete Klasse hat. Als nächstes definieren wir die Klasse selbst und werfen einen Blick auf ihre Instanzen.
class C: #In Python3 erben Klassen standardmäßig Objekte
pass
print(C.__bases__)
#Ausführungsergebnis:(<class 'object'>,)
c = C()
print(c.__class__)
#Ausführungsergebnis:<class '__main__.C'>
print(c.__bases__)
#Ausführungsergebnis:
# ---------------------------------------------------------------------------
# AttributeError Traceback (most recent call last)
# <ipython-input-30-bf9b854689d5> in <module>
# ----> 1 print(c.__bases__)
#
# AttributeError: 'C' object has no attribute '__bases__'
Die übergeordnete Klasse existiert auch hier nicht in der C-Klasseninstanz.
Die folgende Abbildung zeigt die verschiedenen Beziehungen bis zu diesem Punkt. Hier stellt die durchgezogene Linie die Vererbungsbeziehung dar und der Pfeil zeigt auf die übergeordnete Klasse. Die gepunktete Linie stellt die Klasse-Instanz-Beziehung dar und der Pfeil zeigt auf die Typklasse der Instanz.
Aus der obigen Überprüfung kamen wir zu den folgenden Ergebnissen.
Was würde eine Klasse von "Typ" erben?
class M(type):
pass
print(M.__class__)
#Ausführungsergebnis:<class 'type'>
print(M.__bases__)
#Ausführungsergebnis:(<class 'type'>,)
Sowohl die Typklasse als auch die übergeordnete Klasse der M-Klasse sind hier Typ. In der Regel in der obigen Abbildung sollte es in der ersten Spalte platziert werden. Aber wo soll die M-Instanz platziert werden?
class TM(metaclass=M):
pass
print(TM.__class__)
#Ausführungsergebnis:<class '__main__.M'>
print(TM.__bases__)
#Ausführungsergebnis:(<class 'object'>,)
Tatsächlich ist dieses "M" [Metaklasse](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BF%E3%82%AF%E3%83%A9%E3%82 Es ist eine Klasse der Klasse% B9). Ein aus der Metaklasse M erstelltes "TM" gehört zur "Klasse" in der zweiten Spalte der obigen Abbildung. Ich werde später mehr über die Verwendung von Metaklassen sprechen.
type
ist das übergeordnete Element aller Metaklassen, und Sie können Metaklassen erstellen, indem Sie type
erben.object
ist das übergeordnete Element aller" Klassen "und die meisten integrierten Datenklassen sind diese" Klassen ".Sie fragen sich vielleicht, warum Python sowohl "Typ" als auch "Objekt" benötigt. Ohne "Typ" hätte die obige Abbildung beispielsweise zwei Spalten, die erste Spalte wäre die "Typklasse" und die zweite Spalte wäre die "Instanz". Die statische objektorientierte Programmiersprache ist ungefähr zweispaltig. Python hat eine dreispaltige Struktur, da es zur Laufzeit dynamisch Klassen erstellt. Das "Objekt" in der zweiten Spalte ist nur eine Instanz vom Typ "Typ", sodass Sie Methoden und Attribute zur Laufzeit ändern können. Eine dreireihige Struktur ist erforderlich, um diese Eigenschaft zu erreichen.
Die Python-Klasse ist aus "Small Talk" entlehnt. In den meisten objektorientierten Programmiersprachen ist eine Klasse Code, der beschreibt, wie ein Objekt erstellt wird.
class ObjectCreater:
pass
my_object = ObjectCreater()
print(my_object)
#Ausführungsergebnis:<__main__.ObjectCreater object at 0x7fbc76f9a970>
Aber auch hier sind Python-Klassen sowohl Klassen als auch Objekte. Bei der Ausführung des reservierten Wortes "class" erstellt Python ein Objekt im Speicher. Im obigen Code wurde ein Objekt namens "ObjectCreater" erstellt. Dieses "Klassen" -Objekt kann ein "Instanz" -Objekt erstellen. Dies ist die Rolle der "Klasse". Und da es sich um ein Objekt handelt, können die folgenden Operationen mit "ObjectCreater" ausgeführt werden.
class ObjectCreator:
pass
def echo(obj):
print(obj)
echo(ObjectCreator) #Als Argument übergeben
ObjectCreator.new_attr = 'foo' #Attribute erhöhen
assert hasattr(ObjectCreator, 'new_attr') == True
ObjectCreatorMirror = ObjectCreator #Einer anderen Variablen zuweisen
Klassen sind auch Objekte, daher sollten Sie sie wie jedes andere Objekt zur Laufzeit erstellen können. Lassen Sie uns zunächst eine Funktion erstellen, die eine Klasse mit dem reservierten Wort "class" erstellt.
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())
Ausführungsergebnis:
<class '__main__.choose_class.<locals>.Foo'>
<__main__.choose_class.<locals>.Foo object at 0x7fad2abc8340>
Ich konnte eine Klasse mit bedingter Verzweigung erstellen. Diese Methode ist jedoch nicht so "dynamisch". Wenn die Klasse auch ein Objekt ist, muss etwas vorhanden sein, um die Klasse zu erstellen. Tatsächlich ist dieses "Etwas" der in [5.2 Typen und Objekte](# 5-2-Typen und Objekte) eingeführte "Typ".
Wie die meisten Leute es benutzt haben, hat Python eine Funktion namens "Typ".
print(type(1))
#Ausführungsergebnis:<class 'int'>
print(type('1'))
#Ausführungsergebnis:<class 'str'>
print(type(ObjectCreatorMirror))
#Ausführungsergebnis:<class 'type'>
Typ hat jedoch eine andere Funktion. Es ist die Möglichkeit, Klassen zur Laufzeit zu erstellen. Der Grund, warum eine Funktion zwei Funktionen hat, ist, dass es eine alte Klasse gibt, die "Typ" in Python 2 erbt, wie in [3-1. Klasse](# 3-1-Klasse) eingeführt. Ich werde. Zur späteren Kompatibilität hat type
zwei Funktionen.
MyShinyClass = type("MyShinyClass", (), {})
print(MyShinyClass)
print(MyShinyClass())
Ausführungsergebnis:
<class '__main__.MyShinyClass'>
<__main__.MyShinyClass object at 0x7f9cd02bddc0>
Beim Erstellen einer Klasse mit "Typ" benötigen wir drei Argumente.
Als nächstes schauen wir uns die Verwendung von type
genauer an.
class Foo:
bar = True
def echo_bar(self):
print(self.bar)
Wenn Sie mit type
eine Klasse mit der gleichen Struktur wie oben erstellen, sieht diese wie folgt aus.
def echo_bar(self):
print(self.bar)
Foo = type('Foo', (), {'bar': True, 'echo_bar': echo_bar})
Erstellen Sie eine Klasse mit einer Vererbungsbeziehung.
class FooChild(Foo):
pass
Wenn Sie es mit "Typ" machen, wird es wie folgt sein.
FooChild = type('FooChild', (Foo, ), {})
[Metaclass](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BF%E3%82%AF] % E3% 83% A9% E3% 82% B9) ist die Klasse der Klasse, die die Klasse ist, die die Klasse erstellt. Ich erklärte, dass " type
das übergeordnete Element aller Metaklassen ist und Sie Metaklassen erstellen können, indem Sie type
erben." Typ
selbst ist jedoch auch eine Metaklasse. Die Beziehung zwischen Metaklassen, Klassen und Instanzen ist in der folgenden Abbildung dargestellt.
Die Funktion "Typ" ist eine spezielle Metaklasse. Tatsächlich verwendet Python beim Erstellen einer Klasse mit "class" "type" hinter den Kulissen. Daher sind alle "Objekte" Instanzen vom Typ "Typ".
x = 30
print(x.__class__)
#Ausführungsergebnis:<class 'int'>
print(x.__class__.__class__)
#Ausführungsergebnis:<class 'type'>
type
ist eine eingebaute Metaklasse. Ich habe in [5-2. Typen und Objekte](5-2-Typen und Objekte) erklärt, wie man eine eigene Metaklasse erstellt, aber es ist wie folgt.
class Meta(type):
pass
class Foo(metaclass=Meta):
pass
Der Zweck der Verwendung von Metaklassen besteht darin, einige Anpassungen automatisch vorzunehmen, wenn Sie eine Klasse erstellen. In einem Modul können Sie beispielsweise eine Metaklasse wie diese erstellen, wenn Sie die Attributnamen aller Klassen groß schreiben möchten.
class UpperAttrMetaClass(type):
# __new__Ist ein Konstruktor, der eine Instanz selbst erstellt
# __init__Ist ein Initialisierer, der die erstellte Instanz selbst initialisiert
def __new__(cls, new_class_name,
new_class_parents, new_class_attr):
uppercase_attr = {}
for name, val in new_class_attr.items():
#Ausgenommen spezielle Methoden
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)
#Gleich wie unten
# return super().__new__(cls, new_class_name, new_class_parents, new_class_attr)
Metaklassen können zur Überprüfung des Datentyps (https://en.wikipedia.org/wiki/Type_introspection), zur Vererbungskontrolle und mehr verwendet werden. Die Einführung von Metaklassen kann Ihren Code etwas komplizierter machen, aber die Rolle von Metaklassen ist einfach. Sie müssen lediglich den Prozess zum Erstellen der Standardklasse unterbrechen, Änderungen vornehmen und die geänderte Klasse zurückgeben.
Darüber hinaus verfügt die Standardbibliothek von Python über ein Modul namens "types", das Funktionen für Metaklassen und Klassengenerierung bereitstellt.
types.prepare_class (name, base = (), kwds = None)
ist eine Funktion, die die entsprechende Metaklasse für die neue Klasse auswählt, die Sie erstellen möchten. Der Rückgabewert der Funktion ist ein Tupel aus "Metaklasse, Namespace, Kwds". types.new_class (name, base = (), kwds = None, exec_body = None)
ist die Funktion, die die neue Klasse erstellt, und exec_body
empfängt die Rückruffunktion zum Erstellen des Namespace für die neue Klasse. .. Beispielsweise können Sie ein geordnetes Wörterbuch mit "exec_body = lambda ns: collection.OrderedDict ()" verwenden, um einen Namespace zu erstellen (seit Python 3.6 nicht erforderlich).
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})
#Das Ende der Vererbungskette für Metaklassen ist A, nicht Typ
meta, ns, kwds = types.prepare_class("D", (B, C), {"metaclass": type, 'x': 1})
assert meta is A #Sie können sehen, dass Metaklasse A am Ende der Vererbungskette ausgewählt ist
assert ns is expected_ns #Von A.__prepare__Kann bestätigt werden, dass verwendet wird
print(kwds) #Sie können sehen, dass das Schlüsselwortargument metaclass entfernt wurde (da die entsprechende Metaclass als Rückgabewert zurückgegeben wurde).
#Ausführungsergebnis:{'x': 1}
ORM ist ein praktisches Beispiel für eine Metaklasse. Nehmen wir als Beispiel Djangos ORM.
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
Djangos ORM ist wie oben erwähnt sehr einfach zu bedienen. Django verwendet Metaklassen, um komplexe Abfragen für Datenbanken und mehr zu implementieren. Ich werde später ein Implementierungsbeispiel für ORM vorstellen. Einzelheiten zu Django ORM finden Sie unter django.db.models.base.ModelBase. Siehe /base.py#L72).
In [4-2. Eigenschaften](# 4-2-Eigenschaften) haben wir den Dekorator "Eigenschaft" gesehen. property
kann eine Methode nicht nur wie eine Instanzvariable aussehen lassen, sondern auch nach Wertzuweisungen suchen usw.
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
Es gibt zwei Probleme bei der Wertprüfung mit dem Dekorator "Eigenschaft".
Der Deskriptor ist eine Lösung für dieses Problem. Deskriptoren dienen zum Anpassen des Durchsuchens, Speicherns und Löschens von Objektattributen. Wenn Sie in der Klasse eines von "get", "set" und "delete" implementieren, wird es zu einem Deskriptor. Bei Verwendung muss der Deskriptor als Klassenvariable der besitzenden Klasse definiert werden.
Es gibt zwei Arten von Deskriptoren:
__get__
und __set__
implementiert, wird als Datendeskriptor bezeichnet.Erstellen wir einen einfachen Deskriptor, um die Anzahl der Dateien im Verzeichnis zu ermitteln.
import os
class DirectorySize:
def __get__(self, instance, owner):
return len(os.listdir(instance.dirname))
class Directory:
size = DirectorySize() #Deskriptor
def __init__(self, dirname):
self.dirname = dirname
debug = Directory('debug')
print(debug.size)
Zusätzlich zu "self" verwendet die Deskriptormethode "get" zwei Argumente: ihre eigene Klasse "owner" und ihre Instanz "instance". Instanziieren Sie den Deskriptor "DirectorySize" in der Klasse "Directory" und fügen Sie ihn in die Klassenvariable "size" ein. Und wenn Sie "size" aufrufen, wird die "get" -Methode von "DirectorySize" aufgerufen.
print(vars(debug))
#Ausführungsergebnis:{'dirname': 'debug'}
Wie Sie dem obigen Code entnehmen können, ist der Nicht-Daten-Deskriptor im Instanz-Namespace nicht vorhanden.
Als nächstes implementieren wir einen Datendeskriptor, der __get__
und __set__
implementiert.
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() #Deskriptor
def __init__(self, name, age):
self.name = name #Gewöhnliche Instanzvariable
self.age = age #Rufen Sie den Deskriptor auf
def birthday(self):
self.age += 1 # __get__Wann__set__Beide heißen
mary = Person('Mary M', 30)
mary.age
mary.birthday()
Ausführungsergebnis:
INFO:root:Updating 'age' to 30
INFO:root:Accessing 'age' giving 30
INFO:root:Accessing 'age' giving 30
INFO:root:Updating 'age' to 31
Die Deskriptormethode "set" empfängt die Instanz "Instanz" der besitzenden Klasse und den Wert "Wert", der dem Deskriptor zugewiesen werden soll. Das Zuweisen eines Werts zum Deskriptor ruft die Methode __set__
auf. Gleiches gilt für die Initialisierung von init.
print(vars(mary))
#Ausführungsergebnis:{'name': 'Mary M', '_age': 31}
Wenn Sie im Datendeskriptor einer Instanzvariablen einen Wert zuweisen, wird dieser im Namespace angezeigt.
Der Deskriptor hat eine Methode namens "set_name". Sie können den dem Deskriptor zugewiesenen Variablennamen abrufen (im obigen Beispiel age
) und Änderungen vornehmen. Das folgende Beispiel ist ein einfaches ORM, das mit einem Datendeskriptor unter Verwendung von Metaklassen und __set_name__
implementiert wurde.
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('__')]
#Geben Sie einen Dummy-Datentyp an
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 ]
#Eine Tabelle erstellen
create_table = f"CREATE TABLE IF NOT EXISTS {table} ({', '.join(col_names_with_type)});"
conn.execute(create_table)
conn.commit()
attrs['_columns_'] = col_names #Speichert Spaltennamen für jedes Modell
return super().__new__(cls, clsname, bases, attrs)
class Model(metaclass=MetaModel):
def __init__(self, *col_vals):
self.col_vals = col_vals #Speichert den Wert jeder Spalte des Datensatzes
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}')
Ausführungsergebnis:
Star Wars released in 1977 by George Lucas
J.J. Abrams
Country Roads is a country song of John Denver
Auf diese Weise kann ORM einfach implementiert werden, indem Metaklassen und Datendeskriptoren kombiniert werden. Natürlich gibt es keine Einschränkung, dass beide verwendet werden müssen, zum Beispiel verwendet Djangos Field
keine Deskriptoren. Das eigentliche ORM ist mit komplizierteren Funktionen wie Typauswertung und Caching auf Anwendungsebene ausgestattet, um die Anzahl der Kommunikationen mit der DB so weit wie möglich zu reduzieren.
In [5-1. Spezielle Methode](# 5-1-Spezielle Methode) erwähnte ich __getattribute__
. __getattribute__
ist eine Methode, die die Funktion" aufgerufen beim Zugriff auf alle Klassenmitglieder, unabhängig davon, ob sie undefiniert oder definiert ist "hat und wie" bx "für eine Klasse mit einem Deskriptor aufgerufen wird. Wird durch einen Prozess wie "Typ (b) .__ dict __ ['x'] .__ get__ (b, Typ (b))" ersetzt.
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
Wenn Sie "getattribute" anpassen, können Sie den Deskriptor daher nicht verwenden. Und wie zu erwarten, überprüfen Datendeskriptoren, die "get" und "set" implementieren, die Variablenzuweisungen, sodass Instanzvariablen immer überschrieben werden. Im obigen Beispiel bleibt "b.x" ein Deskriptor, selbst wenn "b = B (1); b.x = 2" ist. Andererseits überprüft ein Nicht-Daten-Deskriptor, der nur "get" implementiert, die Zuweisung von Variablen nicht. Wenn Sie also die Klassenvariable direkt aktualisieren, wird der Deskriptor überschrieben.
Tatsächlich können Sie Deskriptoren verwenden, um die gleiche Funktionalität wie die in [4. Python-objektorientierte Grundlagen](# 4-Python-objektorientierte Grundlagen) eingeführten Dekoratoren "Eigenschaft", "Klassenmethode" und "statische Methode" zu erzielen.
5-4-4-1. property
property
kann wie folgt implementiert werden.
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__)
Wenn der obige Deskriptor für "Somemethod" in Form von Decorator "@ Property" verwendet wird, ist dies tatsächlich der Prozess von "Somemethod = Property (Somemethod)". Weisen Sie dann das erste Argument "fget" "self.fget" von "Property" zu, um eine Instanz zu erstellen. Weisen Sie als Nächstes in der "setter" -Methode der mit "@ somemethod.setter" erstellten "Property" -Instanz dem Instanzargument "fset" zu. Sie können dann der Instanz auch "fdel" mit "@ somemethod.deleter" zuweisen. Dieser Ablauf entspricht [4-2. Eigenschaften](# 4-2-Eigenschaften).
Ich habe MethodType
kurz in [4-1. Klassenvariablen und Methoden](# 4-1-Klassenvariablen und Methoden) eingeführt. Die gleiche Funktionalität kann in Python-Code wie folgt implementiert werden:
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)
Und Sie können einen Deskriptor erstellen, der eine Funktion in eine Methode innerhalb der Klasse wie diese verwandelt.
class Function:
def __get__(self, obj, objtype=None):
if obj is None:
return self
return MethodType(self, obj)
In [5-1. Spezielle Methode](# 5-1-Spezielle Methode) erklärte ich, dass " instance.method .__ func__
ein Funktionsobjekt zurückgibt, das die Substanz der Methode darstellt. " Beim Zugriff mit "instance.method" wird jedoch ein Methodenobjekt zurückgegeben. Dieses Verhalten kann mit dem obigen Deskriptor simuliert werden.
Diese beiden Dekoratoren sind mit dem oben beschriebenen MethodType
sehr einfach zu implementieren. Erstens kann "Klassenmethode" wie folgt implementiert werden:
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)
Bei Verwendung in der Form "@ ClassMethod" wird "somemethod = ClassMethod (somemethod)" angegeben, mit der Sie "somemethod" einer Klasse anstelle einer Instanz zuordnen können.
Schauen wir uns als nächstes die statische Methode an.
class StaticMethod:
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
return self.f
Die Entität von Pythons statischer Methode "staticmethod" ist eine gewöhnliche Funktion. Die Verwendung der obigen "StaticMethod" in Form von "@ StaticMethod" führt auch zu "somemethod = StaticMethod (somemethod)", wobei einfach die Funktion "somemethod" in der Deskriptorinstanzvariablen "self.f" und der Klasse und gespeichert wird Verhindert, dass es einer Instanz zugeordnet wird. Und wenn es aufgerufen wird, gibt es "self.f" zurück, wie es ist.
5-4-4-5. types.DynamicClassAttribute Weniger bekannt ist die Standard-Python-Bibliothek, die einen Deskriptor namens "types.DynamicClassAttribute" hat. Die Verwendung ist die gleiche wie "Eigenschaft". Dieser Deskriptor ist genau derselbe wie eine normale Eigenschaft, wenn von einer Instanz aus zugegriffen wird, und seine Funktionalität ändert sich nur, wenn von einer Klasse aus zugegriffen wird. Wenn beim Zugriff von einer Klasse "getattr" nicht in der "getattr" -Methode der Klasse definiert ist, wird es in die Metaklasse "getattr" übertragen.
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)
#Ausführungsergebnis: 1
Color.red._values_ = [1]
print(Color.red.values) #Instanzwerte
#Ausführungsergebnis:[1]
print(Color.values) #Klassenwerte
#Ausführungsergebnis:[1, 2, 3]
Das Obige ist ein einfacher Aufzählungstyp, den ich gemacht habe. Wir werden später mehr über Aufzählungstypen sprechen. Jede Klassenvariable der Klasse "Enum" wurde hier von der Metaklasse "EnumMeta" in eine Instanz von "Enum" konvertiert. Und mit types.DynamicClassAttribute
könnten die Werte
der Klasse und die Werte
der Instanz koexistieren, ohne sich gegenseitig zu stören. Auf diese Weise ist es einfacher, "types.DynamicClassAttribute" zu verwenden, wenn Sie ein anderes Verhalten für Klassen und Instanzen erzielen möchten.
5-4-4-5. __slots__ Python hat ein spezielles Attribut "slots", das verhindern kann, dass vorhandene Klassen neue Attribute in Affen-Patches hinzufügen. Die Verwendung ist wie folgt.
class Student:
__slots__ = ('name', 'age')
student = Student()
student.name = 'Mike'
student.age = 20
student.grade = 'A'
#Ausführungsergebnis:
# Traceback (most recent call last):
# File "oop.py", line 10, in <module>
# student.grade = 'A'
# AttributeError: 'Student' object has no attribute 'grade'
Laut der offiziellen Dokumentation (https://docs.python.org/3/reference/datamodel.html#notes-on-using-slots) wurde diese Funktion tatsächlich auch durch Deskriptoren ermöglicht. Obwohl hier nicht implementiert, kann eine solche Funktion durch Kombinieren einer Metaklasse und eines Deskriptors realisiert werden.
Obwohl in [3-18-7. Pure Artificials](# 3-18-7-Pure Artificials) ein wenig angesprochen, sind abstrakte Basisklassen eine sehr mächtige Waffe in der objektorientierten Programmierung. Sie können eine abstrakte Basisklasse verwenden, um festzustellen, ob eine Klasse eine bestimmte Schnittstelle bereitstellt. Die Standardbibliothek von Python verfügt über ein Modul namens "abc", das die für eine abstrakte Basisklasse erforderlichen Tools bereitstellt, mit denen Sie abstrakte Basisklassen, abstrakte Methoden und mehr erstellen können.
Der Begriff "Schnittstelle" wurde in [3. Objektorientierte Konzepte](# 3-Objektorientierte Konzepte) viele Male wiederholt. Schnittstelle ist ein sehr wichtiges Konzept im Bereich der Softwareentwicklung. Eine bekannte Schnittstelle ist [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). Schnittstellen in der Objektorientierung beziehen sich auf solche auf Objektebene.
Im Gegensatz zu Java und C ++ verfügt Python jedoch nicht über eine integrierte Schnittstellenklasse. Es gibt verschiedene Möglichkeiten, die gleiche Funktionalität wie eine Schnittstelle in Python zu erreichen.
Virtuelle Basisklassen haben keine explizite Vererbungsbeziehung, sie legen jedoch Schnittstellenbeschränkungen fest. In Python können Sie Metaklassen verwenden, um virtuelle Basisklassen zu implementieren.
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))
#Ausführungsergebnis: True
print(ItemList.__mro__)
#Ausführungsergebnis:(<class '__main__.ItemList'>, <class 'object'>)
print(issubclass(UserList, RestAPIInterface))
#Ausführungsergebnis: Falsch
Das Obige ist ein Beispiel für eine virtuelle Basisklasse, die [REST-API] definiert (https://ja.wikipedia.org/wiki/Representational_State_Transfer). Implementierte den __subclasscheck__
der Metaklasse RESTAPIMeta
, um eine Klasse mit den Methoden get
und post
als untergeordnete Klasse zu bestimmen. Auf diese Weise können Sie die Schnittstelle der Klasse ohne explizite Vererbung einschränken.
Lassen Sie uns die obige virtuelle Basisklasse mit dem Modul abc
implementieren. Abstrakte Basisklassen können im Format "Klasse MyClass (abc.ABC)" oder "Klasse MyClass (metaclass = abc.ABCMeta)" erstellt werden.
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))
#Ausführungsergebnis: True
print(ItemList.__mro__)
#Ausführungsergebnis:(<class '__main__.ItemList'>, <class 'object'>)
print(issubclass(UserList, RestAPIInterface))
#Ausführungsergebnis: Falsch
Die Methode "subclasshook" ist als Klassenmethode der aus "abc.ABCMeta" erstellten Instanzklasse implementiert. Wenn Sie "issubclass" aufrufen, fungiert sie als Hook.
Sie können auch ABCMeta.register verwenden, um virtuelle Unterklassen zu registrieren.
import abc
class RestAPIInterface(metaclass=abc.ABCMeta):
...
class UserList:
def get(self, id):
pass
RestAPIInterface.register(UserList)
print(issubclass(UserList, RestAPIInterface))
#Ausführungsergebnis: True
Es kann auch als Dekorateur verwendet werden.
import abc
class RestAPIInterface(metaclass=abc.ABCMeta):
...
@RestAPIInterface.register
class UserList:
def get(self, id):
pass
print(issubclass(UserList, RestAPIInterface))
Sie können das Cache-Token der aktuellen abstrakten Basisklasse auch mit abc.get_cache_token ()
abrufen. Dieses Token ändert sich jedes Mal, wenn "ABCMeta.register" ausgeführt wird, sodass es zur Überprüfung der Äquivalenz verwendet werden kann.
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}')
#Ausführungsergebnis: 36>>> 37
Da die Schnittstelle bisher eine virtuelle Basisklasse ist, gibt es keine Vererbungsbeziehung und die Einschränkungen für untergeordnete Klassen sind schwach. Wenn Sie keine bestimmte Schnittstelle implementieren, müssen Sie anstelle einer virtuellen Basisklasse eine abstrakte Basisklasse und eine abstrakte Methode zusammen verwenden, wenn Sie eine Funktion realisieren möchten, die einen Fehler verursacht.
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()
Ausführungsergebnis:
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
"Abc.abstractmethod" kann auch mit "classmethod", "staticmethod", "property" usw. verwendet werden.
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):
...
Durch Implementieren des Deskriptors __isabstractmethod__
, wenn er zusammen mit dem Deskriptor verwendet wird, der in Form eines Dekorators verwendet wird, wie in [5-4-4. Verwendung des Deskriptors] beschrieben (# 5-4-4-Verwendung des Deskriptors) , Abc.abstractmethod
kann zusammen verwendet werden.
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()
#Ausführungsergebnis:
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
Und abstrakte Python-Methoden sind nicht nur Schnittstellen, sie können auch mit "super" vererbt werden, um den Inhalt der Methode zu erhalten.
Die Standardbibliothek "collection.abc" bietet eine abstrakte Basisklasse für in Python integrierte Datenstrukturen (Container).
ABC | Klasse erben | Abstrakte Methode | Mischmethode |
---|---|---|---|
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 |
Von Sequence und append、reverse、extend、pop、remove、 __iadd__ |
ByteString | Sequence | __getitem__、 __len__ |
Von Sequence geerbte Methoden |
Set | Collection | __contains__、 __iter__、__len__ |
__le__、__lt__、__eq__、__ne__、__gt__、 __ge__、__and__、__or__、__sub__、__xor__、 isdisjoint |
MutableSet | Set | __contains__、 __iter__、__len__、 add、discard |
Von Set und clear geerbte Methoden, 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__ |
Von Mapping, Pop, geerbte Methoden 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__ |
(Siehe: collection.abc - Abstrakte Basisklassen für Container)
Die Verwendung entspricht einer normalen abstrakten Basisklasse.
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__Wird vererbt, kann der Produktsatz so berechnet werden, wie er ist
print(overlap)
#Ausführungsergebnis:['d', 'e', 'f']
Das Obige ist eine Implementierung eines listenbasierten Satzes. Ein normaler Satz von Python wird mithilfe einer Hash-Tabelle wie ein Wörterbuch implementiert, ist also in Bezug auf die Zeitberechnung schneller als eine Liste, hat jedoch eine etwas räumlichere Berechnung, sodass Sie mit dieser Implementierung den Speicherverbrauch sparen möchten. Du kannst es benutzen.
Aufzählungstyp ist eine endliche Menge von Variablen (formal Bezeichner). Es ist eine abstrakte Datenstruktur, die als gebündelt ist. Es hat nichts mit objektorientierter Programmierung zu tun. Beispielsweise unterstützt die prozessorientierte Programmiersprache C auch Aufzählungstypen. In objektorientierten Programmiersprachen wie Java und Python werden Aufzählungstypen jedoch in Form von Klassenobjekten implementiert.
Der Grund, warum Aufzählungstypen erforderlich sind, besteht darin, dass es in der realen Welt eine ganze Reihe von Daten gibt, die auf einen bestimmten endlichen Bereich beschränkt sind. Ein Tag besteht beispielsweise aus sieben Arten von endlichen Daten, die auf eine wöchentliche Basis beschränkt sind. Ebenso gibt es 12 Datentypen für jeden Monat. In der Software gibt es unzählige Sätze endlicher Zustände wie CSS-Farbnamen, HTTP-Codes, Boolesche Werte und Dateideskriptoren. Wenn Sie diese als Aufzählungen und nicht als unverständliche Zahlen ausdrücken, wird die Lesbarkeit des Codes verbessert und die Logik wird übersichtlicher.
Die Standard-Python-Bibliothek bietet ein Modul zum Erstellen eines Aufzählungstyps namens "enum". Lassen Sie uns zunächst die grundlegende Verwendung sehen.
import enum
class Color(enum.Enum):
red = 1
green = 2
blue = 3
print(Color.red)
#Ausführungsergebnis: Farbe.red
print(Color['red'])
#Ausführungsergebnis: Farbe.red
print(Color(1))
#Ausführungsergebnis: Farbe.red
print(Color.red.value)
#Ausführungsergebnis: 1
print(Color.red.name)
#Ausführungsergebnis: rot
for color in Color:
print(color)
#Ausführungsergebnis:
# Color.red
# Color.green
# Color.blue
Im Gegensatz zu Klassenvariablen ist der Aufzählungstyp ein iterierbares Objekt.
Color.red = 4
#Ausführungsergebnis:
# 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.
Mitglieder des Aufzählungstyps können nicht extern geändert werden.
print(Color.red is Color.green)
#Ausführungsergebnis: Falsch
print(Color.red == Color.green)
#Ausführungsergebnis: Falsch
red = Color.red
print(Color.red == red)
#Ausführungsergebnis: True
print(Color.red < Color.green)
#Ausführungsergebnis:
# 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'
Die Aufzählung "enum.Enum" unterstützt nur Übereinstimmungs- und Äquivalenzbewertungen zwischen Mitgliedern. Wenn Sie andere Werte vergleichen möchten, können Sie enum.IntEnum
verwenden.
import enum
class Color(enum.IntEnum):
red = 1
green = 2
blue = 3
purple = enum.auto() #automatische Wertsteigerung
print(Color.purple > Color.blue)
#Ausführungsergebnis: True
Sie können auch "enum.Flag" verwenden, wenn Sie die Kombination von Mitgliedern im Gebotsvorgang realisieren möchten.
import enum
class Color(enum.Flag):
red = enum.auto()
green = enum.auto()
blue = enum.auto()
purple = enum.auto()
print(Color.__members__)
#Ausführungsergebnis:
# {'red': <Color.red: 1>, 'green': <Color.green: 2>, 'blue': <Color.blue: 4>, 'purple': <Color.purple: 8>}
print(Color.purple | Color.blue)
#Ausführungsergebnis: Farbe.purple|blue
print(Color.purple | 2)
#Ausführungsergebnis:
# 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
unterstützt Gebotsoperationen zwischen Mitgliedern, kann jedoch nicht mit ganzzahligen Werten rechnen. Um dies zu erreichen, müssen Sie enum.IntFlag
verwenden.
import enum
class Color(enum.IntFlag):
red = enum.auto()
green = enum.auto()
blue = enum.auto()
purple = enum.auto()
print(Color.__members__)
#Ausführungsergebnis:
# {'red': <Color.red: 1>, 'green': <Color.green: 2>, 'blue': <Color.blue: 4>, 'purple': <Color.purple: 8>}
print(Color.purple | Color.blue)
#Ausführungsergebnis: Farbe.purple|blue
print(Color.purple | 2)
#Ausführungsergebnis: Farbe.purple|green
enum.IntFlag
behandelt Mitglieder als ganzzahlige Werte.
Schauen wir uns als nächstes den gewöhnlichen Aufzählungstyp "enum.Enum" genauer an.
import enum
class MessageResult(enum.Enum):
SUCCESS = 1
INVALID_MESSAGE = 2
INVALID_PARAMETER = 3
BAD_MESSAGE = 2
print(MessageResult(2))
#Ausführungsergebnis: MessageResult.INVALID_MESSAGE
class MessageResult(enum.Enum):
SUCCESS = 1
INVALID_MESSAGE = 2
INVALID_PARAMETER = 3
INVALID_MESSAGE = 4
#Ausführungsergebnis:
# 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'
Der Aufzählungstyp garantiert die Eindeutigkeit des Mitgliedsnamens, beschränkt ihn jedoch nicht auf den Wert.
import enum
@enum.unique
class MessageResult(enum.Enum):
SUCCESS = 1
INVALID_MESSAGE = 2
INVALID_PARAMETER = 3
BAD_MESSAGE = 2
#Ausführungsergebnis:
# 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
Das Anwenden von "enum.unique" auf eine Klasse als Dekorateur garantiert auch die Einzigartigkeit von "Wert".
import enum
MessageResult = enum.Enum(
value='MessageResult',
names=('SUCCESS INVALID_MESSAGE INVALID_PARAMETER'),
)
print(MessageResult.__members__)
#Ausführungsergebnis:
# {'SUCCESS': <MessageResult.SUCCESS: 1>, 'INVALID_MESSAGE': <MessageResult.INVALID_MESSAGE: 2>, 'INVALID_PARAMETER': <MessageResult.INVALID_PARAMETER: 3>}
Sie können Aufzählungstypen auch dynamisch mit der Functional API
anstelle einer harten Codierung erstellen. Wenn Sie eine durch Leerzeichen getrennte Zeichenfolge als Argument "names" übergeben, werden die Nummern automatisch zugewiesen.
import enum
MessageResult = enum.Enum(
value='MessageResult',
names=(('SUCCESS', 3),
('INVALID_MESSAGE', 2),
('INVALID_PARAMETER', 1))
)
print(MessageResult.__members__)
#Ausführungsergebnis:
# {'SUCCESS': <MessageResult.SUCCESS: 3>, 'INVALID_MESSAGE': <MessageResult.INVALID_MESSAGE: 2>, 'INVALID_PARAMETER': <MessageResult.INVALID_PARAMETER: 1>}
Sie können den Wert für jedes Mitglied angeben, indem Sie ein mehrschichtiges iterierbares Objekt als Argument für die Namen übergeben.
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)
#Ausführungsergebnis: Nachricht.DB_SAVE_SUCCESS
print(Message.DB_DELETE_SUCCESS.ok)
#Ausführungsergebnis: True
print(Message.DB_ITEM_NOT_FOUND.ok)
#Ausführungsergebnis: Falsch
Die Mitglieder des Aufzählungstyps können einen beliebigen Datentyp verwenden, nicht nur ganzzahlige Werte. Wenn Sie den Initialisierer "init" implementieren, werden die Elementwerte bei der Auswertung der Klasse an "init" übergeben. Und Sie können Tapples verwenden, um mehrere Variablen zu übergeben.
__Init__
kann jedoch keine Mitgliedswerte anpassen. Wenn Sie die Mitglieder anpassen möchten, müssen Sie den Konstruktor __new__
implementieren.
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)
#Ausführungsergebnis: P..Y 1 km
print(Coordinate.PY)
#Ausführungsergebnis: Koordinieren.PY
Das Obige ist ein Beispiel aus der offiziellen Dokumentation, bei der es sich um eine Aufzählung von Binärobjekten handelt, in der Mitgliedswerte und andere Informationen zusammen gespeichert werden.
Aufzählungstypen sind Klassen, sodass Sie Methoden oder Tänzer intern implementieren können. Aufzählungstypen unterscheiden sich jedoch stark von normalen Klassen. Zunächst werden Aufzählungstypen in speziellen Metaklassen implementiert, wobei Mitglieder (Klassenvariablen) Instanzen der Klasse sind. Daher arbeiten "new" und "init" während der Klassenbewertung und nicht zum Zeitpunkt der Instanziierung. Dann hat der Aufzählungstyp einige spezielle Attribute.
__members__
: Eine Zuordnung von member_name: member
, schreibgeschützt._name_
: Mitgliedsname_Value_
: Der Mitgliedswert __new__
kann festgelegt oder geändert werden._missing_
: Eine Suchfunktion, die verwendet wird, wenn ein Wert nicht gefunden wird und überschrieben werden kann._ignore_
: In einer Liste oder Zeichenfolge werden Klassenvariablen, die mit den darin enthaltenen Elementen übereinstimmen, nicht mehr in Mitglieder konvertiert._order_
: Ein Klassenattribut zum Beibehalten der Reihenfolge der Mitglieder. Wenn Sie beispielsweise mit` order = 'rot grün blau' 'definieren und Mitglieder in einer anderen Reihenfolge definieren, tritt ein Fehler auf._generate_next_value
: Wird für Functional API
und enum.auto
verwendet, um den entsprechenden Wert für ein Mitglied zu erhalten. Er kann überschrieben werden.Übrigens scheint das Attribut mit einem Unterstrich davor und danach "sunder" zu heißen.
Bisher haben wir den größten Teil der objektorientierten Programmierung von Python gesehen. Es ist nicht nur ein Problem mit Python, es ist ein Problem mit objektorientierten Programmiersprachen im Allgemeinen, aber die Klassendefinition ist sehr kompliziert. In Python ist in den meisten Fällen die Definition von "init" das Minimum, das beim Erstellen einer Klasse erforderlich ist. Möglicherweise müssen Sie auch andere spezielle Methoden implementieren. Darüber hinaus gibt es Zeiten, in denen Sie eine große Anzahl ähnlicher Klassen erstellen müssen. Eine solche Code-Redundanz wird als Boilerplate-Code (https://en.wikipedia.org/wiki/Boilerplate_code) bezeichnet.
Sie können types.SimpleNamespace
verwenden, um auf einfache Weise eine Klasse zu erstellen.
import types
bbox = types.SimpleNamespace(x=100, y=50, w=20, h=20)
print(bbox)
#Ausführungsergebnis: Namespace(h=20, w=20, x=100, y=50)
print(bbox==bbox)
#Ausführungsergebnis: True
Wenn Sie in Python dieselbe Funktion wie "types.SimpleNamespace" implementieren, lautet diese wie folgt.
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__
Sie haben "init", "repr" und "eq" implementiert, aber wenn Sie "hash" oder andere spezielle Methoden für Vergleichsoperationen implementieren möchten, ist dies ziemlich problematisch, und im Gegenteil, die Lesbarkeit des Codes kann abnehmen. Es gibt Sex. types.SimpleNamespace
dient nur zum Erstellen einfacher Klassen (Namespaces).
Einige Bibliotheksentwickler von Drittanbietern haben ein Problem mit dem Kesselplattencode festgestellt und ein Tool entwickelt, das die Klassendefinitionen vereinfacht. attrs ist einer von ihnen. Die grundlegende Verwendung ist wie folgt.
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)
#Ausführungsergebnis: Person(name='Mary', sex='F', age=18)
print(mary == mary)
#Ausführungsergebnis: True
attrs
kann in Form von Klassendekorateuren verwendet werden. Hier haben wir drei Instanzvariablen definiert und das Standardargument auf "Alter" gesetzt. attrs
definiert automatisch __init__
und __repr__
. Es definiert auch "eq", "ne", "lt", "__ le__", "gt" und "ge", und das Vergleichsziel ist ein Tapple von Instanzvariablen.
Ich werde nicht ins Detail gehen, aber attrs
ist ein fortgeschritteneres Tool als types.SimpleNamespace
, mit vielen Funktionen und einem leistungsstarken Tool für die objektorientierte Programmierung von Python.
Um den Code der Kesselplatte zu beseitigen, gibt es ein Modul namens Datenklassen, das aus Python 3.7 als offizieller Schritt eingeführt wurde. Dieses Modul bietet ähnliche Funktionen wie "attrs", z. B. die Generierung spezieller Methoden.
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') #Klassenvariable
@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)
#Ausführungsergebnis: S3Image(bucket='Image', key='ItemImage', img_id=1)
print(item_image_1 == item_image_1)
#Ausführungsergebnis: True
Datenklassen können Klassenmitgliedsvariablen in Form von Anmerkungen vom Typ Python definieren. Und es implementiert __init__
und __repr__
. Im Gegensatz zu "attrs" implementieren spezielle Methoden für Vergleichsoperationen standardmäßig nur "eq". Und die Methode, die Sie implementieren möchten, kann als Argument des Klassendekorators "Datenklasse" definiert werden. Der Standardwert lautet wie folgt.
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class C:
...
Die Rolle jedes Arguments ist fast selbsterklärend.
order
ist ein Flag zum automatischen Hinzufügen von __ne__
, __lt__
, __ le__
, __gt__
und __ge__
, die spezielle Methoden für andere Vergleichsoperationen als __eq__
sind. Das Argument eq
muss auch True sein, um True zu sein.unsafe_hash
ist ein Flag, das das Verhalten von __hash__
steuert. Wenn Sie "unsafe_hash = True" festlegen, wird automatisch die Methode "hash" hinzugefügt. Dies kann jedoch zu Problemen führen, wenn es sich nicht um ein unveränderliches Objekt handelt. Wenn sowohl "eq" als auch "eingefroren" True sind, wird standardmäßig die Methode "hash" automatisch hinzugefügt, sodass keine Flag-Steuerung erforderlich ist. Wenn "eq = True", "Frozen = False", erbt es von der übergeordneten Klasse "hash", und wenn "eq = False", "Frozen = True", wird es auf "hash = None" gesetzt. Außerdem kann "unsafe_hash = True" nicht mit "hash" definiert werden.eingefroren
ist ein Flag, das die Zuordnung von Werten zu Feldern (Mitgliedern) steuert. Wenn "True", blockieren Sie die Zuweisung und machen Sie sie schreibgeschützt.Darüber hinaus gibt es in der Datenklasse eine Funktion namens "Feld", und Sie können jedes Feld steuern.
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
verwendet die folgenden Argumente und gibt ein Field
-Objekt zurück.
default
ist ein Argument, das den Standardwert für das Feld angibt. default
und default_factory
können nicht koexistieren.default_factory
nimmt ein Objekt, das ohne Argumente aufgerufen werden kann, und stellt die Standardfactory für das Feld bereit. Das obige Beispiel enthält beispielsweise eine Liste, mit der die Standarddatenstruktur für das Feld erstellt wird.init
ist ein Flag dafür, ob __ init__
Felder enthält.repr
ist ein Flag dafür, ob __repr__
Felder enthält.compare
ist ein Flag, das Felder in spezielle Methoden für Vergleichsoperationen wie __eq__
und __gt__
einschließt.hash
ist ein Flag dafür, ob __hash__
Felder enthält. Wenn hash = None
, verwenden Sie compare
, um den Hashwert zu berechnen. Offiziell wird "Hash = Keine" empfohlen.Mit der Funktion Felder
können Sie alle Feldobjekte der Datenklasse in Form von Taples abrufen.
from dataclasses import fields
print(fields(c))
#Ausführungsergebnis:
# (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
und astuple
konvertieren eine Instanz einer Datenklasse in ein Wörterbuch und 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}]} #Rekursiv verarbeitet
Es gibt auch eine Funktion namens "make_dataclass", die dynamisch eine Datenklasse erstellt.
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})
#Gleich wie unten
@dataclass
class C:
x: int
y: 'typing.Any'
z: int = 5
def add_one(self):
return self.x + 1
Eine Funktion "Ersetzen" wird ebenfalls bereitgestellt, um eine Instanz der Datenklasse zu ändern und ein neues Objekt des gleichen Typs zu erstellen.
from dataclasses import dataclass, replace
@dataclass
class Point:
x: int
y: int
def __post_init__(self):
print('__post_init__')
p1 = Point(1, 2)
#Ausführungsergebnis:__post_init__
p2 = replace(p1, x=2)
#Ausführungsergebnis:__post_init__
print(p2)
#Ausführungsergebnis: Punkt(x=2, y=2)
replace
ruft __init __
auf, um eine neue Instanz zu erstellen. Wenn jedoch __post_init __
definiert ist, wird es nach __ init __
aufgerufen.
Zusätzlich wird eine Funktion "is_dataclass" bereitgestellt, um die Datenklasse zu bestimmen. True
wird nur zurückgegeben, wenn es für eine Datenklasse oder eine Instanz einer Datenklasse verwendet wird.
In der objektorientierten Geschichte haben wir jedes Detail der verwandten Konzepte von OOP gesehen, Pythons objektorientierte Programmierung. Die objektorientierte Programmierung ist jedoch so tiefgreifend, dass es noch viel zu lernen gibt. Diesmal konnte ich das Designmuster nicht im Detail erklären. Ich möchte es in einem anderen Artikel vorstellen.
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? Python-Typen und -Objekte
Recommended Posts