Python是一个面向对象的程序设计语言,可以用类的定义方式来自定义代数系统:定义类中对象(集合元素)所具有的属性以及对象间的运算。Python为顶层抽象类保留了对应各种运算符的虚方法,我们只需在类的定义中重载所需运算符函数即可。设
n
∈
n\in
n∈ℕ,数域
P
P
P上所有次数小于
n
n
n的一元多项式记为
P
[
x
]
n
P[x]_n
P[x]n(参见博文《Python的布尔代数》),不难证明
(
P
[
x
]
n
,
+
,
⋅
)
(P[x]_n,+,\cdot)
(P[x]n,+,⋅)构成一个线性空间。我们来实现这个代数系统。
首先,定义多项式类myPoly:
import numpy as np #导入numpy
class myPoly: #多项式类
def __init__(self, coef): #初始化函数
c=np.trim_zeros(coef,trim='b') #删除高次零系数
self.coef=np.array(c) #设置多项式系数
self.degree=(self.coef).size-1 #设置多项式次数
def __eq__(self, other): #相等运算符函数
if self.degree!=other.degree: #次数不等
return False
return (abs(self.coef-other.coef)<1e-10).all()
def __str__(self): #生成表达式串供输出
return exp(self.coef)
return s
Python自定义类的语法格式为
class 类名:
类定义体
类定义体内定义所属的各函数。上列程序中,第2~13行所定义的多项式类名为“myPoly”。类定义体中罗列了3个函数:第3~6行重载的初始化函数init、第7~10行重载的相等关系运算符函数eq和第11~13行定义的供输出表达式串的函数str。
Python类中的函数分成类函数和对象函数两种。类函数从属于类,其调用格式为\
类名.函数名(实际参数表)
\text{类名.函数名(实际参数表)}
类名.函数名(实际参数表)
对象函数从属于类的对象,其调用格式为
对象.函数名(实际参数表)
\text{对象.函数名(实际参数表)}
对象.函数名(实际参数表)
myPoly类中罗列的三个函数都是对象函数,其定义特征为函数的形式参数表均含表示对象的self。换句话说,类函数的形式参数表中不含参数self。
Pyhon的顶层抽象类中已经定义了部分虚函数,如几乎每个类都必需的初始化函数init,以及各种常用的运算符函数,重载这些函数时的特征是函数名的首、尾各有两个下划线,如上列程序中重载的__init__、__eq__和__str__函数,程序员要做的是按需实现这些函数。普通函数的定义中函数名前后无需如此修饰。
根据多项式的定义可知
f
(
x
)
=
a
0
+
a
1
x
+
⋯
+
a
n
x
n
f(x)=a_0+a_1x+\cdots+a_nx^n
f(x)=a0+a1x+⋯+anxn由其次数
n
n
n及
n
+
1
n+1
n+1个系数构成的序列
a
0
,
a
1
,
⋯
,
a
n
a_0,a_1,\cdots,a_n
a0,a1,⋯,an所确定,文字是取符号
x
x
x还是别的符号无关紧要。Python中表示序列的数据类型有list,还可以使用numpy包中的数组array类。无论是list还是array对象,它们的下标与我们所定义的多项式的系数序列下标一致,也是从
0
0
0开始的。numpy是快速处理数组的工具包,为使用其中的工具模块需事先导入numpy包,这就是程序的第1行的任务。
第3~{}6行重载的init函数中,除表示创建的多项式对象参数self之外还有一个表示多项式系数序列的数组参数coef,该参数既可以是Python的list型对象也可以是numpy的array类对象。第4行调用numpy的trim_zeros函数,消除参数coef的尾部可能包含的若干个0。注意传递给命名参数trim的值为’b’表示操作是针对序列coef的尾部的。第5行用参数coef的数据创建对象自身的array型属性coef。第6行用系数序列的长度-1初始化对象的次数属性degree。需要注意的是,当参数coef传递进来的是元素均为0的数组时,即系数均为0的零多项式时,第4行操作的结果c成为一个空(没有元素)的数组,第6行的操作使得多项式该对象的次数degree为-1。与我们在博文《Python的布尔代数》约定保持一致。
第7~10行重载的是表示两个多项式是否相等的关系运算符“==”的eq函数。该函数有两个参数:表示多项式对象自身的self和另一个多项式other。其返回值是一个布尔型数据:True或False,表示self和other是否相等。第8~9行的{\bf{if}}语句检验两个多项式的次数是否不等,若不等则返回False。第10行针对两个次数相同的多项式判断是否相等。我们知道self和other均具有numpy的array类属性对象coef,表达式self.coef-other.coef表示两个等长数组按对应元素相减得到数组,即
a
0
−
b
0
,
a
1
−
b
1
,
⋯
,
a
n
−
b
n
a_0-b_0,a_1-b_1,\cdots,a_n-b_n
a0−b0,a1−b1,⋯,an−bn。abs(self.coef-other.coef)则表示数组
∣
a
0
−
b
0
∣
,
∣
a
1
−
b
1
∣
,
⋯
,
∣
a
n
−
b
n
∣
|a_0-b_0|,|a_1-b_1|,\cdots,|a_n-b_n|
∣a0−b0∣,∣a1−b1∣,⋯,∣an−bn∣,而abs(self.coef-other.coef)<1e-10则表示数组
∣
a
0
−
b
0
∣
<
1
0
−
10
,
∣
a
1
−
b
1
∣
<
1
0
−
10
,
⋯
,
∣
a
n
−
b
n
∣
<
1
0
−
10
|a_0-b_0|<10^{-10},|a_1-b_1|<10^{-10},\cdots,|a_n-b_n|<10^{-10}
∣a0−b0∣<10−10,∣a1−b1∣<10−10,⋯,∣an−bn∣<10−10
其中的每一项都是布尔型数据:非True即False。(abs(self.coef-other.coef)<1e-10).all()则表示上述数组中的所有项是否都是True。这正是判断两个等长的浮点型数组是否安对应元素相等,若返回True,则意味着以
1
1
0
10
\frac{1}{10^{10}}
10101的精确度,断定两个等次多项式相等,否则认为两个多项式不等。
第11~13行重载的对象函数str的功能是利用对象自身的coef数组数据生成多项式的表达式串供输出时使用:调用print函数输出myPoly对象时自动在后台调用此函数。该函数只有一个表示多项式对象自身的参数self,函数体中简单调用我们在博文《Python的布尔代数》中定义的exp函数即可。用下列代码测试myPoly类。
import numpy as np #导入numpy
from fractions import Fraction as F #导入Fraction
p=myPoly(np.array([1,-2,1])) #用numpy的array类对象创建多项式p
q=myPoly([F(0),F(-1,2),F(0),F(1,3)])#用Python的list对象创建多项式q
r=myPoly([0.0,-0.5,0.0,1/3]) #用list对象创建多项式r
print(p) #输出p的表达式
print(q) #输出q的表达式
print(r) #输出r的表达式
print(p==q) #检测p与q是否相等
print(p==q) #检测q与r是否相等
程序的第2行用整型数据构成的array类对象array([-1, -2, 1])作为系数序列创建myPoly类对象p。注意,形式上似乎在调用一个与myPoly类同名的函数,实际上是调用myPoly的init函数在创建多项式对象。第3行用list类对象[F(0),F(-1,2),F(0),F(1,3)]创建分数型系数的多项式q。第4行用list对象[0.0,-0.5,0.0,1/3]创建浮点型系数的多项式r。第6~8行分别输出各自的表达式串,检测重载的init函数和str函数。第9、10两行检测p与q是否相等,q与r是否相等,即检测重载的eq函数。运行程序,输出
1-2・x+1・x**2
-1/2・x+1/3・x**3
-0.5・x+0.3333333333333333・x**3
False
True
接下来,我们在myPoly类中重载多项式的线性运算:加法和数乘。
import numpy as np #导入numpy
class myPoly: #多项式类
……
def __add__(self, other): #运算符“+”
n=max(self.degree,other.degree)+1 #系数个数
a=np.append(self.coef,[0]*(n-self.coef.size)) #补长
b=np.append(other.coef,[0]*(n-other.coef.size)) #补长
return myPoly(a+b) #创建并返回多项式和
def __rmul__(self, k): #右乘数k
c=self.coef*k #各系数与k的积
return myPoly(c)
程序第3行中的省略号表示第1个程序中已定义的对象函数。第4~9行按多项式加法定义重载运算符“+”的函数add。该函数的两个参数self表示多项式对象本身,other表示参加运算的另一个多项式对象。第5行调用系统函数max计算self及othen次数的最大者+1赋予n。第6、7两行调用numpy的append函数利用n将两个多项式的系数序列a和b调整为相同长度。注意,append函数是将传递给它的两个参数首尾相接。第8行用a与b按元素求和得到的序列创建myPoly对象返回。
我们已经看到重载的相等运算符“==”实际上是函数eq,它是自身对象self的函数。表达式p==q实际上是调用p的eq函数,p扮演了第一个参数self。q是传递给eq的other参数。此处重载的加法运算符“+”的参数意义也是如此。然而,数乘法就不能简单地重载乘法运算符“∗”函数mul。因为参加运算的一个是多项式(参数self),另一个是数k。换句话说,self参数表示的是多项式p,调用时就应写成p∗k,这不符合大多数人的习惯。因此,上列程序的第9~11行重载的是“右乘”运算符“∗”函数rmul,调用时多项式可写在运算符的右边:k∗p。该函数的第一个参数self表示多项式对象作为右运算数,第二个参数表示左运算数k。第10行用k按元素乘self的系数序列coef赋予c,第11行用c创建结果多项式并返回。
下列代码测试多项式的线性运算。
import numpy as np #导入numpy
from fractions import Fraction as F #导入Fraction
p=myPoly(np.array([1,-2,1])) #用numpy的array类对象创建多项式p
q=myPoly([F(0),F(-1,2),F(0),F(1,3)])#用Python的list对象创建多项式q
k=1.5
print(p+q)
print(k*q)
运行程序,输出
1-5/2・x+1・x**2+1/3・x**3
-0.75・x+0.5・x**3
写博不易,敬请支持:
如果阅读本文于您有所获,敬请点赞、评论、收藏,谢谢大家的支持!
代码诚可贵,原理价更高。若为AI学,读正版书好。