前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >开源图书《Python完全自学教程》8.5.1单继承

开源图书《Python完全自学教程》8.5.1单继承

作者头像
老齐
发布2022-07-06 16:13:13
2180
发布2022-07-06 16:13:13
举报
文章被收录于专栏:老齐教室老齐教室

8.5 继承

继承(Inheritance)是 OOP(Object-oriented programming,面向对象程序设计)中的一个重要概念,也是类的三大特性之一(另外两个特性分别是多态和封装)。

OOP 中的“继承”概念和人类自然语言中的“继承”含义相仿。当对象 C 继承了对象 P,C 就具有了对象 P 的所有属性和方法。通常 C 和 P 都是类对象,称 C 为子类,称 P 为父类

代码语言:javascript
复制
>>> class P: p = 2
...
>>> class C(P): pass
...

定义类 P (注意写法,因为代码块只有一行,所以可以如上所示书写),里面只有一个类属性。然后定义类 C 。为了能够让类 C 实现对类 P 的继承,在类 C 的名称后面紧跟一个圆括号,圆括号里面写父类 P 的名称。

虽然类 C 的代码块只有 pass ,在其中没有定义任何属性和方法,但是由于它继承了类 P ,父类中所定义的类属性 p 及其值就会被“代入”到类 C

代码语言:javascript
复制
>>> hasattr(C, 'p')
True
>>> C.p
2

当子类继承父类之后,不需要再次编写相同的代码,实现了代码重用——“减少重复代码”是编程的一项基本原则。另外,子类继承父类的同时,也可以重新定义父类中的某些属性或方法,即用同名称的属性和方法覆盖父类的原有的对应部分,使其获得与父类不同的功能。

代码语言:javascript
复制
>>> class D(P):
...     p = 222
...     q = 20
...
>>> D.p
222

D 继承了类 P ,在类 D 中定义了类属性 p = 222 ,与父类 P 中所定义的类属性重名,则 D.p 的值即为子类中所定义的值。这样的效果称为对父类中属性 p 重写或覆盖。

从继承方式而言,Python 中的继承可以分为“单继承”和“多继承”。

8.5.1 单继承

所谓单继承,就是只从一个父类那里继承。前面列举的继承,都是单继承。要想知道类的父类,可以用类对象的属性 __base__ 查看:

代码语言:javascript
复制
>>> C.__base__
<class '__main__.P'>

C 的父类是类 P ,这是毫无疑问的,前面定义类 C 是就已经说明了。再看:

代码语言:javascript
复制
>>> P.__base__
<class 'object'>

在定义类 P 时,并没有注明它继承什么对象,实际上,它也有上一级,那就是类 object 。在 Python 3 中所有的类都是 object 的子类,所以,就不用在定义类的时候写这个“公共的父类”了(读者在阅读代码的时候,还可能遇到如此定义类的情况:class MyCls(object) ,在 Python 3 中,其效果与 class MyCls 等同。但是,如果在 Python 2 中,通常将 object 作为定义类时显式继承的对象,即写成 class MyCls(object) 的样式。请注意 Python 版本)。

下面列举一个示例,从中既能理解单继承的含义,也能明白为什么要继承——本节开篇未解释为什么,而是单刀直入介绍继承的写法,读者可以通过此示例理解“为什么”。

代码语言:javascript
复制
#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)

执行结果:

代码语言:javascript
复制
% python personinhe.py
Galileo's grade is 99
Galileo
27

Student 继承了类 Person ,相当于将类 Person 的代码完全搬到了类 Student 里,即如同定义了下面的类:

代码语言:javascript
复制
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 类,其效果即等同于上述代码——参数 nameage 皆来自于父类 Person 的初始化方法。

注释(2)的 grade() 方法是子类 Student 中定义的;注释(3)和(4)的两个实例方法,均在父类中定义。

试想,如果还要定义一个名为 Girl 的类,其中也有一部分代码与 Person 类相同,就可以继续继承 Person 类。如此,Person 类中的代码可以被多次重用。这就是继承的意义所在。

继承了之后,子类如果还有个性化的需要,怎么办?例如,子类 Student 需要再增加一个实例属性 school ,用以说明所属学校。若对子类 Student 进行如下修改:

代码语言:javascript
复制
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__() 方法不再显现。按照此逻辑,实例化应当这样做:

代码语言:javascript
复制
if __name__ == "__main__":
    # stu1 = Student("Galileo", 27)
    stu1 = Student("Social University")
    stu1.grade(99) 
    print(stu1.get_name()) 
    print(stu1.get_age())  

再执行程序:

代码语言:javascript
复制
 % 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 类:

代码语言:javascript
复制
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__() 方法中的参数。继续修改程序:

代码语言:javascript
复制
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)              # 增加一行

执行程序:

代码语言:javascript
复制
% 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),在后续的多继承中,会使用到替代写法的形式。

不妨将单继承的知识运用到下面的代码优化中。假设有如下所示的两个类:

代码语言:javascript
复制
#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 利用单继承的知识进行优化——请读者先试试,再看下面的代码。

代码语言:javascript
复制
class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

顿感简洁,可以发出一声惊叹了。

在此基础上,再设计一个计算正方体(六个面都是正方形,也称立方体、正六面体)的体积和表面积的类,继续使用单继承,当如何编写?思考、尝试后再看参考代码。

代码语言:javascript
复制
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

有了继承,是不是感觉编写程序的工作量减少了很多,再也不会“白头搔更短”了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-06-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老齐教室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 8.5 继承
    • 8.5.1 单继承
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档