继承(Inheritance)是 OOP(Object-oriented programming,面向对象程序设计)中的一个重要概念,也是类的三大特性之一(另外两个特性分别是多态和封装)。
OOP 中的“继承”概念和人类自然语言中的“继承”含义相仿。当对象 C 继承了对象 P,C 就具有了对象 P 的所有属性和方法。通常 C 和 P 都是类对象,称 C 为子类,称 P 为父类。
>>> class P: p = 2
...
>>> class C(P): pass
...
定义类 P
(注意写法,因为代码块只有一行,所以可以如上所示书写),里面只有一个类属性。然后定义类 C
。为了能够让类 C
实现对类 P
的继承,在类 C
的名称后面紧跟一个圆括号,圆括号里面写父类 P
的名称。
虽然类 C
的代码块只有 pass
,在其中没有定义任何属性和方法,但是由于它继承了类 P
,父类中所定义的类属性 p
及其值就会被“代入”到类 C
:
>>> hasattr(C, 'p')
True
>>> C.p
2
当子类继承父类之后,不需要再次编写相同的代码,实现了代码重用——“减少重复代码”是编程的一项基本原则。另外,子类继承父类的同时,也可以重新定义父类中的某些属性或方法,即用同名称的属性和方法覆盖父类的原有的对应部分,使其获得与父类不同的功能。
>>> class D(P):
... p = 222
... q = 20
...
>>> D.p
222
类 D
继承了类 P
,在类 D
中定义了类属性 p = 222
,与父类 P
中所定义的类属性重名,则 D.p
的值即为子类中所定义的值。这样的效果称为对父类中属性 p
重写或覆盖。
从继承方式而言,Python 中的继承可以分为“单继承”和“多继承”。
所谓单继承,就是只从一个父类那里继承。前面列举的继承,都是单继承。要想知道类的父类,可以用类对象的属性 __base__
查看:
>>> C.__base__
<class '__main__.P'>
类 C
的父类是类 P
,这是毫无疑问的,前面定义类 C
是就已经说明了。再看:
>>> P.__base__
<class 'object'>
在定义类 P
时,并没有注明它继承什么对象,实际上,它也有上一级,那就是类 object
。在 Python 3 中所有的类都是 object
的子类,所以,就不用在定义类的时候写这个“公共的父类”了(读者在阅读代码的时候,还可能遇到如此定义类的情况:class MyCls(object)
,在 Python 3 中,其效果与 class MyCls
等同。但是,如果在 Python 2 中,通常将 object
作为定义类时显式继承的对象,即写成 class MyCls(object)
的样式。请注意 Python 版本)。
下面列举一个示例,从中既能理解单继承的含义,也能明白为什么要继承——本节开篇未解释为什么,而是单刀直入介绍继承的写法,读者可以通过此示例理解“为什么”。
#coding:utf-8
'''
filename: personinhe.py
'''
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def get_name(self):
return self.name
def get_age(self):
return self.age
class Student(Person):
def grade(self, n):
print(f"{self.name}'s grade is {n}")
if __name__ == "__main__":
stu1 = Student("Galileo", 27) # (1)
stu1.grade(99) # (2)
print(stu1.get_name()) # (3)
print(stu1.get_age()) # (4)
执行结果:
% python personinhe.py
Galileo's grade is 99
Galileo
27
类 Student
继承了类 Person
,相当于将类 Person
的代码完全搬到了类 Student
里,即如同定义了下面的类:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def get_name(self):
return self.name
def get_age(self):
return self.age
def grade(self, n):
print(f"{self.name}'s grade is {n}")
注释(1)实例化 Student
类,其效果即等同于上述代码——参数 name
和 age
皆来自于父类 Person
的初始化方法。
注释(2)的 grade()
方法是子类 Student
中定义的;注释(3)和(4)的两个实例方法,均在父类中定义。
试想,如果还要定义一个名为 Girl
的类,其中也有一部分代码与 Person
类相同,就可以继续继承 Person
类。如此,Person
类中的代码可以被多次重用。这就是继承的意义所在。
继承了之后,子类如果还有个性化的需要,怎么办?例如,子类 Student
需要再增加一个实例属性 school
,用以说明所属学校。若对子类 Student
进行如下修改:
class Student(Person):
def __init__(self, school): # 增加初始化方法
self.school = school
def grade(self, n):
print(f"{self.name}'s grade is {n}")
而后实例化类 Student
,但是会遇到疑惑。按照此前所说,类 Student
继承了类 Person
,那么父类中的 __init__()
方法也就被“搬运”到子类中,而现在子类中又有了一个同名的 __init__()
方法,这就是所谓的重写了父类的该方法,即子类的 __init__()
方法覆盖了父类的此方法,那么在子类中,父类的 __init__()
方法不再显现。按照此逻辑,实例化应当这样做:
if __name__ == "__main__":
# stu1 = Student("Galileo", 27)
stu1 = Student("Social University")
stu1.grade(99)
print(stu1.get_name())
print(stu1.get_age())
再执行程序:
% python personinhe.py
Traceback (most recent call last):
File "/Users/qiwsir/Documents/my_books/Python完全自学教程/codes/personinhe.py", line 27, in <module>
stu1.grade(99)
File "/Users/qiwsir/Documents/my_books/Python完全自学教程/codes/personinhe.py", line 21, in grade
print(f"{self.name}'s grade is {n}")
AttributeError: 'Student' object has no attribute 'name'
报错!
在程序开发中,出现错误很正常,这并不可怕,可怕的是没有耐心阅读报错信息。
从输出的异常信息中不难看出,错误在于类 Student
中没有属性 name
。
从前面的程序中可知,属性 name
是类 Person
的 __init__()
方法中所定义的实例属性,现在新写的类 Student
虽然继承了类 Person
,但因为重写了父类的 __init__()
方法,且子类的该初始化方法中没有定义 name
属性,故在实例化 Student
类的时候,报出没有此属性的异常。
现在就提出了一个问题,子类重写或覆盖了父类的方法,但还要在子类中继续使用被覆盖的父类方法——颇有些“儿子打算脱离老子独立,但是还想要老子给予财政支持”的味道。在单继承中,为了解决此问题,可以使用 Python 的一个内置 super()
函数。再次修改 Student
类:
class Student(Person):
def __init__(self, school, name, age): # 增加参数
self.school = school
super().__init__(name, age) # (5)
def grade(self, n):
print(f"{self.name}'s grade is {n}")
注释(5)即表示在子类中调用父类中被覆盖的 __init__()
方法——注意此处初始化方法的形参,不再显式地写出 self
。这样,在实例化 Student
类的时候,就需要 school, name, age
三个参数——注意类 Student
的 __init__()
方法中的参数。继续修改程序:
if __name__ == "__main__":
# stu1 = Student("Galileo", 27)
stu1 = Student("Social University", "Galileo", 27)
stu1.grade(99)
print(stu1.get_name())
print(stu1.get_age())
print(stu1.school) # 增加一行
执行程序:
% python personinhe.py
Galileo's grade is 99
Galileo
27
Social University
注释(5)还有两种替代写法,分别是:
super(Student, self).__init__(name, age)
Person.__init__(self, name, age)
在这两种替代写法中,都使用了父类的名称。对于单继承而言,推荐使用注释(5),在后续的多继承中,会使用到替代写法的形式。
不妨将单继承的知识运用到下面的代码优化中。假设有如下所示的两个类:
#coding:utf-8
'''
filename: rectangle.py
'''
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * self.length + 2 * self.width
class Square:
def __init__(self, length):
self.length = length
def area(self):
return self.length * self.length
def perimeter(self):
return 4 * self.length
很显然,类 Rectangle
和类 Square
有很多类似之处——数学上也告诉我们,正方形可以看成是长宽相等的矩形。因此,可以将类 Square
利用单继承的知识进行优化——请读者先试试,再看下面的代码。
class Square(Rectangle):
def __init__(self, length):
super().__init__(length, length)
顿感简洁,可以发出一声惊叹了。
在此基础上,再设计一个计算正方体(六个面都是正方形,也称立方体、正六面体)的体积和表面积的类,继续使用单继承,当如何编写?思考、尝试后再看参考代码。
class Cube(Square):
def surface_area(self):
face_area = super().area()
return face_area * 6
def volume(self):
face_area = super().area()
return face_area * self.length
有了继承,是不是感觉编写程序的工作量减少了很多,再也不会“白头搔更短”了。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有