声明:本内容非盈利性质,也不支持任何组织或个人将其用作盈利用途。本内容来源于参考书或网站,会尽量附上原文链接,并鼓励大家看原文。侵删。
2.3 继承
继承是类之间的一种重要关系之一。类之间的关联关系是通过设置类属性(一般是添加关联的id)关联的,这将在学习数据库时详细说到;类之间的聚合关系一般是自然关系的反映,是显而易见的,不需要在类层面做联系(可能在对象层面做联系);类之间的依赖关系,则直接以一种类型做另一种类型的成员。那么,继承关系要如何体现在类的代码上呢?这就是本节要研究的问题。
2.3.1 python中继承的特性
python是支持多继承的,这一点与C++相同,而与Java只支持单继承不同。
多继承的一个重要的问题是当出现“菱形继承”的问题时,是采用深度优先的继承策略还是采用广度优先的继承策略。python中继承时,分为经典类和新式类,经典类按深度优先查找,新式类按广度优先查找,但从python3开始全都为新式类了,所以都按广度优先查找。如下:
2.3.2 python中继承的实现
(1)继承的一般格式
python中的继承是直接将父类写在子类名称后面的括号中。一般我们会遇到以下几种情况的定义类的格式:
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
pass
class Person():
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
pass
class Student(Person): # 如果多继承,括号中可以写多个父类,以逗号分隔
def __init__(self, name, age, class):
super.__init__(name, age) # 继承属性的定义
self.class = class # 自有属性的定义
def study(self, course): # 定义自有方法
pass
def eat(self): # 子类中出现与父类中相同的方法时,用子类中的方法
super().eat # 重写时可以依靠父类的代码
pass
在python2中Animal类和Person类没有继承或没有显式继承,它们都是经典类;Student类有显式继承,是新式类。而在python3中,Animal、Person和Student都是新式类。
(2)继承的示例
python中实现单继承的方式很简单:class <类名>(父类名):,由于python中没有访问权限修饰符,因此也不区分继承方式。如下:
# 父类与子类在同一文件中
class Person(): # 父类
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
pass
class Student(Person): # 子类
def __init__(self, name, age, clazz):
super().__init__(name, age) # 继承属性的定义
self.clazz = clazz # 自有属性的定义
def study(self, course): # 定义自有方法
pass
def eat(self): # 子类中出现与父类中相同的方法时,用子类中的方法
super().eat() # 重写时可以依靠父类的代码
pass
当父类与子类在不同文件或不同包时,要增加一些操作:
# 父类,位于object包下的Person.py文件中
class Person():
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print("Person can eat.")
# 子类,位于object包下的Student.py文件中
from object import Person # 先执行导入模块,object为包名,Person为模块名(就是Person.py文件名去掉后缀)
class Student(Person.Person): # Person.Person表示调用Person模块中的Person对象;这与使用其他模块中的函数的方式是一样的:模块名.函数名();
def __init__(self, name, age, clazz):
super().__init__(name, age) # 继承属性的定义
self.clazz = clazz # 自有属性的定义
def study(self, course): # 定义自有方法
pass
def eat(self): # 子类中出现与父类中相同的方法时,用子类中的方法
super().eat() # 重写时可以依靠父类的代码
stu1 = Student("zhaosi", 23, "三班")
stu1.eat()
在java中继承另一个文件中的父类时,如果同包可以直接用父类名(亦即父类文件名);如果不同包,只需先导包,再直接用父类名即可(在本包中没有同名类的情况下)。但python中这么做是不行的,因这python中并不强制要求文件名与类名一致;并且即便一致的情况下,python仍认为文件与文件内部的类或函数是不同类型的东西。比如,我们在Person类后面用print(Person) 打印,输出的是<class ‘__main__
.Person’>;但是我们在另一个文件中,用from object import Person导入模块后用print(Person) 打印,输出的是<module ‘object.Person’ from ‘C:\RYH\Office\Python\qf2205\object\Person.py’> ;明显前者是一个类,后者是一个.py文件,也就是说两处的Person指代的不是同一个东西。
如果是多继承,则注意复杂的继承关系。如下:
class A(object): # A作为了B和C的父类;
def __init__(self, name):
self.name = name
print("in A")
class B(A):
def __init__(self, name):
self.name = name
print("in B")
super().__init__(name)
class C(A):
def __init__(self, name): # 当子类的__init__方法中示显示调用父类的构造方法时,这里会报阴影,但这并不算代码错误,只是提示用户不要忘记调用父类初始化方法,依然能正常运行;
self.name = name
print("in C")
class D(B, C): # D同时继承了B和C;
pass
obj = D('myname')
# 当继承关系比较复杂时,可以使用__mro__类属性查看类的继承关系
print(D.__mro__) # 打印的列表中有一定会有<class 'object'>,因为这是所有类的共同超类。
# python3以后默认所有的类都有共同的祖先类object。查看object的源码
class Developer(object):
pass
data = Developer()
data.name = 'CooMark'
data.age = 30
print(type(object))
关于多继承中继承构造函数的问题,D同时继承了B和C,按出现顺序D会先继承B的构造函数,如果B中没有构造函数,那么D该继承 A还是C?这个问题在python2中涉及到新式类(继承object的类是新式类)与经典类(没有写父类的是经典类)的概念;经典类是按照深度优先查找,新式类按照广度优先查找。但在python3以上的版本中,只有新式类(即默认所有类继承自object),因此继承方式全按广度优先。由于可以断定,如果B中没有构造,D将优先从C中继承构造。
(3)可以从父类继承的成员
实际上子类可以继承父类的所有成员,如:实例属性(包括公有实例属性与私有实例属性)、类属性、实例方法、类方法、静态方法;但理论上私有实例属性还是直接属性父类的。如下:
class Person(): # 父类
nicheng = "null"
def __init__(self, name, age):
self.name = name
self.age = age
self.__value = 5
def eat(self):
print("Person can eat.")
@classmethod
def run(cls):
print("{}正在跑".format(cls.nicheng))
@staticmethod
def look(who):
print("正在看{}".format(who))
class Student(Person): # 子类
def __init__(self, name, age, clazz):
super().__init__(name, age) # 访问父类中的初始化方法
self.clazz = clazz
s = Student("hang", 18, 4)
print(s.name) # 子类对象访问继承自父类的公有实例属性
print(s._Person__value) # 子类对象访问继承自父类的私有实例属性,虽然可以调用,但却是以父类的私有属性调用的。
print(s.nicheng) # 子类对象访问继承自父类的类属性
s.eat() # 子类对象调用继承自父类的实例方法
s.run() # 子类对象调用继承自父类的类方法
s.look("mm") # 子类对象调用继承自父类的静态方法
print(dir(s)) # 该语句的打印结果为['_Person__value', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'clazz', 'eat', 'look', 'name', 'nicheng', 'run']。可以看到,子类对象中也是有__value这个私有属性的,但其改变之后的名称符号是父类下的_Person__value。
# 子类正规访问父类私有实例属性的方式,是通过该私有属性对应的公开方法访问,即通过getter与setter访问和修改。
2.3.3 子类访问父类成员
外部访问父类成员可以通过子类对象或子类进行,如上一节所示。那么如何在子类中访问父类成员?
python中用super()函数来访问父类属性和方法。由于与C一样支持多重继承,必须要区别是访问哪个父类的成员,因此python中用函数来访问父类属性,而不是用关键字。
super()函数是父类关键字,使用super()可以访问父类的类属性、实例方法、类方法及静态方法,但不能访问实例属性(因为实例属性是定义在__init__
方法下的)。如下:
class Person(): # 父类
nicheng = "null"
def __init__(self, name, age):
self.name = name
self.age = age
self.__value = 5
def eat(self):
print("Person can eat.")
@classmethod
def run(cls):
print("{}正在跑".format(cls.nicheng))
@staticmethod
def look(who):
print("正在看{}".format(who))
class Student(Person): # 子类
def __init__(self, name, age, clazz):
super().__init__(name, age) # 访问父类中的初始化方法
self.clazz = clazz
def study(self, course):
super().look(course) # 访问父类中的静态方法
pass
def eat(self):
super().eat() # 访问父类中的实例方法
print(super().nicheng) # 访问父类中的类属性
def show(self):
# print(super()._Person__value) # 访问父类中的私有实例属性,不能使用super()
# print(super().name) # 访问父类中的公有实例属性,不能使用super()
print(self._Person__value) # 访问父类中的私有实例属性,可以使用self
print(self.name) # 访问父类中的公有实例属性,可以使用self
super().run() # 访问父类中的类方法
s = Student("hang", 18, 4)
s.eat()
s.show()
s.study("mm")
# super.nicheng = "11" # 用super编译是不报错的,但运行会报错,这样是不能访问父类属性的,因为super在python中是一个类,这样的语句相当于调用super类中的属性,但super中是没有该属性的;
print(super) # 打印会显示super为一个class
print(super(Person))
2.3.4 方法重写
python中方法重写有以下规则:
- 在父子类中;
- 方法名相同;
- 参数列表可以相同,也可以不同;
- 与返回值类型无关(python定义函数不用预设返回值类型);
- 与访问权限无关(python没有访问权限关键字);
- 静态方法可以被继承,但是不能被重写;
# 方法的重写
# 当我们调用一个对象的方法时
# 会优先去当前对象中寻找是否具有该方法,如果有则直接调用
# 如果没有则去对象的父类中寻找,如果父类中有则直接调用父类中的方法
# 如果还是没有则去父类中的父类中寻找,以此类推,直到找到object ,
# 如果始祖父类也没有, 就报错
class Animal:
def run(self):
print('动物会跑~~~')
def sleep(self):
print('动物睡觉~~~')
class Dog(Animal):
def bark(self):
print('汪汪汪~~~')
def run(self):
print('狗跑~~~~')
d = Dog()
d.run()
# 如果参数名不同,则子类对象要完全按照自己的方法进行调用
class Animal:
def run(self):
print('动物会跑~~~')
def sleep(self):
print('动物睡觉~~~')
class Dog(Animal):
def bark(self):
print('汪汪汪~~~')
def run(self, who):
print('{}狗跑~~~~'.format(who))
d = Dog()
d.run("我的")
# d.run() # 若按照父类中的方法调用则会出错