[PYTHON] Die Geschichte einer unveränderlichen Form

Einführung

Vorstellen

Das ist H1rono.

Was ist in diesem Artikel zu tun?

Ich habe einen unveränderlichen Typ mit reinem Python gemacht. Ich hoffe, ich kann Ihnen ausführlich sagen, was ich getan habe.

Was machst du

Machen Sie einen Vektortyp. Speziell,

Erstellen Sie eine Klasse mit solchen Anforderungen.

Schritt 1 - Unheilbar

Implementieren Sie zunächst die Anforderung "unveränderlich".

Was ist unveränderlich?

Unveränderliches Zitat aus Offizielles Glossar

immutable (Unveränderlich) Ein Objekt mit einem festen Wert. Unveränderliche Objekte sind Zahlen, Zeichenfolgen und Taples. Die Werte dieser Objekte können nicht geändert werden. Wenn Sie sich einen anderen Wert merken möchten, müssen Sie ein neues Objekt erstellen. Unveränderliche Objekte spielen eine wichtige Rolle in Situationen, in denen feste Hashwerte erforderlich sind. Ein Wörterbuchschlüssel ist ein Beispiel.

Mit anderen Worten, es scheint, dass Sie nicht "vector.direction = 256" sagen können.

Implementierung

Was großartig ist, ist das benannte Tupel in den integrierten Sammlungen Modul. Mit der Funktion 3 / library / collection.html # collection.namedtuple) können Sie einfach eine Unterklasse von "Tupel" (dh unveränderlich) mit einem diesem Element zugewiesenen Namen erstellen. Python ist unglaublich.

vec.py


from collections import namedtuple


_MyVector = namedtuple('_MyVector', ('length', 'direction'))
class MyVector(_MyVector):
    pass #Erfüllen Sie hier andere Anforderungen


if __name__ == '__main__':
    vec = MyVector(4, 2)
    print(vec) #MyVector(length=4, direction=2)

Ausführungsergebnis:

MyVector(length=4, direction=2)

Nun, unter den oben genannten Anforderungen,

Konnte vorerst umgesetzt werden. Die restlichen Anforderungen sind

ist. Es hat auf einmal abgenommen.

Schritt 2 - Eigenschaft erstellen

Das Folgende implementiert die Anforderung, dass "Koordinaten als Positionsvektor durch" Eigenschaft "mit den Namen" x "," y "referenziert werden können". Verwenden Sie das integrierte Modul math. Der Ausdruck für "x" ist "Länge * cos (pi * Richtung / 180)", und der Ausdruck für "y" ist "Länge * sin (pi * Richtung / 180)".

vec.py


from collections import namedtuple
from math import pi, cos, sin #Import hinzufügen


_MyVector = namedtuple('_MyVector', ('length', 'direction'))
class MyVector(_MyVector):
    #Zusätzlicher Teil von hier#
    @property
    def x(self):
        theta = pi * self.direction / 180
        return self.length * cos(theta)

    @property
    def y(self):
        theta = pi * self.direction / 180
        return self.length * sin(theta)

    #Zusätzlicher Teil bisher#


if __name__ == '__main__':
    from math import sqrt
    root2 = sqrt(2)
    vec = MyVector(root2, 45)
    print('x = {}'.format(vec.x)) #x = 1
    print('y = {}'.format(vec.y)) #y = 1

Wie Sie durch Ausführen des obigen Codes sehen können, ist das Ausführungsergebnis nicht so kommentiert. In meiner Umgebung

x = 1.0000000000000002
y = 1.0

Es wird angezeigt. Dieses Verhalten hängt wahrscheinlich mit hier (Gleitkomma-Arithmetik, ihre Probleme und Einschränkungen) zusammen. Überlegen. Wenn Sie interessiert sind, bitte. Jedenfalls vorerst

Konnte umgesetzt werden. Der Rest,

ist. Es ist alles eine arithmetische Implementierung.

Schritt 3 - Implementierung der Arithmetik

Wie?

Referenz: Offizielle Referenz (emuliert numerische Typen)

Die diesmal implementierte Entsprechung zwischen den Operatoren und Methodennamen entspricht in etwa der folgenden Tabelle.

Operator Methodenname
+ __add__, __radd__
- __sub__, __rsub__
* __mul__, __rmul__
/ __truediv__, __rtruediv__

Alle Funktionen haben (self, other) Argumente. Mit anderen Worten, wenn vector = Myvector (8, 128), ruftvector + x`` vector .__ add__ (x)auf und gibt das Ergebnis als Berechnungsergebnis zurück. Methoden mit "r" am Anfang, wie "radd", implementieren "was der Operator widerspiegelt (ersetzt)" (zitiert aus der obigen URL). Das heißt, als "x + vector" wird das Ergebnis von "vector .__ radd __ (x)" zurückgegeben, wenn "x .__ add__ (vector)" nicht implementiert "zurückgibt. Da es eine große Sache ist, werde ich auch diese hinzufügen. Die Implementierungsmethode ist wie folgt. Lass es uns schreiben. Ich möchte sagen, dass es bei der Implementierung von Addition und Subtraktion notwendig ist, den durch "(Länge, Richtung)" dargestellten Vektor aus den Koordinaten von "(x, y)" zu erhalten. Implementieren Sie zunächst diese Methode.

Implementierung (Richtung von Koordinaten, Größe)

vec.py


from collections import namedtuple
from math import pi, cos, sin, sqrt, acos #Import hinzufügen


_MyVector = namedtuple('_MyVector', ('length', 'direction'))
class MyVector(_MyVector):
    #Zusätzlicher Teil von hier#
    @classmethod
    def from_coordinates(cls, p1, p2=None):
        #p1: (x, y)
        #p2: (x, y)Oder keine
        if p2 is None:
            #Als Positionsvektor behandeln
            x, y = p1
        else:
            #Ab p1,Behandle p2 als einen Vektor von Endpunkten
            x = p2[0] - p1[0]
            y = p2[1] - p1[1]
        r = sqrt(x**2 + y**2)
        if r == 0:
            #0 Vektor
            return cls(0, 0)
        #Inverse Dreiecksfunktion
        theta = 180 * acos(x / r) / pi
        if y < 0:
            # 180 < direction < 360
            #theta=32 -> direction=328
            #theta=128 -> direction=232
            theta = 360 - theta
        return cls(r, theta)

    #Zusätzlicher Teil bisher#

    @property
    def x(self):
        theta = pi * self.direction / 180
        return self.length * cos(theta)

    @property
    def y(self):
        theta = pi * self.direction / 180
        return self.length * sin(theta)


if __name__ == '__main__':
    vec = MyVector.from_coordinates((2, 2))
    print('x = {}'.format(vec.x)) #x = 2
    print('y = {}'.format(vec.y)) #y = 2

Die Funktion from_coordinates wurde hinzugefügt. Wie der Name schon sagt, wird aus den Koordinaten ein "MyVector" -Objekt erstellt. Ich habe die Klassenmethode gewählt, weil ich sie sehr praktisch fand. Ausführungsergebnis:

x = 1.9999999999999996
y = 2.0000000000000004

Bis später ... Sie können die Funktion "Runden" hinzufügen und später runden. Wie auch immer, ich konnte den durch "(Länge, Richtung)" dargestellten Vektor aus den Koordinaten von "(x, y)" erhalten.

Implementierung (Addition, Subtraktion)

Als nächstes werden wir Addition und Subtraktion implementieren. Da das unterstützte Berechnungsziel ein Vektor des gleichen Typs ist, ist die Bedingung für die Bestimmung, dass es sich um einen Vektor handelt, "die Länge ist 2, und sowohl das erste als auch das zweite Element sind reelle Zahlen". Diese Bestimmung wird später erneut verwendet, machen Sie sie also zu einer Funktion.

vec.py


from collections import namedtuple
from math import pi, cos, sin, sqrt, acos
from numbers import Real #Import hinzufügen


_MyVector = namedtuple('_MyVector', ('length', 'direction'))
class MyVector(_MyVector):
    #Zusätzlicher Teil von hier#
    def __add__(self, other):
        #Implementierung der Addition
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((self.x + v.x, self.y + v.y))
        else:
            return NotImplemented

    #Die sogenannte reflektierende Version des Zusatzes
    __radd__ = __add__

    def __sub__(self, other):
        #Implementierung der Subtraktion
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((self.x - v.x, self.y - v.y))
        else:
            return NotImplemented

    def __rsub__(self, other):
        #Reflektierende Version der Subtraktion
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((v.x - self.x, v.y - self.y))
        else:
            return NotImplemented

    @staticmethod
    def isvector(obj):
        #Funktion zum Bestimmen, ob es sich um einen Vektor handelt
        try:
            return (
                isinstance(obj[1], Real)
                and isinstance(obj[0], Real)
                and len(obj) == 2)
        except:
            return False

    #Zusätzlicher Teil bisher#

    @classmethod
    def from_coordinates(cls, p1, p2=None):
        if p2 is None:
            x, y = p1
        else:
            x = p2[0] - p1[0]
            y = p2[1] - p1[1]
        r = sqrt(x**2 + y**2)
        if r == 0:
            return cls(0, 0)
        theta = 180 * acos(x / r) / pi
        if y < 0:
            theta = 360 - theta
        return cls(r, theta)

    @property
    def x(self):
        theta = pi * self.direction / 180
        return self.length * cos(theta)

    @property
    def y(self):
        theta = pi * self.direction / 180
        return self.length * sin(theta)


if __name__ == '__main__':
    vec1 = MyVector.from_coordinates((1, 1))
    vec2 = MyVector.from_coordinates((-1, -1))
    print(vec1 + vec2) #MyVector(length=0, direction=0)
    print(vec1 - vec2) #MyVector(length=2.8284...[2√2], direction=45)

Die Real-Klasse des integrierten Moduls numbers ist eine echte Basisklasse. "isinstance (1024, Real)", "isinstance (1.024, Real)" usw. geben alle "True" zurück. Wir setzen __radd__ = __add__, weil vector + x und x + vector den gleichen Wert haben. Übersetzt wie Javascript sieht es aus wie this .__ radd__ = this .__ add__. Ausführungsergebnis:

MyVector(length=4.965068306494546e-16, direction=153.43494882292202)
MyVector(length=2.8284271247461903, direction=45.00000000000001)

Ich weiß nicht, ob es passt. Verwenden Sie die Funktion "Runden", um die Anzeige zu erleichtern.

Umsetzung (Runde)

vec.py


from collections import namedtuple
from math import pi, cos, sin, sqrt, acos
from numbers import Real


_MyVector = namedtuple('_MyVector', ('length', 'direction'))
class MyVector(_MyVector):
    def __add__(self, other):
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((self.x + v.x, self.y + v.y))
        else:
            return NotImplemented

    __radd__ = __add__

    def __sub__(self, other):
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((self.x - v.x, self.y - v.y))
        else:
            return NotImplemented

    def __rsub__(self, other):
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((v.x - self.x, v.y - self.y))
        else:
            return NotImplemented

    @classmethod
    def from_coordinates(cls, p1, p2=None):
        if p2 is None:
            x, y = p1
        else:
            x = p2[0] - p1[0]
            y = p2[1] - p1[1]
        #Hier
        r = round(sqrt(x  ** 2 + y ** 2), 3)
        if r == 0:
            return cls(0, 0)
        #Hier
        theta = round(180 * acos(x / r) / pi)
        if y < 0:
            theta = 360 - theta
        return cls(r, theta)

    @property
    def x(self):
        theta = pi * self.direction / 180
        #Hier
        return round(self.length * cos(theta), 3)

    @property
    def y(self):
        theta = pi * self.direction / 180
        #Hier
        return round(self.length * sin(theta), 3)


if __name__ == '__main__':
    vec1 = MyVector.from_coordinates((1, 1))
    vec2 = MyVector.from_coordinates((-1, -1))
    print(vec1 + vec2) #MyVector(length=0, direction=0)
    print(vec1 - vec2) #MyVector(length=2.828, direction=45)

Ausführungsergebnis:

MyVector(length=0, direction=0)
MyVector(length=2.828, direction=45)

Es ist ein gutes Gefühl. Übrigens wird die "Richtung" des Additionsergebnisses von "153,43 ..." auf "0" geändert, weil es in "if r == 0:" in "from_coordinates" abgefangen wurde. Wie erwartet. Vorerst

Konnte umgesetzt werden. Die restlichen Anforderungen sind

ist. Als nächstes werden wir die Multiplikation implementieren.

Implementierung (Multiplikation)

Die Multiplikation ist eine Voraussetzung dafür, dass sich das Verhalten je nach Berechnungsziel ändert. Für reelle Zahlen und für Vektoren. Sie können die zuvor verwendete Klasse "Real" verwenden, um die reelle Zahl zu bestimmen. Die Funktion "isvector" ist nützlich zum Bestimmen von Vektoren.

vec.py


from collections import namedtuple
from math import pi, cos, sin, sqrt, acos
from numbers import Real


_MyVector = namedtuple('_MyVector', ('length', 'direction'))
class MyVector(_MyVector):
    def __add__(self, other):
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((self.x + v.x, self.y + v.y))
        else:
            return NotImplemented

    __radd__ = __add__

    def __sub__(self, other):
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((self.x - v.x, self.y - v.y))
        else:
            return NotImplemented

    def __rsub__(self, other):
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((v.x - self.x, v.y - self.y))
        else:
            return NotImplemented

    #Zusätzlicher Teil von hier#
    def __mul__(self, other):
        cls = self.__class__
        if isinstance(other, Real):
            #Für reelle Zahlen
            l = round(self.length * other, 3)
            return cls(l, self.direction)
        elif cls.isvector(other):
            #Für Vektor
            theta = pi * (other[1] - self[1]) / 180
            product = self[0] * other[0] * cos(theta)
            return round(product, 3)
        else:
            #Andernfalls
            return NotImplemented

    __rmul__ = __mul__

    #Zusätzlicher Teil bisher#

    @staticmethod
    def isvector(obj):
        try:
            return (
                isinstance(obj[1], Real)
                and isinstance(obj[0], Real)
                and len(obj) == 2)
        except:
            return False

    @classmethod
    def from_coordinates(cls, p1, p2=None):
        if p2 is None:
            x, y = p1
        else:
            x = p2[0] - p1[0]
            y = p2[1] - p1[1]
        r = round(sqrt(x**2 + y**2), 3)
        if r == 0:
            return cls(0, 0)
        theta = round(180 * acos(x / r) / pi)
        if y < 0:
            theta = 360 - theta
        return cls(r, theta)

    @property
    def x(self):
        theta = pi * self.direction / 180
        return round(self.length * cos(theta), 3)

    @property
    def y(self):
        theta = pi * self.direction / 180
        return round(self.length * sin(theta), 3)


if __name__ == '__main__':
    vec1 = MyVector(2, 45)
    vec2 = MyVector(2, 135)
    print(vec1 * 2) #MyVector(length=4, direction=45)
    print(vec1 * vec2) #0

Ausführungsergebnis:

MyVector(length=4, direction=45)
0.0

mit diesem,

Konnte umgesetzt werden. Die restlichen Anforderungen sind

ist. Einer noch!

Implementierung (Division)

Wir werden es genauso implementieren wie die Multiplikation. Ich habe es beim Schreiben bemerkt, aber wenn Sie denken, dass die reelle Zahl und die Größe des Vektors in der Multiplikation und Division der reellen Zahl und des Vektors multipliziert und dividiert werden, dann "vector1 / vector2 = vector1 * (1 / vector2)" und der Vektor Es scheint, dass Sie auch eine Trennung untereinander implementieren können. Ich werde das auch hinzufügen.

vec.py


from collections import namedtuple
from math import pi, cos, sin, sqrt, acos
from numbers import Real


_MyVector = namedtuple('_MyVector', ('length', 'direction'))
class MyVector(_MyVector):
    def __add__(self, other):
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((self.x + v.x, self.y + v.y))
        else:
            return NotImplemented

    __radd__ = __add__

    def __sub__(self, other):
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((self.x - v.x, self.y - v.y))
        else:
            return NotImplemented

    def __rsub__(self, other):
        if self.isvector(other):
            cls = self.__class__
            v = cls(*other)
            return cls.from_coordinates((v.x - self.x, v.y - self.y))
        else:
            return NotImplemented

    def __mul__(self, other):
        cls = self.__class__
        if isinstance(other, Real):
            l = round(self.length * other, 3)
            return cls(l, self.direction)
        elif cls.isvector(other):
            theta = pi * (other[1] - self[1]) / 180
            product = self[0] * other[0] * cos(theta)
            return round(product, 3)
        else:
            return NotImplemented

    __rmul__ = __mul__

    #Zusätzlicher Teil von hier#
    def __truediv__(self, other):
        cls = self.__class__
        if isinstance(other, Real):
            l = round(self.length / other, 3)
            return cls(l, self.direction)
        elif cls.isvector(other):
            return self * (1 / cls(*other))
        else:
            return NotImplemented

    #Reflektierende Version der Teilung
    def __rtruediv__(self, other):
        cls = self.__class__
        if isinstance(other, Real):
            l = round(other / self.length, 3)
            return cls(l, self.direction)
        elif cls.isvector(other):
            return other * (1 / self)
        else:
            return NotImplemented

    #Zusätzlicher Teil bisher#

    @staticmethod
    def isvector(obj):
        try:
            return (
                isinstance(obj[1], Real)
                and isinstance(obj[0], Real)
                and len(obj) == 2)
        except:
            return False

    @classmethod
    def from_coordinates(cls, p1, p2=None):
        if p2 is None:
            x, y = p1
        else:
            x = p2[0] - p1[0]
            y = p2[1] - p1[1]
        r = round(sqrt(x**2 + y**2), 3)
        if r == 0:
            return cls(0, 0)
        theta = round(180 * acos(x / r) / pi)
        if y < 0:
            theta = 360 - theta
        return cls(r, theta)

    @property
    def x(self):
        theta = pi * self.direction / 180
        return round(self.length * cos(theta), 3)

    @property
    def y(self):
        theta = pi * self.direction / 180
        return round(self.length * sin(theta), 3)


if __name__ == '__main__':
    vec1 = MyVector(2, 45)
    print(vec1 / 2) #MyVector(length=1, direction=45)
    print(vec1 / vec1) #1

Ausführungsergebnis:

MyVector(length=1.0, direction=45)
1.0

Es ging gut. Jetzt sind alle Anforderungen erfüllt!

Schritt 4 - Probieren Sie es aus

>>> vec1 = MyVector(2, 45)
>>> vec2 = MyVector(2, 135)
>>> vec3 = MyVector(2, 225)
>>> vec1.direction = 256
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: can`t set attribute
>>> vec1 + vec3
MyVector(length=0, direction=0)
>>> vec1 - vec3
MyVector(length=3.999, direction=45)
>>> vec1 * vec2
0.0
>>> vec1 * (2, 135)
0.0
>>> vec1 * 2
MyVector(length=4, direction=45)
>>> vec1 / 2
MyVector(length=1.0, direction=45)
>>> vec1 += vec3
>>> vec1
MyVector(length=0, direction=0)

Nur das Ergebnis von vec1 --vec3 ist etwas seltsam, aber ungefähr wie beabsichtigt.

Am Ende

Ich habe einen unveränderlichen Vektortyp in reinem Python erstellt. Es ist hier nicht seltsam, wenn Sie hier nicht verstehen, sagen Sie mir bitte etwas. Die Wertabweichung war unterwegs spürbar, aber wenn Sie interessiert sind, versuchen Sie es mit hier (eingebautes Dezimalmodul) usw. Wie ist das?

Bonus

`(x, y)` Vektortyp

vec2.py


from math import pi, sqrt, acos
from numbers import Real
from operator import itemgetter


class MyVector2(tuple):
    _ROUND_DIGIT = 2

    __slots__ = ()

    def __new__(cls, x=0, y=0):
        return tuple.__new__(cls, (x, y))

    def __repr__(self):
        t = (self.__class__.__name__, *self)
        return ('{0}(x={1}, y={2})'.format(*t))

    __bool__ = (lambda self: bool(self.length))

    def __add__(self, other):
        cls = self.__class__
        if cls.isvector(other):
            return cls(self[0] + other[0], self[1] + other[1])
        else:
            return NotImplemented

    def __sub__(self, other):
        cls = self.__class__
        if cls.isvector(other):
            return cls(self[0] - other[0], self[1] - other[1])
        else:
            return NotImplemented

    def __mul__(self, other):
        cls = self.__class__
        if cls.isvector(other):
            return self[0]*other[0] + self[1]*other[1]
        elif isinstance(other, Real):
            return cls(self.x * other, self.y * other)
        else:
            return NotImplemented

    def __truediv__(self, other):
        cls = self.__class__
        if cls.isvector(other):
            v = cls(*other)
            return self * (1 / v)
        elif isinstance(other, Real):
            return cls(self.x / other, self.y / other)
        else:
            return NotImplemented

    __radd__ = __add__
    __rmul__ = __mul__

    def __rsub__(self, other):
        return -(self - other)

    def __rtruediv__(self, other):
        cls = self.__class__
        if isinstance(other, Real):
            return cls(other / self.x, other / self.y)
        elif cls.isvector(other):
            return other * (1 / self)
        else:
            return NotImplemented

    __abs__ = (lambda self: abs(self.length))
    __pos__ = (lambda self: self)

    def __neg__(self):
        x, y = -self.x, -self.y
        return self.__class__(x, y)

    def __complex__(self):
        return self.x + self.y * 1j

    @staticmethod
    def isvector(obj):
        try:
            return (
                isinstance(obj[1], Real)
                and isinstance(obj[0], Real)
                and len(obj) == 2)
        except:
            return False

    @classmethod
    def _round(cls, a):
        return round(a, cls._ROUND_DIGIT)

    x = property(itemgetter(0))
    y = property(itemgetter(1))

    @property
    def length(self):
        r = sqrt(self.x**2 + self.y**2)
        return self._round(r)

    @property
    def direction(self):
        r = self.length
        if r == 0:
            return 0
        x, y = self
        theta = self._round(180 * acos(x / r) / pi)
        if y < 0:
            theta = 360 - theta
        return theta

Ich werde es versuchen.

>>> v1 = MyVector2(1, 1)
>>> v2 = MyVector2(-1, -1)
>>> v1 + v2
MyVector2(x=0, y=0)
>>> v1 - v2
MyVector2(x=2, y=2)
>>> v1 * v2
-2
>>> v1 / v2
-2.0
>>> v1.direction
44.83
>>> v1.length
1.41
>>> v2.direction
224.83
>>> v1 / (-1, -1)
-2.0
>>> v1 + (1, 1)
MyVector2(x=2, y=2)
>>> (1, 1) + v1
MyVector2(x=2, y=2)
>>> v1 - (1, 1)
MyVector2(x=0, y=0)
>>> (1, 1) - v1
MyVector2(x=0, y=0)
>>> v1 * 2
MyVector2(x=2, y=2)
>>> v1 / 2
MyVector2(x=0.5, y=0.5)
Führen Sie ABC ein, um den Vektor `(Länge, Richtung)` und den Vektor `(x, y)` kompatibel zu machen

vector.py


from operator import itemgetter
from math import pi, cos, sin, sqrt, acos
from numbers import Real
import abc


def isvector(obj):
    try:
        return (
            isinstance(obj[0], Real)
            and isinstance(obj[1], Real)
            and	len(obj) == 2)
    except:
        return False


def empty(*args, **kwargs): pass


class MyABCMeta(abc.ABCMeta):
    def __new__(cls, name, bases=(), namespace={}, ab_attrs=(), **kwargs):
        namespace.update(cls.__prepare__(name, bases, ab_attrs))
        return super().__new__(cls, name, bases, namespace, **kwargs)

    @classmethod
    def __prepare__(cls, name, bases, ab_attrs=()):
        result = {
            name: abc.abstractmethod(empty) \
                for name in ab_attrs
        }
        return result


class ABVector(
        tuple,
        metaclass=MyABCMeta,
        ab_attrs=(
            'x', 'y', 'length', 'direction', #properties
            'from_xy', 'from_ld', #converter [argments: (cls, vector: tuple)]
            '__repr__', '__bool__', '__abs__',
            '__pos__', '__neg__', '__complex__',
            '__eq__', '__add__', '__radd__',
            '__sub__', '__rsub__','__mul__',
            '__rmul__', '__truediv__', '__rtruediv__'
        )
    ):
    __slots__ = ()

    ROUND_DIGIT = 3

    @classmethod
    def round(cls, a):
        return round(a, cls.ROUND_DIGIT)

    @classmethod
    def __instancecheck__(cls, instance):
        return all(map(
            (lambda string: hasattr(instance, string)),
            ('x', 'y', 'length', 'direction')
        ))

    @classmethod
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)


class XYVector(ABVector):
    _fields_ = ('x', 'y')
    
    def __new__(cls, x=0, y=0):
        t = (cls.round(x), cls.round(y))
        return super().__new__(cls, t)

    def __repr__(self):
        t = (self.__class__.__name__, *self)
        return f'{self.__class__.__name__}(x={self.x}, y={self.y})'

    def __bool__(self):
        return bool(self.length)

    def __abs__(self):
        return abs(self.length)

    __pos__ = lambda self: self

    def __neg__(self):
        t = (-self.x, -self.y)
        return self.__class__(*t)

    def __complex__(self):
        return self.x + self.y * 1j

    def __eq__(self, other):
        try:
            return (
                self.x == other.x
                and self.y == other.y
            )
        except:
            return False

    # operation implementation: start #

    template = """
    def __{name}__(self, other):
        cls = self.__class__
        if isinstance(other, ABVector):
            x = {0} self.x {1} other.x
            y = {0} self.y {1} other.y
            return cls(x, y)
        elif isvector(other):
            vec = cls(*other)
            x = {0} self.x {1} vec.x
            y = {0} self.y {1} vec.y
            return cls(x, y)
        else:
            return NotImplemented
    """.replace(f'\n{" " * 4}', '\n')

    order = (
        (('+', '+'), {'name': 'add'}),
        (('+', '+'), {'name': 'radd'}),
        (('+', '-'), {'name': 'sub'}),
        (('-', '+'), {'name': 'rsub'}),
    )
    for t, d in order:
        exec(template.format(*t, **d))

    template = """
    def __{name}__(self, other):
        cls = self.__class__
        if isinstance(other, Real):
            x = cls.round({0} self.x {1} other)
            y = cls.round({0} self.y {1} other)
            return cls(x, y)
        elif isinstance(other, ABVector):
            product = (self.x * other.x + self.y * other.y) / {2}
            return cls.round(product)
        elif isvector(other):
            other = cls(*other)
            product = (self.x * other.x + self.y * other.y) / {2}
            return cls.round(product)
        else:
            return NotImplemented
    """.replace(f'\n{" " * 4}', '\n')

    order = (
        (('', '*', 1), {'name': 'mul'}),
        (('', '*', 1), {'name': 'rmul'}),
        (('', '/', '(other.x ** 2 + other.y ** 2)'), {'name': 'truediv'}),
        (('1 /', '*', '(self.x ** 2 + self.y ** 2)'), {'name': 'rtruediv'}),
    )
    for t, d in order:
        exec(template.format(*t, **d))

    del template, order, t, d

    # operation implementation: end #

    @classmethod
    def from_xy(cls, vector):
        return cls(*vector)

    @classmethod
    def from_ld(cls, vector):
        l, d = vector
        t = pi * d / 180
        x = l * cos(t)
        y = l * sin(t)
        return cls(x, y)

    x = property(itemgetter(0))
    y = property(itemgetter(1))

    @property
    def length(self):
        r = sqrt(self.x ** 2 + self.y ** 2)
        return self.__class__.round(r)

    @property
    def direction(self):
        r = self.length
        if r == 0:
            return 0
        x, y = self
        theta = round(180 * acos(x / r) / pi)
        if y < 0:
            theta = 360 - theta
        return theta


class LDVector(ABVector):
    _fields_ = ('length', 'direction')

    def __new__(cls, length=0, direction=0):
        t = (cls.round(length), round(direction))
        return super().__new__(cls, t)

    def __repr__(self):
        return f'{self.__class__.__name__}(length={self.length}, direction={self.direction})'

    def __bool__(self):
        return bool(self.length)

    def __abs__(self):
        return abs(self.length)

    __pos__ = lambda self: self

    def __neg__(self):
        d = self.direction + 180
        if d <= 360:
            d -= 360
        return self.__class__(self.length, d)

    def __complex__(self):
        return self.x + self.y * 1j

    def __eq__(self, other):
        try:
            return (
                self.length == other.length
                and self.direction == other.direction
            )
        except:
            return False

    # operation implementation: start #

    template = """
    def __{name}__(self, other):
        cls = self.__class__
        if isinstance(other, ABVector):
            x = {0} self.x {1} other.x
            y = {0} self.y {1} other.y
            return cls.from_xy((x, y))
        elif isvector(other):
            vec = cls(*other)
            x = {0} self.x {1} vec.x
            y = {0} self.y {1} vec.y
            return cls.from_xy((x, y))
        else:
            return NotImplemented
    """.replace(f'\n{" " * 4}', '\n')

    order = (
        (('+', '+'), {'name': 'add'}),
        (('+', '+'), {'name': 'radd'}),
        (('+', '-'), {'name': 'sub'}),
        (('-', '+'), {'name': 'rsub'}),
    )
    for t, d in order:
        exec(template.format(*t, **d))

    template = """
    def __{name}__(self, other):
        cls = self.__class__
        if isinstance(other, Real):
            l = cls.round({0} self.length {1} other)
            return cls(l, self.direction)
        elif isinstance(other, ABVector):
            t = pi * (self.direction - other.direction) / 180
            product = (self.length * other.length * cos(t)) / {2}
            return cls.round(product)
        elif isvector(other):
            other = cls(*other)
            t = pi * (self.direction - other.direction) / 180
            product = (self.length * other.length * cos(t)) / {2}
            return cls.round(product)
        else:
            return NotImplemented
    """.replace(f'\n{" " * 4}', '\n')

    order = (
        (('1 *', '*', '1'), {'name': 'mul'}),
        (('1 *', '*', '1'), {'name': 'rmul'}),
        (('1 *', '/', '(other.length ** 2)'), {'name': 'truediv'}),
        (('1 /', '*', '(self.length ** 2)'), {'name': 'rtruediv'}),
    )
    for t, d in order:
        exec(template.format(*t, **d))

    del template, order, t, d

    # operation implementation: end #

    @classmethod
    def from_ld(cls, vector):
        return cls(*vector)

    @classmethod
    def from_xy(cls, vector):
        x, y = vector
        l = sqrt(x ** 2 + y ** 2)
        if l == 0:
            return cls(0, 0)
        d = round(180 * acos(x / l) / pi)
        if y < 0:
            d = 360 - d
        return cls(l, d)

    length = property(itemgetter(0))
    direction = property(itemgetter(1))

    @property
    def x(self):
        d = pi * self.direction / 180
        return self.__class__.round(self.length * cos(d))

    @property
    def y(self):
        d = pi * self.direction / 180
        return self.__class__.round(self.length * sin(d))


XYVector.to_ld = (lambda self: LDVector.from_xy(self))
XYVector.to_xy = (lambda self: XYVector.from_xy(self))
LDVector.to_xy = (lambda self: XYVector.from_ld(self))
LDVector.to_ld = (lambda self: LDVector.from_ld(self))


if __name__ == "__main__":
    xyv1 = XYVector(1, 1)
    xyv2 = XYVector(-1, 1)
    ldv1 = LDVector.from_xy((-1, -1))
    ldv2 = LDVector.from_xy((1, -1))
    codes = ('xyv1', 'xyv2', 'ldv1', 'ldv2')
    tmpls = (
        'xyv1 {0} xyv2', 'xyv1 {0} ldv1', 'xyv1 {0} ldv2',
        'xyv2 {0} xyv1', 'xyv2 {0} ldv1', 'xyv2 {0} ldv2',
        'ldv1 {0} xyv1', 'ldv1 {0} xyv2', 'ldv1 {0} ldv2',
        'ldv2 {0} xyv1', 'ldv2 {0} xyv2', 'ldv2 {0} ldv1',
    )
    order = tuple('+-*/')
    for e in order:
        codes += tuple(map(
            (lambda tmpl: tmpl.format(*e)),
            tmpls
        ))
    for code in codes:
        print(f'{code} = {eval(code)}')

Ausführungsergebnis:

xyv1 = XYVector(x=1, y=1)
xyv2 = XYVector(x=-1, y=1)
ldv1 = LDVector(length=1.414, direction=225)
ldv2 = LDVector(length=1.414, direction=315)
xyv1 + xyv2 = XYVector(x=0, y=2)
xyv1 + ldv1 = XYVector(x=0.0, y=0.0)
xyv1 + ldv2 = XYVector(x=2.0, y=0.0)
xyv2 + xyv1 = XYVector(x=0, y=2)
xyv2 + ldv1 = XYVector(x=-2.0, y=0.0)
xyv2 + ldv2 = XYVector(x=0.0, y=0.0)
ldv1 + xyv1 = LDVector(length=0, direction=0)
ldv1 + xyv2 = LDVector(length=2.0, direction=180)
ldv1 + ldv2 = LDVector(length=2.0, direction=270)
ldv2 + xyv1 = LDVector(length=2.0, direction=0)
ldv2 + xyv2 = LDVector(length=0, direction=0)
ldv2 + ldv1 = LDVector(length=2.0, direction=270)
xyv1 - xyv2 = XYVector(x=2, y=0)
xyv1 - ldv1 = XYVector(x=2.0, y=2.0)
xyv1 - ldv2 = XYVector(x=0.0, y=2.0)
xyv2 - xyv1 = XYVector(x=-2, y=0)
xyv2 - ldv1 = XYVector(x=0.0, y=2.0)
xyv2 - ldv2 = XYVector(x=-2.0, y=2.0)
ldv1 - xyv1 = LDVector(length=2.828, direction=225)
ldv1 - xyv2 = LDVector(length=2.0, direction=270)
ldv1 - ldv2 = LDVector(length=2.0, direction=180)
ldv2 - xyv1 = LDVector(length=2.0, direction=270)
ldv2 - xyv2 = LDVector(length=2.828, direction=315)
ldv2 - ldv1 = LDVector(length=2.0, direction=0)
xyv1 * xyv2 = 0.0
xyv1 * ldv1 = -2.0
xyv1 * ldv2 = 0.0
xyv2 * xyv1 = 0.0
xyv2 * ldv1 = 0.0
xyv2 * ldv2 = -2.0
ldv1 * xyv1 = -1.999
ldv1 * xyv2 = 0.0
ldv1 * ldv2 = 0.0
ldv2 * xyv1 = -0.0
ldv2 * xyv2 = -1.999
ldv2 * ldv1 = 0.0
xyv1 / xyv2 = 0.0
xyv1 / ldv1 = -1.0
xyv1 / ldv2 = 0.0
xyv2 / xyv1 = 0.0
xyv2 / ldv1 = 0.0
xyv2 / ldv2 = -1.0
ldv1 / xyv1 = -1.0
ldv1 / xyv2 = 0.0
ldv1 / ldv2 = 0.0
ldv2 / xyv1 = -0.0
ldv2 / xyv2 = -1.0
ldv2 / ldv1 = 0.0

Recommended Posts

Die Geschichte einer unveränderlichen Form
Die Geschichte eines Fehlers in PyOCR
[Pythonista] Die Geschichte einer Aktion zum Kopieren ausgewählten Textes
Die Geschichte von sys.path.append ()
Die Geschichte der Herstellung des Mel Icon Generator Version 2
Die Geschichte eines Mel-Icon-Generators
Die Geschichte des Baus von Zabbix 4.4
Die Geschichte eines neuronalen Netzwerks der Musikgeneration
Die Geschichte, wie man mit discord.py einen Fragenkasten-Bot erstellt
Die Geschichte von Python und die Geschichte von NaN
Die Geschichte der Teilnahme an AtCoder
Ruft die Attribute eines Objekts ab
Die Geschichte des "Lochs" in der Akte
Die Geschichte des erneuten Bereitstellens des Anwendungsservers
Die Geschichte des Exportierens eines Programms
Die Geschichte, einen Standardtreiber für db mit Python zu erstellen.
Die Geschichte, ein Modul zu erstellen, das E-Mails mit Python überspringt
Die Geschichte des Versuchs, den Client wieder zu verbinden
Die Geschichte, MeCab in Ubuntu 16.04 zu setzen
Die Geschichte der Manipulation globaler Python-Variablen
Die Geschichte, deep3d auszuprobieren und zu verlieren
Dekodierung von Keras 'LSTM model.predict
Die Geschichte der Verarbeitung A von Blackjack (Python)
Die Geschichte von pep8 wechselt zu pycodestyle
Machen Sie den Standardwert des Arguments unveränderlich
Die Geschichte einer Soundkamera mit Touch Designer und ReSpeaker
Sprechen Sie über die Fluchtwahrscheinlichkeit eines zufälligen Gehens auf einem ganzzahligen Gitter
Die Geschichte, ein Paket zu erstellen, das den Betrieb von Juman (Juman ++) & KNP beschleunigt
Achten Sie beim Erstellen einer Bildmaske mit Numpy auf den Typ
Die Geschichte, ein Tool zum Laden von Bildern mit Python zu erstellen ⇒ Speichern unter
Die Geschichte des tiefen Lernens mit TPU
Die Geschichte, dass die Lernkosten von Python niedrig sind
Die Geschichte einer Box, die Peppers AL Memory und MQTT miteinander verbindet
Die Geschichte der Erstellung einer Webanwendung, die umfangreiche Lesungen mit Django aufzeichnet
Bildverarbeitung? Die Geschichte, Python für zu starten
Die Geschichte, das optimale n in N Faust zu finden
Die Geschichte des Fehlinterpretierens der Swap-Zeile des obersten Befehls
Die Geschichte des Lesens von HSPICE-Daten in Python
Die Geschichte, Sourcetrail × macOS × VS Code auszuprobieren
Die Geschichte der Anzeige von Mediendateien in Django
Die Geschichte, einen Line Bot zu erstellen, der uns den Zeitplan für die Wettbewerbsprogrammierung erzählt
[Kleine Geschichte] Laden Sie das Bild von Ghibli sofort herunter
Die Geschichte von soracom_exporter (Ich habe versucht, SORACOM Air mit Prometheus zu überwachen)
Die Geschichte vom Umzug von Pipenv zur Poesie
Die Geschichte des Starts eines Minecraft-Servers von Discord
Eine Geschichte, die den Aufwand für Betrieb / Wartung reduziert
Die Geschichte von Python ohne Inkrement- und Dekrementoperatoren.
Die Geschichte des Stoppens des Produktionsdienstes mit dem Befehl hostname
Eine Einführung in die neuesten Technologien-AI, ML, DL
Die Geschichte des Aufbaus der schnellsten Linux-Umgebung der Welt
Die Geschichte der durch gcrypto20 verursachten Nichtübereinstimmung von Hash Sum
Die Geschichte des Teilens der Pyenv-Umgebung mit mehreren Benutzern
Die Geschichte von FileNotFound im Python open () -Modus = 'w'
Machen Sie den Standardwert des Arguments unveränderlich (Artikelerklärung)
[Golang] Geben Sie ein Array für den Wert der Karte an
Eine Geschichte über die Änderung des Master-Namens von BlueZ