Python 3.5 führt die Funktion ** Typhinweis ** ein, die Sie auf Typen aufmerksam macht. Sie sehen jedoch selten Code mit Typhinweisen. Zuerst war ich skeptisch, dass "Python statisch eingegeben werden muss?", Aber als ich meinem Code einen Tipphinweis hinzufügte, "[oh my (py)!](Http: // blog) .zulip.org / 2016/10/13 / static-types-in-python-oh-mypy /) ", und ich wollte es verbreiten, also habe ich beschlossen, diesen Artikel zu schreiben.
Von nun an werden wir die Körpererweiterung in Python mithilfe von Typhinweisen in drei Teilen implementieren. Zur Implementierung habe ich auf den Artikel "Einführung in die Algebra mit Swift" von taketo1024 verwiesen.
Übrigens ist der Autor mit der Erweiterung der Algebra nicht vertraut (so viel ich gelesen habe "Mathematics Girl / Galois Theory" von Hiroshi Yuki), also wenn es irgendwelche Fehler gibt I würde es schätzen wenn du könntest.
Die Algebra-Erweiterung ist im Artikel von taketo1024 und auf anderen Webseiten sorgfältig beschrieben. Lassen Sie uns daher in diesem Artikel nicht viel auf mathematische Themen eingehen, sondern uns auf die Vorgehensweise mit Python konzentrieren. Ich denke.
Eine der Grundlagen für Typhinweise ist das Schreiben des Codes ** Funktionsanmerkung (PEP 3107) **. Dies wird verwendet, um die Argumente und Rückgabewerte einer Funktion zu erläutern.
Das Folgende ist ein Beispiel für eine Anmerkung aus PEP 3107.
def compile(source: "something compilable",
filename: "where the compilable thing comes from",
mode: "is this a single statement or a suite?"):
Der Inhalt der Funktionsanmerkung wirkt sich nicht auf das Ausführungsergebnis des Programms aus. Es wird jedoch versucht, die Funktionsanmerkung zu verwenden, um den Code offline zu überprüfen.
Da PEP 3107 den Inhalt der Anmerkung nicht speziell spezifiziert, können verschiedene Anmerkungsstile aufgrund des Inspektionswerkzeugs verstreut sein. Dies würde jedoch die Vorteile für den Programmierer halbieren, also ab Python 3.5 ** Typhinweise (PEP 484) ** Ein standardisiertes Framework namens, wurde eingeführt, und ein zugehöriges Typisierungsmodul wird bereitgestellt.
Das Folgende ist ein Beispiel für eine Anmerkung aus PEP 484. Geben Sie anstelle der Beschreibung den Namen der Klasse oder des Typs in die Variable oder Zeichenfolge ein.
from typing import List
Vector = List[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
Ich werde es noch einmal schreiben, aber das Verhalten des Programms ändert sich nicht abhängig vom Inhalt der Annotation, und die Annotationsmethode wird nicht erzwungen. Es ist auch nicht immer erforderlich, diese Anmerkungsmethode zu befolgen. Die Standardisierung vereinheitlicht jedoch die Regeln, und wir können eine Zusammenarbeit zwischen Programmierern erwarten.
Nur weil ein Typ-Hinweis-Framework vorhanden ist, bedeutet dies nicht, dass Inspektionstools als Teil von Python bereitgestellt werden. Sie müssen also eines auswählen. In diesem Artikel ist sich PEP 484 sehr bewusst und verwendet ** mypy **, den aktuellen De-facto-Standard.
Sie können mypy mit dem Befehl pip wie folgt installieren (beachten Sie, dass es sich um mypy-lang handelt, nicht um mypy).
$ sudo pip3 install mypy-lang
Die tatsächliche Verwendung erfolgt später in diesem Artikel.
Jetzt machen wir eine "Zahl".
Erstens ist das Monoid. Ein Satz, der die folgenden Bedingungen erfüllt, wird als "Monoid" bezeichnet.
Wegen Binäroperation $ \ star $ geschlossen
- Das Gesetz der Kombination gilt ($ x \ Stern (y \ Stern z) = (x \ Stern y) \ Stern z $)
- Es gibt ein Einheitselement ($ e $ existiert so, dass $ x \ star e = e \ star x = x $)
Beispielsweise sind natürliche Zahlen, reelle Zahlen, quadratische Matrizen usw. Monoide für "Produkt".
Um dies in Python auszudrücken, erstellen Sie zunächst eine ** abstrakte Basisklasse ** für Monoide. Fügen Sie metaclass = ABCMeta
hinzu, um es zu einer abstrakten Basisklasse zu machen.
from abc import ABCMeta, abstractmethod
class Monoid(metaclass=ABCMeta):
@abstractmethod
def __mul__(self, x):
pass
@abstractmethod
def identity(self):
pass
@abstractmethod
def __eq__(self, x):
pass
def testAssociativity(self, a, b):
return (self * a) * b == self * (a * b)
def testIdentity(self):
return self * self.identity() == self
Da alle Monoide mindestens "Binäroperation $ \ star $", "Einheitselement" und "gleich" haben sollen, deklariert die abstrakte Basisklasse eine leere Methode mit einem Dekorator "@ abstractmethod". Ich werde. Wenn ** diese drei Methoden nicht in der Klasse definiert sind, die Group
erbt, tritt daher während der Instanziierung ** ein Fehler auf.
Die Operation $ \ star $ ersetzt den Python-Operator *
. Um den Operator "*" für eine Klasse zu definieren, definieren Sie die Methode "mul". Instanzen der Klasse, in der dieser Operator definiert ist, können zum Erstellen von Ausdrücken verwendet werden, indem andere Objekte mit dem Operator *
verbunden werden. Zum Beispiel:
class A(object):
def __init__(self, data):
self.data
def __mul__(self, x):
return self.data * x.data
a = A(5)
b = A(10)
print((a * b).data) # -> 50
Bei der Auswertung eines Ausdrucks werden die verbundenen Objekte in "mul" "x" zugewiesen und berechnet, und der Rückgabewert ist das Auswertungsergebnis des Ausdrucks. __eq__
entspricht der Operation ==
.
Dies allein bedeutet jedoch nicht, dass das Monoid für die Operation $ \ star $ ** geschlossen ** ist. Hier kommt der ** Typ-Hinweis ** ins Spiel. Typhinweise können die Typen von ** Funktionsargumenten und Rückgabewerten ** darstellen. Schauen wir uns ein Beispiel mit Typhinweisen an.
from typing import TypeVar
MonoidType = TypeVar("MonoidType", bound="Monoid")
class Monoid(metaclass=ABCMeta):
@abstractmethod
def __mul__(self: MonoidType, x: MonoidType) -> MonoidType:
pass
@abstractmethod
def identity(self: MonoidType) -> MonoidType:
pass
@abstractmethod
def __eq__(self, x):
pass
def testAssociativity(self: MonoidType, a: MonoidType, b: MonoidType) -> bool:
return (self * a) * b == self * (a * b)
def testIdentity(self: MonoidType) -> bool:
return self * self.identity() == self
Auf jedes Argument der Funktion folgen ein :
und eine Typensignatur. Es gibt auch eine Unterschrift am Ende der def
-Anweisung nach dem->
. Hier ist ein Tipp.
Mithilfe von Typhinweisen können Sie Funktionsargumenten, Rückgabewerten und anderen Variablen Typinformationen geben. Diese werden beim Ausführen des Programms wie Kommentare behandelt, sind jedoch nützliche Informationen bei Verwendung der Typprüfung.
An jeder Stelle wird eine Variable namens "MonoidType" deklariert und verwendet. Dies wird als ** Typvariable ** bezeichnet, und Variablen, die mit derselben Typvariablen versehen sind, müssen vom selben Typ sein.
In der Deklaration von "MonoidType" wird "bound =" Monoid "verwendet. Auf diese Weise können Variablen mit "MonoidType" auf Instanzen von Unterklassen von "Monoid" beschränkt werden.
Hier wird kein Typhinweis zu __eq__
gegeben. __eq__
ist in object
definiert, der Basis aller Klassen. Wenn Sie einen anderen Typhinweis geben, tritt ein Fehler auf.
Eine "Gruppe" ist ein Monoid plus die umgekehrte Bedingung.
Wegen Binäroperation $ \ star $ geschlossen
- Das Gesetz der Kombination gilt ($ x \ Stern (y \ Stern z) = (x \ Stern y) \ Stern z $)
- Es gibt ein Einheitselement ($ e $ existiert so, dass $ x \ star e = e \ star x = x $)
- Es gibt ein inverses Element ($ x ^ {-1} $ existiert so, dass $ x \ star x ^ {-1} = x ^ {-1} \ star x = e $)
Die abstrakte Basisklasse der Gruppe erbt das Monoid, und das inverse Element wird durch die Methode "inverse" definiert. Neben der Definition des inversen Elements empfiehlt es sich, auch hier die Divisionsdefinition "truediv" zu verwenden.
GroupType = TypeVar("GroupType", bound="Group")
class Group(Monoid, metaclass=ABCMeta):
@abstractmethod
def inverse(self: GroupType) -> GroupType:
pass
def __truediv__(self: GroupType, x: GroupType) -> GroupType:
return self * x.inverse()
def testInverse(self: GroupType) -> bool:
return self * self.inverse() == self.identity()
Insbesondere wird eine Menge, die eine Gruppe für eine Operation namens "Addition" bildet, als "Additionsgruppe" bezeichnet. Da die additive Gruppe die Machbarkeit voraussetzt, stellen wir vier Bedingungen wie folgt ein.
Die Operation $ \ oplus $ ist geschlossen.
- Das Kombinationsgesetz gilt ($ x \ oplus (y \ oplus z) = (x \ oplus y) \ oplus z $)
- Ein Einheitselement existiert ($ 0 $ existiert so, dass $ x \ oplus 0 = 0 \ oplus x = x $, Nullelement)
- Inverses Element existiert ($ -x $ existiert so, dass $ x \ oplus (-x) = (-x) \ oplus x = 0 $, minus Element)
- Cabrio ($ x \ oplus y = y \ oplus x $)
Ähnlich wie die Gruppe, aber das Codebeispiel lautet wie folgt. Anstatt die Division in Gruppen zu definieren, definieren Sie die Subtraktion.
AdditiveGroupType = TypeVar("AdditiveGroupType", bound="AdditiveGroup")
class AdditiveGroup(metaclass=ABCMeta):
@abstractmethod
def __add__(self: AdditiveGroupType, x: AdditiveGroupType) -> AdditiveGroupType:
pass
@abstractmethod
def zero(self: AdditiveGroupType) -> AdditiveGroupType:
pass
@abstractmethod
def __neg__(self: AdditiveGroupType) -> AdditiveGroupType:
pass
def __sub__(self: AdditiveGroupType, x: AdditiveGroupType) -> AdditiveGroupType:
return self + (-x)
@abstractmethod
def __eq__(self, x):
pass
def testAdditiveAssociativity(self: AdditiveGroupType, a: AdditiveGroupType, b: AdditiveGroupType) -> bool:
return (self + a) + b == self + (a + b)
def testZero(self: AdditiveGroupType) -> bool:
return self + self.zero() == self
def testNegative(self: AdditiveGroupType) -> bool:
return self + (-self) == self.zero()
def testAbelian(self: AdditiveGroupType, a: AdditiveGroupType) -> bool:
return self + a == a + self
Wenn ich "self + (-x)" betrachte, merke ich noch einmal, dass ich eine Operation definiere.
Die Gruppen- und Monoid-Bedingungen für nur eine Operation. Der "Ring" definiert die Bedingungen für die beiden Operationen.
Die als Multiplikation bezeichnete Operation $ \ star $ und die als Addition bezeichnete Operation $ \ oplus $ werden geschlossen.
- Monoid für die Multiplikation $ \ star $
- Informationen zur additiven $ \ oplus $ Abel-Gruppe
- Das Verteilungsgesetz gilt zwischen Multiplikation und Addition ($ x \ Stern (y \ oplus z) = x \ Stern y \ oplus x \ Stern z $)
Erben Sie beim Definieren einer Ringklasse einfach die monoiden und additiven Klassen und implementieren Sie den Verteilungsgesetz-Testcode.
RingType = TypeVar("RingType", bound="Ring")
class Ring(Monoid, AdditiveGroup):
def testDistributive(self: RingType, a: RingType, b: RingType) -> bool:
return self * (a + b) == self * a + self * b
Sie können endlich einen Integer-Ring implementieren. Erben Sie die abstrakte Basisklasse "Ring" des Rings und überschreiben Sie die in "@ abstractmethod" deklarierte Methode. Definieren Sie außerdem die Methode __repr__
, damit der Inhalt der Klasse Integer
auf der Python-Konsole angezeigt werden kann.
Wenn es wahr ist, wäre es schön, wenn wir "int" erweitern könnten, indem wir nur "identity" und "zero" schreiben, aber leider kann Python das nicht, also werden wir jede Methode stetig definieren.
class Integer(Ring):
def __init__(self: "Integer", v: int) -> None:
self.v = v
def __repr__(self: "Integer") -> str:
return str(self.v)
def __mul__(self: "Integer", x: "Integer") -> "Integer":
return Integer(self.v * x.v)
def __add__(self: "Integer", x: "Integer") -> "Integer":
return Integer(self.v + x.v)
def __neg__(self: "Integer") -> "Integer":
return Integer(-self.v)
def __eq__(self, x):
if not isinstance(x, Integer):
return NotImplemented
return self.v == x.v
def identity(self: "Integer") -> "Integer":
return Integer(1)
def zero(self: "Integer") -> "Integer":
return Integer(0)
Z = Integer
Verwenden wir nun Z
als Alias für Integer
.
Jede Methode gibt den Typ "Integer" als Zeichenfolge an. Dies ist eine Vorwärtsreferenz, da die Klasse "Integer" zum Zeitpunkt der Methodendefinition noch nicht definiert wurde. Verwenden Sie Zeichenfolgenliterale anstelle von Symbolen, wenn Sie mit Typhinweisen vorwärts referenzieren.
Lassen Sie uns nach der Implementierung der Ganzzahl den Code mit mypy überprüfen. Geben Sie zur Überprüfung einfach den Dateinamen im Befehl mypy an und führen Sie ihn aus.
$ mypy ./field_extension.py
Wenn keine Probleme vorliegen, wird kein Fehler angezeigt. Versuchen wir nun, die Methode mul aus der Klasse Integer zu entfernen und zu überprüfen.
$ mypy ./field_extension.py
field_extension.py:174: error: Cannot instantiate abstract class 'Integer' with abstract attribute '__mul__'
...
Ringe, für die keine Multiplikation definiert ist, führen zu einem Fehler. Dieser Fehler tritt auf, wenn Sie eine Instanz der Klasse "Integer" erstellen, ohne mypy zu verwenden.
Als nächstes ändern wir den Argumenttyp der Methode mul von Integer in int.
def __mul__(self: "Integer", x: int) -> "Integer":
return Integer(self.v * x)
Wenn Sie mypy in diesem Status ausführen, wird eine Fehlermeldung angezeigt, dass die Typen nicht übereinstimmen.
$ mypy ./field_extension.py
field_extension.py: note: In class "Integer":
field_extension.py:91: error: Argument 1 of "__mul__" incompatible with supertype "Monoid"
Zu diesem Zeitpunkt haben wir die Monoide, Gruppen und Ringe abstrahiert und den ganzzahligen Ring erfolgreich implementiert.
Das nächste Mal, nachdem ich den Körper berührt habe, plane ich, rationale Zahlenfelder, Restringe und endliche Körper zu erstellen.
Recommended Posts