9.6 继承和私有性

9.6.1 继承

面向对象编程的一大优点是对代码的重用(Reuse),重用的一种实现方法就是通过继承(Inheritance)机制。继承最好是想象成在类之间实现类型与子类型(Type and Subtype)关系的工具。

在之前的例子中,学校里的老师和学生可以抽象成两个类,有一些特征是他们都具有的,例如姓名、年龄和性别等。另外一些特征是他们各自独有的,例如教师的薪水、课程与假期等,学生的成绩和学费等。

学生类
属性:姓名、性别、年龄、学历、手机号码、个人账户余额等
功能:提出报名,缴费

教师类
属性;姓名、性别、年龄、学历、手机号码、员工编号等
功能:登记报名信息,分配教室等

如果为老师和学生创建两个独立的类,并分别对它们进行处理,增添上面所说的共有特征就意味着将其添加进两个独立的类,这很快就会使程序变得笨重,修改起来也容易遗漏从而产生错误。

更好的方法是创建一个公共类叫做SchoolMember,然后让老师类和学生类从这个类中继承(Inherit),也就是说他们将成为这一类型(类)的子类型,就可以向这些子类型中添加某些该类独有的特征。

SchoolMember类
属性:姓名、性别、年龄等
功能:活着

这种方法有诸多优点。如果增加或修改了SchoolMember的任何功能,它将自动反映在子类型中。举个例子,可以通过简单地向SchoolMember类进行操作,来为所有老师与学生添加一条新的ID卡字段。而对某一子类型作出的改动并不会影响到其它子类型。

同时还需要注意的是重用父类的代码,但不需要再在其它类中重复它们,当使用独立类型时才会必要地重复这些代码。

在上文设想的情况中,SchoolMember类会被称作基类(Base Class)或是超类(Superclass)或是父类。Teacher和Student类会被称作派生类(Derived Classes)或是子类(Subclass)。

Python中使用继承,在定义类时需要在类后面跟一个包含基类名称的元组。如果继承元组(Inheritance Tuple)中有超过一个类,这种情况就会被称作多重继承(Multiple Inheritance)。

class SchoolMember:
    """ 基类,学校成员类,代表任何学校里的成员。 """

    def __init__(self, name, age):
        self.name = name
        self.age = age
        print('(初始化 SchoolMember: {})'.format(self.name))


class Teacher(SchoolMember):
    """ 子类,教师类,从SchoolMember继承 """

    def __init__(self, name, age, salary):
        SchoolMember.__init__(self, name, age)
        self.salary = salary 
        print('(初始化 Teacher: {})'.format(self.name))

基类的__init__方法是通过self变量被显式调用的,因此可以初始化对象的基类部分。

因为在Teacher和Student子类中定义了__init__方法,Python不会自动调用基类SchoolMember的构造函数,必须自己显式地调用它。

相反,如果没有在一个子类中定义一个__init__方法,Python将会自动调用基类的构造函数。

这时可以通过在方法名前面加上基类名作为前缀,再传入self和其余变量,来调用基类的方法。

Python有两个内置函数可被用于继承机制:

使用 isinstance() 来检查一个实例的类型。例如:isinstance(obj, int),仅会在 obj 为 int 或某个派生自 int 的类时为 True。

使用 issubclass() 来检查类的继承关系。例如:issubclass(bool, int) 为 True,因为 bool 是 int 的子类。但是,issubclass(float, int) 为 False,因为 float 不是 int 的子类。

9.5-继承.py

# 继承


class SchoolMember:
    """ 基类,学校成员类,代表任何学校里的成员。 """

    def __init__(self, name, age):
        self.name = name
        self.age = age
        print('(初始化 SchoolMember: {})'.format(self.name))

    def tell(self):
        """ 输出对象信息 """
        print('Name:{}\tAge:{}\t'.format(self.name, self.age), end='\t')


class Teacher(SchoolMember):
    """ 子类,教师类,从SchoolMember继承 """

    def __init__(self, name, age, salary):
        SchoolMember.__init__(self, name, age)
        self.salary = salary
        print('(初始化 Teacher: {})'.format(self.name))

    def tell(self):
        SchoolMember.tell(self)
        print('Salary: {}'.format(self.salary))


class Student(SchoolMember):
    """ 子类,学生类,从SchoolMember继承 """

    def __init__(self, name, age, marks):
        SchoolMember.__init__(self, name, age)
        self.marks = marks
        print('(初始化 Student: {})'.format(self.name))

    def tell(self):
        SchoolMember.tell(self)
        print('Marks: {}'.format(self.marks))


print('初始化过程:')
t = Teacher('张老师', 40, 10000)
s = Student('李四', 25, 75)

print('\n对象调用方法:')
# 子类对象调用各自子类方法
t.tell()
s.tell()

print('\n检查实例类型:isinstance')
print('isinstance(t, Teacher):', isinstance(t, Teacher))
print('isinstance(t, SchoolMember):', isinstance(t, SchoolMember))

print('\n检查类的继承关系:issubclass')
print('issubclass(Student, SchoolMember):', issubclass(Student, SchoolMember))
print('issubclass(Student, Teacher):', issubclass(Student, Teacher))

运行结果为:

初始化过程:
(初始化 SchoolMember: 张老师)
(初始化 Teacher: 张老师)
(初始化 SchoolMember: 李四)
(初始化 Student: 李四)

对象调用方法:
Name:张老师     Age:40          Salary: 10000
Name:李四       Age:25          Marks: 75

检查实例类型:isinstance
isinstance(t, Teacher): True
isinstance(t, SchoolMember): True

检查类的继承关系:issubclass
issubclass(Student, SchoolMember): True
issubclass(Student, Teacher): False

9.6.2 成员私有性

和有的面向对象编程语言不同,Python没有关键字来标明变量的私有化情况,Python使用下划线来进行一定的私有化表示。

例如,有一个变量原本的标识符为xx,则:

xx:公有变量。

__xx:双前置下划线,私有化变量,避免与子类中的属性命名冲突,无法在外部直接访问,使用 _Class__object可以访问。

一些其他用法如下:

_xx:单前置下划线,常用于模块中,在一个模块中以单下划线开头的变量和函数被默认当作内部函数,使用from somemodule import * 时不会被导入。

xx_:单后置下划线,没有特别的含义,常用于避免与Python关键词的冲突。

__xx__:双前后下划线,用户名字空间的魔法对象或属性。例如:__init__

例如:test.py

aaa = 111
_bbb = 222
__ccc = 333


class Student:
    aa = 11
    _bb = 22
    __cc = 33

    def __init__(self, a, b, c):
        self.a = a
        self._b = b
        self.__c = c

在另一文件中引入,test1.py

import sys
import test

s = test.Student(1, 2, 3)
try:
    print(s.a)
    print(s._b)
    print(s.__c)
except (AttributeError, NameError):
    print(sys.exc_info()[0])

try:
    print(test.Student.aa)
    print(test.Student._bb)
    print(test.Student.__cc)
except (AttributeError, NameError):
    print(sys.exc_info()[0])

try:
    print(test.aaa)
    print(test._bbb)
    print(test.__ccc)
except (AttributeError, NameError):
    print(sys.exc_info()[0])

执行结果为:

1
2
<class 'AttributeError'>
11
22
<class 'AttributeError'>
111
222
333

如果采用 from ... import * 方式引入模块,test1.py

import sys
from test import *

s = Student(1, 2, 3)
try:
    print(s.a)
    print(s._b)
    print(s.__c)
except (AttributeError, NameError):
    print(sys.exc_info()[0])

try:
    print(Student.aa)
    print(Student._bb)
    print(Student.__cc)
except (AttributeError, NameError):
    print(sys.exc_info()[0])

try:
    print(aaa)
    print(_bbb)
except (AttributeError, NameError):
    print(sys.exc_info()[0])

try:
    print(__ccc)
except (AttributeError, NameError):
    print(sys.exc_info()[0])

执行结果为:

1
2
<class 'AttributeError'>
11
22
<class 'AttributeError'>
111
<class 'NameError'>
<class 'NameError'>