Python 面向对象编程技术(五)
??在 Python程序中,类的继承是指新类从已有的类中取得已有的特性,诸如属性、变量和方法等。类的派生是指从已有的类产生新类的过程,这个已有的类称之为基类或者父类,而新类则称之为派生类或者子类。派生类(子类)不但可以继承使用基类中的数据成员和成员函数,而且也可以增加新的成员。
一、定义子类
在 Python程序中,定义子类的语法格式如下所示。
class ClassName1 (ClassName2):
语句
??上述语法格式非常容易理解:“ClassName1”表示子类(派生类)名,“ClassName2”表示基类(父类)名。如果在基类中有一个方法名,而在子类使用时未指定,Python会从左到右进行搜索。也就是说,当方法在子类中未找到时,从左到右查找基类中是否句含方法。另外,基类名ClassName2必须与子类在同一个作用域内定义。 ??例如在前面的文章的实例中,我们多次用到了汽车的场景模拟。其实市场中的汽车品牌有很多,例如宝马、奥迪、奔驰、丰田、比亚迪等。如果想编写一个展示霍老豆腐车的程序,最合理的方法是先定义一个表示汽车的类,然后定义一个表示某个品牌汽车的子类。例如在下面的实例中,代码中,首先定义了汽车类Car,能够表示所有品牌的汽车。然后定义了基于汽车类的子类Bmw,用于表示宝马牌豆腐汽车。实例代码如下:
class Car():
"汽车之家"
def __init__(self,manufacturer,model,year):
"初始化操作,创建描述汽车的属性"
self.manufacturer = manufacturer
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"返回描述信息"
long_name = str(self.year) + ' ' + self.manufacturer + ' ' + self.model
return long_name.title()
def read_odometer(self):
"行驶里程"
print("这是一两豆腐车,目前仪表显示的驶里程是" +str (self.odometer_reading) + "公里!")
class Bmw(Car):
"这是一个子类Bmw,基类是Car"
def __init__(self, manufacturer, model, year):
super().__init__(manufacturer, model, year)
my_tesla = Bmw("宝马","拉豆腐525Li",2019)
print(my_tesla.get_descriptive_name())
??对上述实例代码的具体说明如下所示。 ??(1)汽车类Car是基类(父类),宝马类Bmw是派生类(子类)。 ??(2)在创建子类Bmw时,父类必须包含在当前文件中,且位于子类前面。 ??(3)倒数第六行代码定义了子类Bmw,在定义子类时,必须在括号内指定父类的名称。方法__init__()可以接受创建Car实例所需的信息。 ??(4)加粗代码中的方法super()是一个特殊函数, 功能是将父类和子类关联起来。可以让Python调用Car的父类的方法__init__(), 可以让Bmw的实例包含父类Car中的所有属性。父类也被称为超类(superclass),名称super因此而得名。 ??(5)为了测试继承是否能够正确地发挥作用,在倒数第2行代码中创建了一辆宝马汽车实例,代码中提供的信息与创建普通汽车的完全相同。在创建类Bmw的一个实例时, 将其存储在变量my_ tesla中。这行代码调用在类Bmw中定义的方法__init__(), 后者能够让Python调用父类Car中定义的方法__init__()。 在代码中使用了三个实参“宝马”、“ 拉豆腐525Li”和“2019”进行测试。执行结果是: 注意:除了方法__init__()外,在子类Bmw中没有其他特有的属性和方法,这样做的目的是验证子类汽车(Bmw)是否具备父类汽车(Car) 的行为。
二、在子类中定义方法和属性
??在Python程序中,子类除了可以继承使用父类中的属性和方法外,还可以单独定义自己的属性和方法。 例如继续拿宝马子类进行举例,在宝马5系中,530Li配备的是6缸3.0T发动机。例如在下面的实例中,我们可以定义一个专有的属性来存储这个发动机参数:
class Car():
"汽车之家"
def __init__(self,manufacturer,model,year):
"初始化操作,创建描述汽车的属性"
self.manufacturer = manufacturer
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"返回描述信息"
long_name = str(self.year) + ' ' + self.manufacturer + ' ' + self.model
return long_name.title()
def read_odometer(self):
"行驶里程"
print("这是一两豆腐车,目前仪表显示的驶里程是" +str (self.odometer_reading) + "公里!")
class Bmw(Car):
"这是一个子类Bmw,基类是Car"
def __init__(self, manufacturer, model, year):
super().__init__(manufacturer, model, year)
self.battery_size = "12马力的,拉豆腐绰绰有余"
def motor(self):
"输出发动机参数"
print("发动机是"+str(self.battery_size))
my_tesla = Bmw("宝马","拉豆腐525Li",2019)
print(my_tesla.get_descriptive_name())
my_tesla.motor()
??对上述实例代码的具体说明如下所示。 ??●在倒数第七行代码中,在子类Bmw中定义了新的属性“self.battery_size",开设置属性值为 “12马力的,拉豆腐绰绰有余!” ??●在倒数第六行代码中,在子类Bmw中定义了新的方法motor(),功能是打印输出发动机参数。 ??●对于类Bmw来说,里面定义的属性“self.battery_ size” 和方法motor()可以随便使用,并且还可以继续添加任意数量的属性和方法。但是如果–个属性或方法是任何汽车都具有的,而不是宝马汽车特有的,建议将其加入到类Car中,而不是类Bmw中。这样,在程序中使用类Car时就会获得相应的功能,而在类Bmw中只包含处理和宝马牌汽车有关的特有属性和方法。本实例执行后会输出:
三、子类可以继续派生新类
??在Python程序中,根据项目情况的需要,可以基于一个子类继续创建一个子类。这种情况是非常普遍的,例如在使用代码模拟实物时,开发者可能会发现需要给类添加越来越多的细节,这样随着属性和方法个数的增多,代码也变得更加复杂,十分不利于阅读和后期维护。在这种情况下,为了使整个代码变得更加直观一些,可能需要将某个类中的一部分功能作为 一个独立的类提取出来。例如我们可以将大型类( 例如类A)派生成多个协同工作的小类,既可以将它们划分为和类A同级并列的类,也可以将它们派生为类A的子类。例如我们发现宝马汽车类Bmw中的发动机属性和方法非常复杂,例如5系有多款车型,每个车型的发动机参数也不一样,随着程序功能的增多,很需要将发动机作为一个独立的类进行编写。例如在下面的实例代码中,将原来保存在类Bmw中和发动机有关的这些属性和方法提取出来,放到另一个名为Motor的类中,将类Motor作为类Bmw的子类,并将一个 Motor实例作为类Bmw的一一个属性。
class Bmw(Car):
"这是一个子类Bmw,基类是Car"
def __init__(self, manufacturer, model, year):
super().__init__(manufacturer, model, year)
self.Motor = Motor()
class Motor(Bmw):
"类Motor是类Car的子类"
def __init__(self,Motor_size=60):
"初始化发动机参数"
self.Motor_size = Motor_size
def describe_motor(self):
"输出发动机参数"
print("发动机是"+str(self.Motor_size) + "12马力,3.0 T涡轮增压,225KW")
my_tesla = Bmw("宝马","拉豆腐525Li",2019)
print(my_tesla.get_descriptive_name())
my_tesla.Motor.describe_motor()
??对上述实例代码的具体说明如下所示。 ??(1)在第6行定义了一个名为 Motor的新类,此类继承于类Bmw。在第8行的方法__init__()中, 除了属性self之外,还设置了形参Motor_size。 形参Motor_size 是可选的,如果没有给它提供值,发动机功率将被设置为60。另外,方法describe_motor() 的实现代码也被放置到了这个类Motor中。 ??(2)在类Bmw中,添加了一个名为self.Motor的属性(第5行)。 运行这行代码后,Python会创建一个新的 Motor实例。因为没有指定发动机的具体参数,所以会被设置为默认值60,并将该实例存储在属性self.Motor中。因为每当方法__init__()被调用时都会执行这个操作,所以在每个Bmw实例中都包含–个自动创建的Motor实例。 ??(3)创建了一辆宝马汽车,并将其存储在变量my_tesla中。 在描述这辆宝马车的发动机参数时,需要使用类Bmw中的属性Motor。 ??(4)调用方法describe_ motor()。 ??(5) 整个实例的继承关系就是类Car是父类,在下面创建了一个子类Bmw,而在子类Bmw中又创建了一个子类Motor。可以将类Motor看作类Car的孙子,这样类Motor不但会继承类Bmw的方法和属性,而且也会继承Car的方法和属性。执行后Python会在实例my_tesla中查找属性Motor,并对存储在该属性中的Motor调用方法describe_ motor() 输出信息。执行后会输出:
四、私有属性和私有方法
??在Python程序中,当子类继承了父类之后,虽然子类具有了父类的属性与方法,但是不能继承父类中的私有属性和私有方法(属性名或方法名的前缀为两个下画线),在子类中还可以使用重写的方式来修改父类的方法,以实现与父类不同的行为表现或能力。例如在下面的实例中,虽然类A和类B是继承关系,但是不能相互访问私有变量。
class A:
def __init__(self):
#定义私有属性
self.__name = "wangwu"
#定义普通属性
self.age = 19
class B(A):
def sayName(self):
print(self.__name)
b = B()
b.sayName()
输出结果:
五、多重继承
??在面向对象编程的语言中,有很多开发语言支持多重继承。多重继承是指一个类可以同时继承多个,在实现多重继承定义时,在定义类时需要继承父类的小括号中以“,”分隔开要多重继承的父类。具体语法格式如下所示。
class DerivedClassName(Base1,base2,base3)
??上述语法格式很容易理解“DerivedClassName”表示子类名,小括号中的“Basel”、“Base2”和“Base3”表示多个父类名。在Python多重继承程序中,继承顺序是一个很重要的要素。如果继承的多个父类中有相同的方法名,但在类中使用时未指定父类名,则Python解释器将从左至右搜索,即调用先继承的类中的同名方法。例如在下面的实例中,演示了实现多重继承的过程:
class PrntOne: #定义类Prntone
namea= 'PrntOne' #定义变量
def set_value(self,a): #定义方法set value()
self.a = a #设置属性值
def set_namea (self,namea): #定义方法 set namea ()
PrntOne.namea= namea #设置属性值
def info (self): #定义方法info()
print('Prntone:%s,%s' %(PrntOne.namea, self.a))
class PrntSecond: #定义类PrntSecond
nameb='PrntSecond' #定义变量
def set_nameb (self,nameb): #定义方法set_nameb ()
PrntSecond.nameb= nameb #设置属性值
def info (self): #定义方法info()
print ('PrntSecond:%s' %(PrntSecond.nameb,))
class Sub (PrntOne, PrntSecond): #定义子类Sub,先后继承于类PrntOne和 PrntSecond
pass
class Sub2 (PrntSecond, PrntOne): #定义子类 sub2,
pass
class Sub3 (PrntOne,PrntSecond):#定义子类Suo3,先后继承于类PrntOne和 PrntSeconddef info(self):
def info(self):
PrntOne.info(self) #分别调用两个父类中的info()方法
PrntSecond.info(self) #分别调用两个父类中的info()方法
print('使用第一个子类:')
sub = Sub() #定义子类 Sub的对象实例
sub.set_value('11111') #调用方法set_value()
sub.info() #调用方法info()
sub.set_nameb('22222') #调用方法 set nameb()
sub.info() #调用方法info()
print('使用第二个子类:')
sub2= Sub2() #定义子类Sub2的对象实例
sub2.set_value('33333') #周用力法set_Vaue
sub2.info() #调用方法info()
sub2.set_nameb ('44444') #调用方法set nameb ()
sub2.info() #调用方法info()
print('使用第三个子类:')
sub3= Sub3() #定义子类Sub3的对象实例
sub3.set_value('55555') #调用方法set value ()
sub3.info()
sub3.set_nameb("66666")
sub3.info()
??对上述实例代码的具体说明如下所示。 ??(1)首先定义了两个父类PmOne和Priscond,它们有一个同名的方法info()用于输出类的相关信息。 ??(2)第一个子类Sub先后继承了PrntOne, PrntSecond,在实例化后,先调用PrntOne中的方法,然后调用了info()方法,由于两个父类中有同名的方法info(),所以实际上调用了PmtOne中的info()方法,因此只输出了从父类PrntOne中继承的相关信息。 ??(3)第二个子类Sub2继承的顺序相反,当调用info()方法时,实际上调用的是属于PrntSecond中的info()方法,因此只输出从父类PrntSecond中继承的相关信息。 ??(4)第三个子类Sub3继承的类及顺序和第一个子类Sub相同,但是修改了父类中的info()方法,在其中分别调用了两个父类中的info()方法,因此,每次调用Sub3类实例的info()方法,两个被继承的父类中的信息都输出了。当使用第1个和第2个子类时,虽然两次调用了方法info(),但是仅输出了其中一个父类的信息。当使用第三个子类时,每当调用方法info()时会同时输出两个父类的信息。执行后会输出:
|