Python中的OOP – Part2
Python中的OOP–Part2,在Part1中我介绍了面向对象编程的三大支柱:封装、继承和多态。并且已经讲解了Python中封装的相关知识,接下来我们谈谈继承。
什么是继承?
继承作为面向对象编程中的一个概念,它可以帮助程序员实现:
1.建立模型-一种关系树(不是所有的编程语言都这样)
2.重用代码-帮助开发者遵守DRY原则和重用现有的实现和逻辑代码
3.扩展功能-有些时候不能改变现行类的源代码(别人都在用它,或者它不是public或干脆它就是不允许修改);要延伸的类的功能,就可以通过继承来实现。
Python支持单一和多重继承。在Python2.x和3.x中,继承的原理和实现存在很大的差异。我给出的的例子都是基于Python3.x的。
Python中的单一继承
在Python中,继承可以通过这的语法实现:classMySubClass(MyBaseClass)
先声明新的子类,然后括号中指明继承的基类(或称为父类、超类)。
举个常用的例子:Animal作为一个基类,Panda,Pig等各种动物就可以作为它的子类。
来看一段代码:
classAnimal:
__name=None
__age=0
__is_hungry=False
__nr_of_legs=0
def__init__(self,name,age,is_hungry,nr_of_legs):
self.name=name
self.age=age
self.is_hungry=is_hungry
self.nr_of_legs=nr_of_legs
#
#METHODS
#
defeat(self,food):
print("{}iseating{}.".format(self.name,food))
#
#PROPERTIES
#
@property
defname(self):
returnself.__name
@name.setter
defname(self,new_name):
self.__name=new_name
@property
defage(self):
returnself.__age
@age.setter
defage(self,new_age):
self.__age=new_age
@property
defis_hungry(self):
returnself.__is_hungry
@is_hungry.setter
defis_hungry(self,new_is_hungry):
self.__is_hungry=new_is_hungry
@property
defnr_of_legs(self):
returnself.__nr_of_legs
@nr_of_legs.setter
defnr_of_legs(self,new_nr_of_legs):
self.__nr_of_legs=new_nr_of_legs
在Animal类中,定义了四个私有成员(__name,__age,__is_hungry,__nr_of_legs)。然后分别使用@property定义每个成员的属性(这个在Part1的内容中有提到)。然后我创建了一个构造函数,带有4个参数(不包含self),使用@property设定私有成员的值。基于这4个property,我创建了一个方法叫eat(self,food),它会打印出XiseatingY,其中X是动物,Y是入参。接下来,我们把Animal作为一个基类来使用,用继承创建一条Snake。
classSnake(Animal):
__temperature=28
def__init__(self,name,temperature):
super().__init__(name,2,True,0)
self.temperature=temperature
#
#METHODS
#
defeat(self,food):
iffood=="meat":
super().eat(food)
else:
raiseValueError
#
#PROPERTIES
#
@property
deftemperature(self):
returnself.__temperature
@temperature.setter
deftemperature(self,new_temperature):
ifnew_temperature<10ornew_temperature>40:
raiseValueError
self.__temperature=new_temperature
在Snake类中存在两个参数:name和temperature,对于__temperature这个私有成员,我创建一个property来接受入参。在构造函数里,我首先使用super()调用基类的构造函数(还有其他的方法来调用,但在Python3.x中,这是推荐的方式)。当调用基类的构造函数时,我会传入默认值,例如nr_of_legs默认值zero,因为蛇没有腿,is_hungry默认值为True,因为蛇通常比其他动物要饿
跟其他编程语言一样,在Python中也可以重写方法。我已经重写了eat方法,并且我加了额外的逻辑:如果给出的食物不是肉,那么我会抛出一个ValueError;如果是,我就会调用基类Animal中定义的eat方法。
Python中的多重继承
Python为我们提供了从多个基类继承的可能性,叫做多重继承。如果有一些不同功能的类,而且这些不同的功能需要被组合在一起使用,那么多重继承将会非常有用。
在Python中,多重继承的语法非常简单,只要在新子类后的括号内填写所有的基类即可,例如:classMySubClass(MyBaseClass1,MyBaseClass2,MyBaseClass3)。
classPhone:
def__init__(self):
print("Phoneconstructorinvoked.")
defcall_number(self,phone_number):
print("Callingnumber{}".format(phone_number))
classComputer:
def__init__(self):
print("Computerconstructorinvoked.")
definstall_software(self,software):
print("Computerinstallingthe{}software".format(software))
classSmartPhone(Phone,Computer):
def__init__(self):
super().__init__()
我定义了3个类:Phone,Computer,SmartPhone
其中2个是基类:Phone,Computer;1个是继承类:SmartPhone
从逻辑上讲,这样做是正确的:一部智能手机可以打电话也可以安装软件,SmartPhone应该具有call_number方法(从Phone继承)和install_software方法(从Computer继承)
#
#willbediscussedlaterwhyonlytheconstructorofPhoneclasswasinvoked
#
>>>my_iphone=SmartPhone()
Phoneconstructorinvoked.
>>>my_iphone.call_number("123456789")
Callingnumber123456789
>>>my_iphone.install_software("python")
Computerinstallingthepythonsoftware
我们来看SmartPhone这个类的构造函数,挺简单,它包含了基类的构造函数,看上去没错,它通过继承获得的。但问题是从哪个基类继承的。如果仔细代码,会发现它仅仅从Phone继承的构造函数,这是为什么?
这个问题不是很好解释,它与Python的MRO(MethodResolutionOrder)有关系。
Python中的MRO
MRO是一套规则,用于定义和确定类的线性化(linearization)。线性化(也称为优先级列表)是一个由近及远的基类的列表。MRO对于支持多重继承的编程语言十分重要,它有助于编程语言处理DiamondProblem。
在Python2.3,为了更好的定义多重继承时具体的MRO,发生了一些根本性的变化(这个版本采用C3线性化)。MicheleSimionato写过一篇很棒的帖子,说明了相关的方法和算法,帖子相当长,包含了大量的例子和算法的详细说明。GuidovanRossum也写过一篇关于PythonMRO的详细文章。Perl语言同样是使用C3线性化算法来定义MRO的。
为了给回答我前面提出的问题:为什么super().__init__()只调用Phone的构造函数?出现这种情况,是因为基类的解析顺序。当super().__init__()调用Phone(MRO中的第一个基类),其他基类的__init__()没有被调用。MRO影响基类的结构函数如何被调用,我作为一个程序猿,当我使用多重继承时,必须确保我的基类全部被正确的初始化。我更新SmartPhone的代码以确保Computer和Phone都被初始化。(如果使用Python2.x,请确保你获得了相同的MRO)
classPhone(object):
def__init__(self):
print("Phoneconstructorinvoked.")
defcall_number(self,phone_number):
print("Callingnumber{}".format(phone_number))
classComputer(object):
def__init__(self):
print("Computerconstructorinvoked.")
definstall_software(self,software):
print("Computerinstallingthe{}software".format(software))
classSmartPhone(Phone,Computer):
def__init__(self):
Phone.__init__(self)
Computer.__init__(self)
现在创建了一个新的SmartPhone,这两个基类的构造函数都被调用了。
Python3.2.3(default,Feb272014,21:31:18)[GCC4.6.3]onlinux2
Type"help","copyright","credits"or"license"formoreinformation.
>>>fromoop_multiimportSmartPhone
>>>s=SmartPhone()
Phoneconstructorinvoked.
Computerconstructorinvoked.
>>>
在Python中,对一个类使用mro()或者__mro__能够显示出类的MRO。
例如:
>>>SmartPhone.mro()[<class'SmartPhone'>,<class'Phone'>,<class'Computer'>,<class'object'>]>>>SmartPhone.__mro__(<class'SmartPhone'>,<class'Phone'>,<class'Computer'>,<class'object'>)
MRO中的第一个元素,是我们使用mro()这个命令的类SmartPhone,接着是Phone,最后是Computer,这三个基类是如此设定的。
在一些拥有复杂层次结构的大型类中定义MRO将会十分困难,甚至在一些特定情况下MRO是无法定义的。Python将会抛出TypeError以防基类间出现交叉引用,例如:
classPhone(object):
def__init__(self):
print("Phoneconstructorinvoked.")
defcall_number(self,phone_number):
print("Callingnumber{}".format(phone_number))
classComputer(object):
def__init__(self):
print("Computerconstructorinvoked.")
definstall_software(self,software):
print("Computerinstallingthe{}software".format(software))
classSmartPhone(Phone,Computer):
def__init__(self):
Phone.__init__(self)
Computer.__init__(self)
classTablet(Computer,Phone):
def__init__(self):
Computer.__init__(self)
Phone.__init__(self)
classPhablet(SmartPhone,Tablet):
def__init__(self):
SmartPhone.__init__(self)
Tablet.__init__(self)
如代码中所示,我创建了两个新类,一个是Tablet(多重继承自Computer和Phone,注意跟SmartPhone的不同),另个是Phablet(多重继承自SmartPhone和Tablet)。SmartPhone和Tablet对于基类Phone和Computer差生了交叉引用,Python解释器于是抛出了TypeError,因为C3线性化规则不能给出确切的基类的调用顺序。
Python培训、Python培训班、Python培训机构,就选光环大数据!
还不够过瘾?想学习更多?点击 http://hadoop.aura.cn/python/ 进行Python学习!
大数据培训、人工智能培训、Python培训、大数据培训机构、大数据培训班、数据分析培训、大数据可视化培训,就选光环大数据!光环大数据,聘请专业的大数据领域知名讲师,确保教学的整体质量与教学水准。讲师团及时掌握时代潮流技术,将前沿技能融入教学中,确保学生所学知识顺应时代所需。通过深入浅出、通俗易懂的教学方式,指导学生更快的掌握技能知识,成就上万个高薪就业学子。 更多问题咨询,欢迎点击------>>>>在线客服!