Vollständiges Verständnis der objektorientierten Programmierung von Python

Objektorientierung

1. Objektorientierter Ursprung

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.

2. Objektorientierte Merkmale

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:

Wenn es viele solche Prozesse gibt

Schauen wir uns als nächstes die Vorteile der Objektorientierung anhand von Python-Codebeispielen genauer an.

2-1. Vereinheitlichung und Verwaltung von Schnittstellen

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.

2-2. Kapselung

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.

2-3. Dynamischer Betrieb von Objekten

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.

3. Objektorientiertes Konzept

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.

3-1. Klasse

[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.

3-2. Instanz

[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.

3-3. Instanziierung

[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.

3-4 Instanzvariablen

[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.

3-5. Klassenvariablen

Klassenvariablen Eine von einer Klasse und ihren Instanzen gemeinsam genutzte Variable.

3-6. Methode

[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.

3-7 Statische Methode

[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.

3-8. Klassenmethode

[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.

3-9. Mitglieder

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).

3-10. Überschreiben

[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.

3-11 Kapselung

Kapselung ist Es bezieht sich auf das Gruppieren von Daten und Verarbeiten in einem Objekt, um eine Grenze zu erstellen.

3-12. Vererbung

[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.

3-13. Polymorphismus

[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).

3-14. Überlastung des Bedieners

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.

3-15. Abstraktion

[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.

3-16. Ententipp und Affenpflaster

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).

3-17-1. Prinzip der Einzelverantwortung

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.

3-17-2. Prinzip des Öffnens und Schließens

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.

3-17-3. Riskovs Ersatzprinzip

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.

3-17-4. Prinzip der Schnittstellentrennung

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. Screen Shot 2020-10-17 at 23.31.01.png (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. Screen Shot 2020-10-17 at 23.46.32.png Screen Shot 2020-10-17 at 23.46.55.png (Quelle: Agile Prinzipien, Muster und Praktiken in C #)

3-17-5. Prinzip der Abhängigkeitsumkehr

Das Prinzip der Abhängigkeitsumkehr beinhaltet zwei Regeln.

Dieses Prinzip dient zur Entkopplung zwischen Modulen. Das Folgende sind Beispiele. Screen Shot 2020-10-17 at 23.57.12.png (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. Screen Shot 2020-10-18 at 0.00.17.png (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.

3-18-1. Informationsexperte

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. Screen Shot 2020-10-18 at 21.30.32.png

3-18-2. Generator

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. Screen Shot 2020-10-18 at 21.32.06.png

3-18-3. Controller

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.

3-18-4. Lose Kupplung

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. Screen Shot 2020-10-18 at 21.33.12.png

3-18-5. Hohe Kohäsivität

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. Screen Shot 2020-10-18 at 21.33.42.png

3-18-6. Polymorphismus

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. Screen Shot 2020-10-18 at 21.44.21.png

3-18-7. Rein künstlich

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. Screen Shot 2020-10-18 at 22.07.19.png

3-18-8. Indirekt

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.

3-18-9 Schutz vor Schwankungen

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

3-19. Entwurfsmuster

[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.

4. Objektorientierte Python-Grundlagen

Ich erklärte das Konzept der Objektorientierung. Erstellen wir nun vier Klassen, um die Grundstruktur der objektorientierten Programmierung von Python zu sehen.

4-1. Klassenvariablen und Methoden

Python-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

4-2. Eigenschaften

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.

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

4-3. Vererbung / Überschreibung von privaten Variablen und Methoden

Definieren wir die Cat-Klasse und ihre untergeordnete Klasse BlackCat und betrachten die Vererbung / Überschreibung privater Variablen und Methoden.

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'

4-4. Vererbung von Klassenmitgliedern

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

** 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

5. Pythons objektorientierte Evolution

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.

5-1. Spezielle Methode

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.

5-2 Typen und Objekte

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.

Die Beziehung zwischen diesen beiden Typen ist in der folgenden Abbildung dargestellt.                                                   image.png

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. Screen Shot 2020-11-02 at 22.24.34.png

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.

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.

5-3. Metaklasse

5-3-1. Klasse ist ein Objekt

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

5-3-2. Dynamische Erstellung von Klassen

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, ), {})

5-3-3. Definition der Metaklasse

[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.                      image.png

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

5-3-4. Verwendung der Metaklasse

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).

5-4. Deskriptor

5-4-1. Grundlagen der Deskriptoren

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:

5-4-2. Nicht-Daten-Deskriptor und Daten-Deskriptor

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.

5-4-3. Mechanismus des Deskriptors

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.

5-4-4. Verwendung des Deskriptors

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).

5-4-4-2. Methode und Funktion

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.

5-4-4-3. Klassenmethode und statische Methode

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.

5-5 Abstrakte Basisklasse

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.

5-5-1. Schnittstelle

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.

5-5-1-1. Schnittstelle nach virtueller Basisklasse

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.

5-5-1-2. Schnittstelle zur abstrakten Basisklasse

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
5-5-1-2. Abstrakte Methode

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.

5-5-1-3. Container abstrakte Basisklasse

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.

5-6 Aufzählungstyp

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.

Übrigens scheint das Attribut mit einem Unterstrich davor und danach "sunder" zu heißen.

5-7 Datenklasse

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.

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.

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.

Zusammenfassung

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.

Referenz

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

Vollständiges Verständnis der objektorientierten Programmierung von Python
Vollständiges Verständnis der Funktion numpy.pad
Memo der kollektiven Wissensprogrammierung verstehen
Hinweis zur Verwendung der Python-Eingabefunktion
[Python] Ein grobes Verständnis des Protokollierungsmoduls
[Python] Ein grobes Verständnis von Iterablen, Iteratoren und Generatoren
[PyTorch] Ein wenig Verständnis von CrossEntropyLoss mit mathematischen Formeln
[Hinweis] Beginn der Programmierung
Rekrutierung von Programmiermeistern
Simulation von Comp Gacha
elasticsearch_dsl Memorandum
Erstellen Sie sofort ein Diagramm mit 2D-Daten mit der matplotlib von Python
[Für Anfänger] Eine Wortzusammenfassung der gängigen Programmiersprachen (Version 2018)