建立一种新的数据类型

建立一种新的数据类型

我们都知道在 Python 中有各式和各样的数据类型, 比如 int, float, 在语言的定义下, 我们很容易作一些动作, 比如加减乘除, 但是一旦我们有新的数据类型, 这些动作我们就用不了, 还要定义各种函数来使用.

现在本文就是要教你怎么定义一个新的数据类型, 而且还要能使用 Python 的语法来作一些基本的动作, 这里以二维座标点为例, 我们都知道一个座标点是 P(x, y), x, y 为整数或浮点数, 原则上就是一个长度为 2 的简单 tuple. 如果我们要作一些运算, 就得调用函数, 那用起来就很麻烦.

所以, 我们定义了一个新的数据类型来处理, 以下就是其作法, 类似的方式可运用到其他的地方.

新数据类型的类定义

  1. 因为我们的座标基本上就是一个 tuple, 所以我们以 tuple 为其父类
class Coordinate(tuple):
  1. 该类的类调用返回是一个 tuple, 而不是一个一般的类实例, 这里我们就不能调用 __init__, 因为它只是作初始化动作, 所以我应该调用的是 __new__. 这第一个参数不是 self, 因为一开始该实例并未存在, 所以使用的是 cls (class), 调用 tuple.__new__ 来建立一个 tuple, 并返回该 tuple, 所以调用该类的结果得到的是一个 tuple 值, 但是其类别为 Coordinate.
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))
  1. 我们现在要取得该座标的两个最基本的属性, x 座标及 y 座标, 怎么办呢? 当然可以调用 __init__, 不过我们不想需要随时更新其值再来调用, 所以我们用到 functools 的 cached_property 来将函数名变成属性名, 这样我们就可以直接调用 P.x, P.y, 而不是 P[0], P[1] 或 P.x(), P.y(), 是不是更直观更简单了.

这里也可以使用Python 内建的@property, 这个方式每次都会计算函数结果, 而@cached_property 则会缓存结果, 下次壐调用就不会重新计算一次. 如果函数的结果只与参数有关, 使用@ cached_property 比较快, 否则还是使用 @property.

from functools import cached_property

    @cached_property
    def x(self):
        return self[0]

    @cached_property
    def y(self):
        return self[1]
  1. 另外我们希望提供一个属性, distance, 到原点的距离, 一般我们都使用函数来定义及调用, 同样地, 我们也把它设为属性, 直接使用 P.distance
import math

    @cached_property
    def distance(self):
        return (self[0]**2 + self[1]**2)**0.5
  1. 再来我们要作的是提供 python 语法中的加减乘, 这里没有除, 因为座标点之间没有除法. 在 Python 中, 如果运算式为 实例1 运算子 实例2, 如果实例2是实例1中运算子可以处理的, 就会调用实例1的该运算子函数, 如 __add__ (加), __sub__ (减), __mul__ (乘), 所以这里定义的是以座标点类开始的运算式, 比如 P+1, P1+P2…, 而不是 1+P. 在这我们定义了座标点类与座标点类的运算, 以及座标点类与 int 或 float 的运算. 当然也可以定义与 tuple, list 的运算, 只是麻烦一点, 因为还要去检查其类别啊, 长度等等, 所以我们就没這么定义, 当然也就不允许这样的运算式.
    def __add__(self, other):
        if isinstance(other, Coordinate):
            return Coordinate(self.x+other.x, self.y+other.y)
        elif isinstance(other, (int, float)):
            return Coordinate(self.x+other, self.y+other)
        else:
            raise ValueError

    def __sub__(self, other):
        if isinstance(other, Coordinate):
            return Coordinate(self.x-other.x, self.y-other.y)
        elif isinstance(other, (int, float)):
            return Coordinate(self.x-other, self.y-other)
        else:
            raise ValueError

    def __mul__(self, other):
        if isinstance(other, Coordinate):
            return Coordinate(self.x*other.x, self.y*other.y)
        elif isinstance(other, (int, float)):
            return Coordinate(self.x*other, self.y*other)
        else:
            raise ValueError
  1. 定义了左运算子, 再来就定义一下右运算子, 所使用的函数名为__radd__ ( 加 ), __rsub__ ( 减 ), __rmul__ ( 乘 ), 这里因为要使用负数, 所以我们也定义了 +P ( __pos__ 正数 ), -P ( __neg__ 负数 )
    def __neg__(self):
        return Coordinate(-self.x, -self.y)

    def __pos__(self):
        return self

    def __radd__(self, other):
        return self.__add__(other)

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

    def __rmul__(self, other):
        return self.__mul__(other)
  1. 再来我们定义一下, 输出的显示格式, 当然不用定义也可以, 因为它是 tuple 的子类, 所以没定义时它会调用 tuple 定义的输出的显示格式
    def __repr__(self):
        return f'({self.x}, {self.y})'
  1. 如果就只有这样, 那肯定是不够的, 要定义一个类, 总会有一些特殊的函数供调用, 比如逆时针旋转某个角度. 这个算法可以使用几何数学公式就可以了, 当然这是以原点为参考点.
    def rotate(self, angle):
        angle = angle * math.pi / 180.0
        cos, sin = math.cos(angle), math.sin(angle)
        return Coordinate(self.x*cos-self.y*sin, self.x*sin+self.y*cos)
  1. 如果要求两点间的距离, 当然可以另外定义一个函数来计算, 也可以使用 (P1-P2).distance.

  2. 在这里你要小心一件事,例如以下的情况,将会造成加法递归不停而出错。

def __add__(self, other):
        return self + other


使用方式

>>> p = Coordinate(3, 4) # 座标

>>> p.x, p.y, p.distance
3, 4, 5.0

>>> p+1, p-1, 1+p, 1-p, 2*p, p*2
(4, 5), (2, 3), (4, 5), (-2, -3), (6, 8), (6, 8)

>>> +p, -p
(3, 4), (-3, -4)

>>> p1, p2 = Coordinate(3, 4), Coordinate(5, 6)

>>> p1+p2, p1-p2, p1*p2
(8, 10), (-2, -2), (15, 24)

>>> p1.rotate(45)
(-0.7071067811865475, 4.949747468305834)

全部源代码

import math
from functools import cached_property

class Coordinate(tuple):

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

    @cached_property
    def x(self):
        return self[0]

    @cached_property
    def y(self):
        return self[1]

    @cached_property
    def distance(self):
        return math.dist(self, (0, 0))

    def __add__(self, other):
        if isinstance(other, Coordinate):
            return Coordinate(self.x+other.x, self.y+other.y)
        elif isinstance(other, (int, float)):
            return Coordinate(self.x+other, self.y+other)
        else:
            raise ValueError

    def __sub__(self, other):
        if isinstance(other, Coordinate):
            return Coordinate(self.x-other.x, self.y-other.y)
        elif isinstance(other, (int, float)):
            return Coordinate(self.x-other, self.y-other)
        else:
            raise ValueError

    def __mul__(self, other):
        if isinstance(other, Coordinate):
            return Coordinate(self.x*other.x, self.y*other.y)
        elif isinstance(other, (int, float)):
            return Coordinate(self.x*other, self.y*other)
        else:
            raise ValueError

    def __pos__(self):
        return self

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

    def __radd__(self, other):
        return self.__add__(other)

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

    def __rmul__(self, other):
        return self.__mul__(other)

    def __repr__(self):
        return f'({self.x}, {self.y})'

    def rotate(self, angle):
        angle = angle * math.pi / 180.0
        cos, sin = math.cos(angle), math.sin(angle)
        return Coordinate(self.x*cos-self.y*sin, self.x*sin+self.y*cos)
本作品采用《CC 协议》,转载必须注明作者和本文链接
Jason Yang
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!