众所周知,Python 里面有一种特殊的方法叫做魔法方法;同时我们还知道字符串 s*整数 n 表示字符串复制了 n 次,一个 numpy 数组+一个数等于把这个数加到 numpy 数组的每个元素,最后得到新数组。或许大家觉得很奇怪,毕竟在上面的两个例子中乘法运算符和加法运算符做了很不符合常理的事情,一个数组+一个数完全说不通,看完今天的文章或许就能够说得通了。
概述
如果和我一样学过 C++的话,都知道 C++里面有着一个被称为运算符重载的知识点。实际上在 Python 中也有运算符重载,今天正是要讲 Python 运算符重载,有些人可能会说我之前没有学过类似于 C++这样的有运算符重载的语言怎么办?没关系,毕竟这个运算符重载在 Python 里面完全不是难点,C++另当别论。在这里我以自定义一个数学中的三维向量类为例进行讲解,在定义之前我们先想一下一个三维向量会有哪些数学知识,首先想都不用想一个向量有三个坐标——x、y 和 z,每个坐标对应一个分量,其次是向量或者向量之间可以做各种运算——取模、反向、加法、减法、数乘、数量积和向量积。
自定义向量以及用运算符重载实现其基本运算
下面我就详细讲解一下构造向量和实现其基本运算的逻辑。
构造向量
我们都知道一个三维向量坐标表示里面有三个坐标值——x、y 和 z。知道这些写出构造函数已经是绰绰有余了,代码如下:
class Vector:
def __init__(self, x=0.0, y=0.0, z=0.0):
self.x = x
self.y = y
self.z = z
定义向量的输出
向量的输出非常简单,大家可以自己随便定义,我的格式是 Vector(x,y,z),我加上 Vector 主要是为了防止有一部分人会理解成一个元组,我的代码如下:
def __str__(self):
return f'Vector({self.x},{self.y},{self.z})'
取模
在数学中向量取模直接左边一条竖线右边一条竖线就完事了,但是在 Python 中这样做是严重错误的,因为在 Python 中竖线表示按位或的运算。既然如此取模以后如何调用?很简单,我们是不是发现在数学上左边一条竖线右边一条竖线类似于绝对值?Python 中取绝对值函数不就是内置函数 abs 吗?那么我们只要让 abs 这个函数对向量实例起作用不就行了吗?怎么让它起作用就非常简单了,在__abs__魔法方法中实现取模逻辑并返回长度就行了,代码如下:
def __abs__(self):
"""
调用过程:abs(vector)
取模
:return: 向量模长
"""
return(self.x**2+self.y**2+self.z**2)**0.5
反向
在物理中我们应该都学过矢量是负值表示方向反了,在数学的向量中也是如此,在这里我通过给向量实例前面添加负号来获取与原向量大小相等方向相反的向量,具体怎么做很简单,就是把原向量的每个坐标的分量取个相反数并传入构造方法构造新向量并返回,直接看代码:
def __neg__(self):
"""
调用过程:-vector
反向
:return: 长度相等方向相反的向量
"""
return Vector(-self.x, -self.y, -self.z)
加法和减法
向量的加法和减法逻辑类似,向量加法就是对应坐标相加之后得到新向量,减法就是相减,代码如下:
def __add__(self, other):
"""
调用过程:vector1+vector2
加法
:param other: 另一个向量
:return: 自己和另一个向量相加之后得到的新向量
"""
return Vector(self.x+other.x, self.y+other.y, self.z+other.z)
def __sub__(self, other):
"""
调用过程:vector1-vector2
减法
:param other: 另一个向量
:return: 自己和另一个向量相减之后得到的新向量
"""
return Vector(self.x-other.x, self.y-other.y, self.z-other.z)
数乘
向量的数乘数学也学过,逻辑就是把每个坐标值乘那个数得到的新向量,代码略微有点繁琐,因为要考虑两种情况:“向量*数”和“数*向量”,其中“向量*数”直接在__mul__魔法方法中实现其逻辑并返回新向量,另外一种情况在__rmul__魔法方法中实现其逻辑,代码如下:
def __mul__(self, k):
"""
调用过程:vector*k
数乘(向量*数)
:param k: 数
:return: 自己和数 k 进行数乘之后的向量
"""
return Vector(k*self.x, k*self.y, k*self.z)
def __rmul__(self, k):
"""
调用过程:k*vector
数乘(数*向量)
:param k:
:return:
"""
return Vector(k*self.x, k*self.y, k*self.z)
数量积
数量积在数学上叫做点积,这个点位于中间位置(·),并不是英文句号的那个点,在 Python 中并没有这个符号,这可能有些难办了,只不过不要太失望,Python 还是能够让你办到!我们在学 numpy 矩阵的时候矩阵点乘矩阵可以调用 dot 方法,也可以通过@运算符来实现,我们在这里直接通过重载@运算符来实现向量数量积,代码如下:
def __matmul__(self, other):
"""
调用过程:vector1@vector2
数量积
:param other:另一个向量
:return:自己和另一个向量作数量积之后的结果
"""
return self.x*other.x+self.y*other.y+self.z*other.z
向量积
向量积在数学中是向量x乘,x 这个符号在计算机中可是没有的,只不过我们会发现有些时候会把 x 改成 ^,主要为了防止和字母 x 产生混淆,既然^能够表示向量积运算符那就只要重载这个运算符不就行了吗?这个运算符叫做位异或运算符,原本位异或的逻辑我就不讲了,应该都学过,再上代码之前先讲一下重载这个运算符对应定义哪一个魔法方法?定义__xor__魔法方法就是对位异或运算符做重载,代码如下:
def __xor__(self, other):
"""
调用过程:vector1^vector2
:param other:另一个向量
:return:自己和另一个向量做向量积之后的新向量
"""
return Vector(self.y*other.z-other.y*self.z, other.x*self.z-self.x*other.z, self.x*other.y-other.x*self.y)
最后给出完整源代码:
class Vector:
def __init__(self, x=0.0, y=0.0, z=0.0):
self.x = x
self.y = y
self.z = z
def __str__(self):
return f'Vector({self.x},{self.y},{self.z})'
def __abs__(self):
"""
调用过程:abs(vector)
取模
:return: 向量模长
"""
return(self.x**2+self.y**2+self.z**2)**0.5
def __neg__(self):
"""
调用过程:-vector
反向
:return: 长度相等方向相反的向量
"""
return Vector(-self.x, -self.y, -self.z)
def __add__(self, other):
"""
调用过程:vector1+vector2
加法
:param other: 另一个向量
:return: 自己和另一个向量相加之后得到的新向量
"""
return Vector(self.x+other.x, self.y+other.y, self.z+other.z)
def __sub__(self, other):
"""
调用过程:vector1-vector2
减法
:param other: 另一个向量
:return: 自己和另一个向量相减之后得到的新向量
"""
return Vector(self.x-other.x, self.y-other.y, self.z-other.z)
def __mul__(self, k):
"""
调用过程:vector*k
数乘(向量*数)
:param k: 数
:return: 自己和数 k 进行数乘之后的向量
"""
return Vector(k*self.x, k*self.y, k*self.z)
def __rmul__(self, k):
"""
调用过程:k*vector
数乘(数*向量)
:param k:
:return:
"""
return Vector(k*self.x, k*self.y, k*self.z)
def __matmul__(self, other):
"""
调用过程:vector1@vector2
数量积
:param other:另一个向量
:return:自己和另一个向量作数量积之后的结果
"""
return self.x*other.x+self.y*other.y+self.z*other.z
def __xor__(self, other):
"""
调用过程:vector1^vector2
:param other:另一个向量
:return:自己和另一个向量做向量积之后的新向量
"""
return Vector(self.y*other.z-other.y*self.z, other.x*self.z-self.x*other.z, self.x*other.y-other.x*self.y)
if __name__ == '__main__':
v1 = Vector(1, 2, 3)
v2 = Vector(1, 3, 2)
print(f'|v1|={abs(v1)}')
print(f'-v1={-v1}')
print(f'v1+v2={v1+v2}')
print(f'v1-v2={v1-v2}')
print(f'v1*2={v1*2}')
print(f'2*v1={2*v1}')
print(f'v1·v2={v1@v2}')
print(f'v1 x v2={v1^v2}')
运行结果如图所示:
其实运算符除了上面讲到的还有很多,我就不一一列举了,今天的目的就是让大家知道 Python 也有运算符重载,至于我演示了多少个运算符重载就不重要了。
本文分享自 Python机器学习算法说书人 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!