[PYTHON] L'histoire de la fabrication d'un moule immuable

introduction

Auto-introduction

C'est H1rono.

Que faire dans cet article

J'ai créé un type immuable (immuable) avec du python pur. J'espère pouvoir vous dire en détail ce que j'ai fait.

Tu fais quoi

Créez un type de vecteur. Plus précisément,

Créez une classe avec de telles exigences.

Étape 1-Immurable

Tout d'abord, mettez en œuvre l'exigence «immuable».

Qu'est-ce qui est immuable?

Citation immuable du Glossaire officiel

immutable (Immuable) Un objet avec une valeur fixe. Les objets immuables incluent les nombres, les chaînes et les taples. Les valeurs de ces objets ne peuvent pas être modifiées. Si vous voulez vous souvenir d'une autre valeur, vous devez créer un nouvel objet. Les objets immuables jouent un rôle important dans les situations où des valeurs de hachage fixes sont requises. Une clé de dictionnaire est un exemple.

En d'autres termes, il semble que vous ne puissiez pas dire «vector.direction = 256».

la mise en oeuvre

Ce qui est génial, c'est le tuple nommé dans le module intégré collections. Avec la fonction 3 / library / collections.html # collections.namedtuple), vous pouvez facilement créer une classe avec un nom attribué à cet élément dans une sous-classe de tuple (c'est-à-dire immuable). python est incroyable.

vec.py


from collections import namedtuple


_MyVector = namedtuple('_MyVector', ('length', 'direction'))
class MyVector(_MyVector):
    pass #Répondez aux autres exigences ici


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

Résultat de l'exécution:

MyVector(length=4, direction=2)

Maintenant, parmi les exigences mentionnées ci-dessus,

A pu être mis en œuvre pour le moment. Les autres exigences sont

est. Il a diminué d'un seul coup.

Étape 2-Créer une propriété

Ce qui suit implémente l'exigence selon laquelle «les coordonnées en tant que vecteur de position peuvent être référencées par une propriété nommée« x »,« y »». Utilisez le module intégré math. L'expression de «x» est «length * cos (pi * direction / 180)», et l'expression de «y» est «length * sin (pi * direction / 180)».

vec.py


from collections import namedtuple
from math import pi, cos, sin #Ajouter une importation


_MyVector = namedtuple('_MyVector', ('length', 'direction'))
class MyVector(_MyVector):
    #Partie supplémentaire d'ici#
    @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)

    #Partie supplémentaire jusqu'à présent#


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

Comme vous pouvez le voir en exécutant le code ci-dessus, le résultat de l'exécution n'est pas aussi commenté. Dans mon environnement,

x = 1.0000000000000002
y = 1.0

Il sera affiché. Ce comportement est probablement lié à ici (l'arithmétique en virgule flottante, ses problèmes et ses limitations). pense. Si vous êtes intéressé, s'il vous plaît. Bref, pour le moment

A pu être mis en œuvre. Le reste,

est. Tout est une implémentation arithmétique.

Étape 3-Implémentation de l'arithmétique

Comment?

Référence: Référence officielle (émule les types numériques)

La correspondance entre les opérateurs et les noms de méthode implémentés cette fois est à peu près comme indiqué dans le tableau suivant.

opérateur Nom de la méthode
+ __add__, __radd__
- __sub__, __rsub__
* __mul__, __rmul__
/ __truediv__, __rtruediv__

Toutes les fonctions ont des arguments (self, other). En d'autres termes, lorsque vector = Myvector (8, 128), vector + x appelle vector .__ add__ (x) et renvoie le résultat comme résultat du calcul. Les méthodes avec r au début, telles que __radd__, implémentent" ce que l'opérateur est reflété (remplacé) "(cité à partir de l'URL ci-dessus). Autrement dit, comme «x + vector», lorsque «x .__ add__ (vector)« retourne »Non implémenté», le résultat de «vector .__ radd __ (x)» est renvoyé. Comme c'est un gros problème, j'ajouterai celui-ci également. La méthode de mise en œuvre est comme ça. Écrivons-le. Je voudrais dire que, dans la mise en œuvre de l'addition et de la soustraction, il est nécessaire d'obtenir le vecteur représenté par (longueur, direction) à partir des coordonnées de (x, y). Tout d'abord, implémentez cette méthode.

Mise en œuvre (direction à partir des coordonnées, taille)

vec.py


from collections import namedtuple
from math import pi, cos, sin, sqrt, acos #Ajouter une importation


_MyVector = namedtuple('_MyVector', ('length', 'direction'))
class MyVector(_MyVector):
    #Partie supplémentaire d'ici#
    @classmethod
    def from_coordinates(cls, p1, p2=None):
        #p1: (x, y)
        #p2: (x, y)Ou aucun
        if p2 is None:
            #Traiter comme un vecteur de position
            x, y = p1
        else:
            #À partir de p1,Traitez p2 comme un vecteur de points finaux
            x = p2[0] - p1[0]
            y = p2[1] - p1[1]
        r = sqrt(x**2 + y**2)
        if r == 0:
            #0 vecteur
            return cls(0, 0)
        #Fonction de triangle inversé
        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)

    #Partie supplémentaire jusqu'à présent#

    @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

Ajout de la fonction from_coordinates. Comme son nom l'indique, il crée un objet MyVector à partir des coordonnées. Je l'ai choisi comme méthode de classe car je l'ai trouvé très pratique. Résultat de l'exécution:

x = 1.9999999999999996
y = 2.0000000000000004

A plus tard ... Vous pouvez ajouter la fonction round et l'arrondir plus tard. Quoi qu'il en soit, j'ai pu obtenir le vecteur représenté par (longueur, direction) à partir des coordonnées de (x, y).

Implémentation (addition, soustraction)

Ensuite, nous implémenterons l'addition et la soustraction. Puisque la cible de calcul prise en charge est un vecteur du même type, la condition pour déterminer qu'il s'agit d'un vecteur est "la longueur est de 2, et les premier et second éléments sont des nombres réels". Cette détermination sera réutilisée plus tard, alors faites-en une fonction.

vec.py


from collections import namedtuple
from math import pi, cos, sin, sqrt, acos
from numbers import Real #Ajouter une importation


_MyVector = namedtuple('_MyVector', ('length', 'direction'))
class MyVector(_MyVector):
    #Partie supplémentaire d'ici#
    def __add__(self, other):
        #Mise en œuvre de l'ajout
        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

    #La version dite réfléchissante de l'addition
    __radd__ = __add__

    def __sub__(self, other):
        #Mise en œuvre de la soustraction
        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):
        #Version réfléchissante de la soustraction
        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):
        #Fonction pour déterminer s'il s'agit d'un vecteur
        try:
            return (
                isinstance(obj[1], Real)
                and isinstance(obj[0], Real)
                and len(obj) == 2)
        except:
            return False

    #Partie supplémentaire jusqu'à présent#

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

La classe Real du module intégré numbers est une vraie classe de base. ʻIsinstance (1024, Real) , ʻisinstance (1.024, Real), etc. retournent tous True. Nous définissons «radd = add» car «vector + x» et «x + vector» ont la même valeur. Traduit comme javascript, il ressemble à this .__ radd__ = this .__ add__. Résultat de l'exécution:

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

Je ne sais pas si ça va. Utilisez la fonction «round» pour l'arrondir pour une meilleure visualisation.

Mise en œuvre (ronde)

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]
        #ici
        r = round(sqrt(x  ** 2 + y ** 2), 3)
        if r == 0:
            return cls(0, 0)
        #ici
        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
        #ici
        return round(self.length * cos(theta), 3)

    @property
    def y(self):
        theta = pi * self.direction / 180
        #ici
        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)

Résultat de l'exécution:

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

C'est un sentiment agréable. Au fait, la "direction" du résultat de l'addition passe de "153.43 ..." à "0", car elle a été interceptée dans "if r == 0:" in "from_coordinates". Comme prévu. Pour le moment

A pu être mis en œuvre. Les autres exigences sont

est. Ensuite, nous mettrons en œuvre la multiplication.

Mise en œuvre (multiplication)

La multiplication est une exigence que le comportement change en fonction de la cible de calcul. Pour les nombres réels et pour les vecteurs. Vous pouvez utiliser la classe Real utilisée précédemment pour déterminer le nombre réel. La fonction «est un vecteur» est utile pour déterminer les vecteurs.

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

    #Partie supplémentaire d'ici#
    def __mul__(self, other):
        cls = self.__class__
        if isinstance(other, Real):
            #Pour des nombres réels
            l = round(self.length * other, 3)
            return cls(l, self.direction)
        elif cls.isvector(other):
            #Pour le vecteur
            theta = pi * (other[1] - self[1]) / 180
            product = self[0] * other[0] * cos(theta)
            return round(product, 3)
        else:
            #Autrement
            return NotImplemented

    __rmul__ = __mul__

    #Partie supplémentaire jusqu'à présent#

    @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

Résultat de l'exécution:

MyVector(length=4, direction=45)
0.0

avec ça,

A pu être mis en œuvre. Les autres exigences sont

est. Un de plus!

Mise en œuvre (division)

Nous le mettrons en œuvre de la même manière que la multiplication. Comme je l'ai écrit, j'ai remarqué qu'en pensant que le nombre réel et la magnitude du vecteur sont multipliés et divisés dans la multiplication et la division du nombre réel et du vecteur, le vecteur devient vecteur1 / vecteur2 = vecteur1 * (1 / vecteur2). Il semble que vous puissiez également mettre en œuvre une division entre eux. J'ajouterai cela aussi.

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__

    #Partie supplémentaire d'ici#
    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

    #Version réfléchissante de la division
    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

    #Partie supplémentaire jusqu'à présent#

    @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

Résultat de l'exécution:

MyVector(length=1.0, direction=45)
1.0

Ça s'est bien passé. Maintenant, toutes les exigences sont implémentées!

Étape 4-Essayez-le

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

Seul le résultat de vec1 --vec3 est un peu étrange, mais à peu près comme prévu.

À la fin

J'ai créé un type de vecteur immuable en python pur. Ce n'est pas étrange ici, si vous ne comprenez pas ici, dites-moi quelque chose. L'écart de valeur était perceptible en cours de route, mais si vous êtes intéressé, essayez d'utiliser here (module intégré décimal) etc. Comment c'est?

prime

`(x, y)` type de vecteur

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

J'essaierai de l'utiliser.

>>> 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)
Introduisez ABC pour rendre le vecteur `(longueur, direction)` et le vecteur `(x, y)` compatibles

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)}')

Résultat de l'exécution:

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

L'histoire de la fabrication d'un moule immuable
L'histoire d'une erreur dans PyOCR
[Pythonista] L'histoire de la réalisation d'une action pour copier le texte sélectionné
L'histoire de sys.path.append ()
L'histoire de la création du Mel Icon Generator version 2
L'histoire de la création d'un générateur d'icônes mel
L'histoire de la construction de Zabbix 4.4
L'histoire de la création d'un réseau neuronal de génération musicale
L'histoire de la création d'un bot de boîte à questions avec discord.py
L'histoire de Python et l'histoire de NaN
L'histoire de la participation à AtCoder
Obtenir les attributs d'un objet
L'histoire du "trou" dans le fichier
L'histoire du remontage du serveur d'application
L'histoire de l'exportation d'un programme
L'histoire de la création d'un pilote standard pour db avec python.
L'histoire de la création d'un module qui ignore le courrier avec python
L'histoire d'essayer de reconnecter le client
L'histoire de la mise en place de MeCab dans Ubuntu 16.04
L'histoire de la manipulation des variables globales Python
L'histoire d'essayer deep3d et de perdre
Décodage du modèle LSTM de Keras.
L'histoire du traitement A du blackjack (python)
L'histoire du changement de pep8 en pycodestyle
Rendre la valeur par défaut de l'argument immuable
L'histoire de la création d'une caméra sonore avec Touch Designer et ReSpeaker
Parlez de la probabilité d'évasion d'une marche aléatoire sur une grille entière
L'histoire de la création d'un package qui accélère le fonctionnement de Juman (Juman ++) & KNP
Faites attention au type lorsque vous créez un masque d'image avec Numpy
L'histoire de la création d'un outil pour charger une image avec Python ⇒ l'enregistrer sous un autre nom
L'histoire de l'apprentissage profond avec TPU
L'histoire selon laquelle le coût d'apprentissage de Python est faible
L'histoire de la fabrication d'une boîte qui interconnecte la mémoire AL de Pepper et MQTT
L'histoire de la création d'une application Web qui enregistre des lectures approfondies avec Django
Traitement d'image? L'histoire du démarrage de Python pour
L'histoire de la recherche du n optimal dans N poing
L'histoire de la mauvaise lecture de la ligne d'échange de la commande supérieure
L'histoire de la lecture des données HSPICE en Python
L'histoire d'essayer Sourcetrail × macOS × VS Code
L'histoire de l'affichage des fichiers multimédias dans Django
L'histoire de la création d'un Line Bot qui nous raconte le calendrier de la programmation du concours
[Petite histoire] Téléchargez l'image de Ghibli immédiatement
L'histoire de la fabrication de soracom_exporter (j'ai essayé de surveiller SORACOM Air avec Prometheus)
Histoire de passer de Pipenv à la poésie
L'histoire du lancement d'un serveur Minecraft depuis Discord
Une histoire qui réduit l'effort de fonctionnement / maintenance
L'histoire de Python sans opérateurs d'incrémentation et de décrémentation.
L'histoire de l'arrêt du service de production avec la commande hostname
Une introduction des technologies de pointe-AI, ML, DL
L'histoire de la création de l'environnement Linux le plus rapide au monde
L'histoire de l'inadéquation de Hash Sum causée par gcrypto20
L'histoire du partage de l'environnement pyenv avec plusieurs utilisateurs
L'histoire de FileNotFound en Python open () mode = 'w'
Rendre la valeur par défaut de l'argument immuable (explication de l'article)
[Golang] Spécifiez un tableau pour la valeur de la carte
Une histoire sur le changement du nom principal de BlueZ