Python数据结构与算法-------基础篇(类与继承)
1 导论
1.2 Python基础
1.2.6 Python面向对象编程:定义类
Python是一门面向对象的编程语言,面向对象的编程语言最强大的一项特性是允许程序员创建全新的类来对求解问题所需的数据进行建模,每当用户需要实现抽象数据类型时,就可以创建全新的类。
1.2.6.1 Fraction类
展示如何实现用户定义的类,一个常用的例子就是构建实现抽象数据类型Fraction的类,构建出的Fraction类的主要功能是创建出看起来很像分数的数据对象,并且可以针对分数进行加减乘除等操作,最后的结果也必须是最简分数,以下从Fraction类的构建过程来讲解Python类的构建过程。
—定义类:Python中定义类的方法是,提供一个类名以及一整套与函数定义语法类似的方法定义,一个方法定义框架如下:
class Fraction:
#方法定义
Python中所有的类应该首先提供的是构造方法,构造方法定义了数据对象的创建方式,在本例中,要创建一个Fraction对象需要提供分子和分母两个数据,构造方法总是命名为_init_,示例代码如下:
class Fraction:
def __init__(self, top, bottom):
self.num = top
self.den = bottom
需要注意的是,形参有三项,self是一个总是指向对象本身的特殊参数,它必须是第一个参数,在调用方法时,不需要给其提供实参,即如果提供了两个参数,则其默认分配给后面两个形参,而不会分配给self。同时,在构造方法中将后两个形参分配给了两个内部数据对象,作为状态的一部分,方便数据在各个方法之间流通。
—创建Fraction类的实例
使用类名,传入状态的实际值,调用构造方法,就可完成创建,不需要直接调用构造方法,传入状态实际值构造方法自动被调用,示例代码如下:
myfraction = Fraction(3, 5)
—打印Fraction实例对象
直接打印Fraction实例对象,输出的结果是实例对象本身的地址,而非我们想要的结果,示例代码如下:
class Fraction:
def __init__(self, top, bottom):
self.num = top
self.den = bottom
myfraction = Fraction(3, 5)
print(myfraction)
输出:<__main__.Fraction object at 0x000001E9714DCA30>
Python要求对象将自己转换成一个可以被写到输出段的字符串,有两种方法可以实现这一目的。
—method 1:show方法
定义一个show方法,使得调用该方法Fraction对象能够将自己作为字符串打印出来,示例代码如下:
class Fraction:
def __init__(self, top, bottom):
self.num = top
self.den = bottom
def show(self):
print(self.num, "/", self.den)
myfraction = Fraction(3, 5)
myfraction.show()
print(myfraction)
输出:3 / 5
<__main__.Fraction object at 0x000002784E754850>
—method 2:重写默认实现
Python为所有的类都提供了一套标准的方法,但是这些方法可能没有正常工作,其中之一就是将对象转换为字符串的方法_str_,我们只需要重写该方法的实现,就可以实现将对象转换为字符串输出,示例代码如下:
class Fraction:
def __init__(self, top, bottom):
self.num = top
self.den = bottom
def __str__(self) -> str:
return str(self.num) + "/" +str(self.den)
myfraction = Fraction(3, 5)
print(myfraction)
输出:3/5
—Fraction对象相加
我们可以重写Fraction类中的许多方法来实现我们需要的功能,未重写方法前,如果试图将两个对象相加,示例代码和输出结果如下:
class Fraction:
def __init__(self, top, bottom):
self.num = top
self.den = bottom
def __str__(self) -> str:
return str(self.num) + "/" +str(self.den)
f1 = Fraction(1, 4)
f2 = Fraction(1, 2)
f3 = f1 + f2
输出:Traceback (most recent call last):
File "c:\Users\test4.py", line 12, in <module>
f3 = f1 + f2
TypeError: unsupported operand type(s) for +: 'Fraction' and 'Fraction
在上述代码中,我们发现加号无法处理Fraction对象,可以重写Fraction类的__add__方法来修正这个错误,在此,两个分数相加的公式为:
a
b
+
c
d
=
a
d
b
d
+
c
b
b
d
=
a
d
+
c
b
b
d
\frac{a}{b} +\frac{c}{d}=\frac{ad}{bd}+\frac{cb}{bd}=\frac{ad+cb}{bd}
ba+dc=bdad+bdcb=bdad+cb
根据此公式,重写的__add__方法示例代码如下:
class Fraction:
def __init__(self, top, bottom):
self.num = top
self.den = bottom
def __str__(self) -> str:
return str(self.num) + "/" +str(self.den)
def __add__(self, otherfraction):
newnum = self.num*otherfraction.den + self.den*otherfraction.num
newden = self.den*otherfraction.den
return Fraction(newnum, newden)
f1 = Fraction(1, 4)
f2 = Fraction(1, 2)
f3 = f1 + f2
print(f3)
输出:6/8
—化最简分数
上述代码实现了Fraction对象的加法运算,但是仍有瑕疵,最后得到的结果不是最简分数,需要一个辅助算法将结果化为最简分数。只需将分子分母除以其最大公因数即可。寻找最大公因数最著名的算法就是欧几里得算法,欧几里得算法指出,对于整数m和n,如果m能被n整除,那么他们的最大公因数就是n,否则它们的最大公因数是n与m除以n的余数的最大公因数。基于上述陈述,完善后的示例代码如下:
class Fraction:
def __init__(self, top, bottom):
self.num = top
self.den = bottom
def __str__(self) -> str:
return str(self.num) + "/" +str(self.den)
def __add__(self, otherfraction):
def gcd(m, n):
while m%n != 0:
oldm = m
oldn = n
m = oldn
n = oldm%oldn
return n
newnum = self.den*otherfraction.num + self.num*otherfraction.den
newden = self.den*otherfraction.den
common = gcd(newnum, newden)
return Fraction(newnum//common, newden//common)
f1 = Fraction(1, 4)
f2 = Fraction(1, 2)
f3 = f1 + f2
print(f3)
输出:3/4
—浅相等和深相等
假设有两个Fraction对象,f1和f2,只有在它们是同一个对象引用时,f1 == f2才为True,这被称为浅相等,根据值来判断相等可以建立深相等。
1.2.6.2 继承:以逻辑门电路为例
继承是使一个类与另一个类相联系,Python中的子类可以从父类继承特征数据和行为,父类也称为超类。例如Python集合类中的相互联系,列表是有序集合的子,因此我们可以将有序集合称为父,列表称为有序集合的子,这种关系称为IS-A关系。列表从有序集合继承了重要特征,也就是内部数据的顺序以及诸如拼接、重复和索引等方法。
子类继承了父类的共同特征,但是子类之间通过额外的特征来区分彼此。通过将类组合成继承层次,能够大大的提高代码的复用率,使以前编写的代码可以更好的应用到新的场景中,同时使系统之间的结构层次更加清晰。
—以逻辑门电路为例
逻辑门电路的基本逻辑单元分别为与门、或门和非门,通过不同的模式将这些逻辑门组合起来并且提供一系列的值,可以构建出具有逻辑功能的电路,下图是逻辑门电路的继承层次结构:
—LogicGate类
LogicGate类代表的是逻辑门的通用特性,其主要包含的是逻辑门的标签和一个输出,两个特性也是三个基本逻辑门所共有的。其代码实现如下所示:
class LogicGate:
def __init__(self, n) -> None:
self.label = n
self.output = None
def getLabel(self):
return self.label
def getOutput(self):
self.output = self.performGateLogic()
return self.output
在上述构建的超类中,暂时还不用实现performGateLogic函数,在后续继承层次结构中的每一个逻辑门中来实现该函数,这也是面向对象编程中一个非常强大的思想——我们创建了一个方法,但是该方法的代码还不存在。
接下来是依据逻辑门的输入个数来对逻辑门进一步分类,整理出每一类的共有特性。
—BinaryGate和UnaryGate类
BinaryGate和UnaryGate是LogicGate的一个子类,BinaryGate有两个输入,与门与或门就属于这一类,而UnaryGate有一个输入,非门属于这一类,这两个类的代码实现如下所示:
class BinaryGate(LogicGate):
def __init__(self, n) -> None:
super().__init__(n)
self.pinA = None
self.pinB = None
def getPinA(self):
return int(input("Enter Pin A input for gate" + self.getLabel() +"-->"))
def getPinB(self):
return int(input("Enter Pin B input for gate" + self.getLabel() +"-->"))
class UnaryGate(LogicGate):
def __init__(self, n) -> None:
super().__init__(n)
self.pin = None
def getPin(self):
return int(input("Enter Pin input for gate" + self.getLabel() +"-->"))
在这两个构造方法中首先使用super函数来调用其父类LogicGate的构造方法,初始化从父类LogicGate中继承的数据项(输出和标签),接着构造方法添加输入特性,子类的构造方法需要先调用父类的构造方法,然后再初始化自己独有的数据项。
—AndGate类
与门有两个输入,因此AndGate类是BinaryGate的子类,以下代码是AndGate类的具体代码实现:
class AndGate(BinaryGate):
def __init__(self, n) -> None:
super().__init__(n)
def performGateLogic(self):
a = self.getPinA()
b = self.getPinB()
if a == 1 and b == 1:
return 1
else:
return 0
或门与非门也用同样的方式创建,示例代码如下所示:
class OrGate(BinaryGate):
def __init__(self, n) -> None:
super().__init__(n)
def performGateLogic(self):
a = self.getPinA
b = self.getPinB
if a == 0 and b == 0:
return 0
else:
return 1
class NotGate(UnaryGate):
def __init__(self, n) -> None:
super().__init__(n)
def performGateLogic(self):
a = self.getPin
if a == 0:
return 1
else:
return 0
在构建与、或、非子类时,不需要添加任何新的数据,其所需要的数据均从父类继承而来。
是与门整体代码的验证输出如下列代码所示:
class LogicGate:
def __init__(self, n) -> None:
self.label = n
self.output = None
def getLabel(self):
return self.label
def getOutput(self):
self.output = self.performGateLogic()
return self.output
class BinaryGate(LogicGate):
def __init__(self, n) -> None:
super().__init__(n)
self.pinA = None
self.pinB = None
def getPinA(self):
return int(input("Enter Pin A input for gate" + self.getLabel() +"-->"))
def getPinB(self):
return int(input("Enter Pin B input for gate" + self.getLabel() +"-->"))
class UnaryGate(LogicGate):
def __init__(self, n) -> None:
super().__init__(n)
self.pin = None
def getPin(self):
return int(input("Enter Pin input for gate" + self.getLabel() +"-->"))
class AndGate(BinaryGate):
def __init__(self, n) -> None:
super().__init__(n)
def performGateLogic(self):
a = self.getPinA()
b = self.getPinB()
if a == 1 and b == 1:
return 1
else:
return 0
g1 = AndGate("G1")
print(g1.getOutput())
输出:Enter Pin A input for gateG1-->1
Enter Pin B input for gateG1-->0
0
—连接器Connector类
在基本逻辑门构建完成之后,接下来就是使用基本逻辑门搭建逻辑门电路,搭建逻辑门电路除了基本逻辑门之外,还需要一个连接器的类,主要功能是将上一个逻辑门的输出连入下一逻辑门的输入,实现逻辑门之间的连接。Connector类并不在逻辑门的继承层次结构中,但是它会使用该结构,从而使得每一个连接器两端都有一个逻辑门,这被称为HAS-A关系。Connector 与 LogicGate 是 HAS-A 关系,连接器内部包含LogicGate实例。示例代码如下所示:
class Connector:
def __init__(self, fgate, tgate) -> None:
self.fromgate = fgate
self.togate = tgate
tgate.setNextPin(self)
def getFrom(self):
return self.fromgate
def getTo(self):
return self.togate
setNextPin得调用对于连接器得实现非常重要,需要将这个方法添加至逻辑门类中,示例代码如下:
def setNextPin(self, source):
if self.pinA == None:
self.pinA = source
else:
if self.pinB == None:
self.getPinB = source
else:
raise RuntimeError("Error: NO EMPTY PINS")
—逻辑门电路验证
两个与门( g1 和 g2)的输出与或门( g3)的输入相连接,或门的输出又与非门( g4)的输入相连接。非门的输出就是整个电路的输出 ,电路图和完整代码如下所示:
class LogicGate:
def __init__(self, n) -> None:
self.label = n
self.output = None
def getLabel(self):
return self.label
def getOutput(self):
self.output = self.performGateLogic()
return self.output
class BinaryGate(LogicGate):
def __init__(self, n) -> None:
super().__init__(n)
self.pinA = None
self.pinB = None
def getPinA(self):
if self.pinA == None:
return int(input("Enter Pin A input for gate" + self.getLabel() + "-->"))
else:
return self.pinA.getFrom().getOutput()
def getPinB(self):
if self.pinB == None:
return int(input("Enter Pin B input for gate" + self.getLabel() +"-->"))
else:
return self.pinB.getFrom().getOutput()
def setNextPin(self, source):
if self.pinA == None:
self.pinA = source
else:
if self.pinB == None:
self.getPinB = source
else:
raise RuntimeError("Error: NO EMPTY PINS")
class UnaryGate(LogicGate):
def __init__(self, n) -> None:
super().__init__(n)
self.pin = None
def getPin(self):
if self.pin == None:
return int(input("Enter Pin input for gate" + self.getLabel() +"-->"))
else:
return self.pin.getFrom().getOutput()
def setNextPin(self, source):
if self.pin == None:
self.pin = source
else:
raise RuntimeError("Error: NO EMPTY PINS")
class AndGate(BinaryGate):
def __init__(self, n) -> None:
super().__init__(n)
def performGateLogic(self):
a = self.getPinA()
b = self.getPinB()
if a == 1 and b == 1:
return 1
else:
return 0
class OrGate(BinaryGate):
def __init__(self, n) -> None:
super().__init__(n)
def performGateLogic(self):
a = self.getPinA
b = self.getPinB
if a == 0 and b == 0:
return 0
else:
return 1
class NotGate(UnaryGate):
def __init__(self, n) -> None:
super().__init__(n)
def performGateLogic(self):
a = self.getPin
if a == 0:
return 1
else:
return 0
class Connector:
def __init__(self, fgate, tgate) -> None:
self.fromgate = fgate
self.togate = tgate
tgate.setNextPin(self)
def getFrom(self):
return self.fromgate
def getTo(self):
return self.togate
g1 = AndGate("G1")
g2 = AndGate("G2")
g3 = OrGate("G3")
g4 = NotGate("G4")
c1 = Connector(g1, g3)
c2 = Connector(g2, g3)
c3 = Connector(g3, g4)
print(g4.getOutput())
输出:Enter Pin A input for gateG1-->0
Enter Pin B input for gateG1-->1
Enter Pin A input for gateG2-->1
Enter Pin B input for gateG2-->1
0