Python面向对象编程

类是对现实世界中一些事物的封装,对象是事物存在的实体。Python是一门面向对象的程序设计语言,在Python中,一切都可以视为对象,即不仅人或具体的事物称为对象,字符串、函数、列表等也都是对象。Python可以用方便的方法创建类和对象。对初学者来说,面向对象比较抽象,需要先掌握面向对象的一些基本概念,形成基本的面向对象思想。理解面向对象的基本概念有助于更好地学习和使用Python。

6.1 面向对象的基本概念

Python中对象分为类对象和实例对象。面向对象的基本概念有很多:类、对象、实例、属性、方法、继承、重写等,具体概念如下:

  • 类:用来描述具有相同的属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法。
  • 实例:类的实例,也称实例对象。
  • 实例化:创建一个类的实例对象的过程。
  • 方法:类中定义的函数,通常分为类方法和实例方法。
  • 属性:类中定义的变量,在函数体之外,用于描述对象的特征,也称类变量。
  • 继承:子类拥有父类(也可以叫超类或者基类)的属性和方法称为继承,子类也可以拥有自己的方法和属性。
  • 重写:子类从父类中继承的方法不能够满足需求时,可以对父类的方法进行改写,称为子类对父类的重写、重载或者覆盖。
  • 封装:类具有封装性,封装性的意思是类内部实现不需要被外界知晓,只需要提供必要的接口供外界调用即可。
  • 多态:相同行为支持不同类型对象,产生的结果也不同。

Python面向对象的机制比其他编程语言简单,但是也需要掌握好这些基本概念才能进行更深入的学习。

6.2 定义和使用类

之前章节中学习过的字符串、列表、元组、字典、集合,都是类对象,可义使用type()函数返回对象的类型。除此之外,Python中可以根据需求来自定义类,class语句作为定义类的关键字,类中使用赋值语句定义类属性,使用def关键字定义类方法。在使用类时,需要先定义类,再创建类的实例,通过类的实例就可以访问类中的属性和方法。

6.2.1 定义类

定义类的语法如下:

class 类名:
	赋值语句
	赋值语句
	......
	def 定义函数
	def 定义函数
    ......

类是一种自定义类型,使用class关键字作为定义类的开头,后面加类的名称,类的名称与定义函数的名称有一定区别,类名首字母需要大写,建议使用大驼峰命名方式(如果需要使用多个单词表示类名时每个单词首字母大写),使用赋值语句创建类变量(类属性),使用def关键字定义类的函数(类方法),示例如下:

class People:
    # 定义属性
    sex = 'woman'

    # 定义初始化方法
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height

    def speak(self):
        print("%s 说: 我 %d 岁, 身高 %d 厘米" % (self.name, self.age, self.height))

类中定义属性和方法无需分先后顺序,上述示例定义了People类,其中sex为类变量(类属性),speak()为类方法,__ init __()为初始化方法或者称为构造方法。

6.2.2 使用类

调用类名创建类的实例对象,使用类对象和实例对象可以访问类的属性和方法,示例如下:

# 调用People类创建一个实例对像
>>> lucy = People('lucy', 25, 170) # 调用类对象创建一个实例对象
>>> lucy.speak() # 实例对象调用类方法
lucy 说:25, 身高 170 厘米
>>> print(lucy.sex) # 实例对象调用类属性
woman

# 再创建一个实例对象
>>> lily = People('lily', 24, 171) # 调用类对象创建一个实例对象
>>> lily.speak() # 实例对象调用类方法
lily 说:24, 身高 171 厘米
>>> print(lily.sex) # 实例对象调用类属性
woman

6.3 类和对象

由上例可知,每个人都有自己的姓名,年龄,性别,身高,这些可以看作是每个人(实例对象)共有的属性;每个人都有说话,吃饭,睡觉这些动作,动作就是方法,也就是函数。拥有这些共同的属性和方法的个体都可以被划分到人类中。人类是一个类对象,每个具体的人是一个实例对象。

Python中万物皆对象,对象分为类对象和实例对象。类对象在执行class语句时创建。使用类名称可以调用类对像,类对象也称作类实例。调用类对象可以创建类的实例对象。类对象只能有一个,实例对象可以创建多个。类对象和类的实例对象分别拥有自己的命名空间,它们在各自的命名空间内使用对象的属性和方法。

6.3.1 类对象

类对象具有以下几个主要特点:

  • Python在执行class语句时创建一个类对象和一个变量(与类同名),变量引用类对象。与def语句类似,class语句也是可执行语句。导入模块时会执行class语句。
  • 类中的赋值语句创建的变量是类的数据属性也称作类变量或者类属性。类的数据属性使用"对象名.属性名"格式访问。对象名可以是类对象名,也可以是实例对象名。
  • 类中的顶层def语句定义的函数是类的方法,使用"对象名.方法名()"格式来访问。
  • 类属性可以被类的所有实例对象共享。类的实例对象可读取类的属性值,但不能通过赋值语句修改类的属性值。可以为"类对象名.属性"赋值对类属性进行修改。

6.3.2 实例对象

实例对象具有以下几个主要特点:

  • 实例对象通过调用类对象来创建。
  • 每个实例对象继承类对象的所有属性,并获得自己的命名空间。
  • 实例对象拥有私 属性。通过赋值语句为实例对象的属性赋值时,如果该属性不存在,就会创建属于实例对象的私有属性。

6.4 属性和方法

Python把类中的变量和函数统称为属性,分别称为数据属性和方法属性:其中数据属性也叫类变量,也可以叫类属性,是定义类时用赋值语句创建的变量;方法属性也叫类方法,是类中定义的函数,表示的是对象的行为或者动作。

6.4.1 对象的属性

在Python中,实例对象拥有类对象的所有属性,可以使用dir()函数来查看对象的属性,示例如下:

# 定义类
class People:
    # 定义属性
    sex = 'woman'

    # 定义初始化方法
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height

    def speak(self):
        print("%s 说: 我 %d 岁, 身高 %d 厘米" % (self.name, self.age, self.height))
        
# 使用dir()函数查看类对象的属性
>>> print(dir(People))
['__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__', 'sex', 'speak']

# 使用dir()函数查看实例对象的属性
>>> lily = People('lily', 24, 171)
>>> print(dir(lily))
['__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', 'height', 'name', 'sex', 'speak']

由上例可以看出:

  1. 实例对象拥有类对象的所有属性,类变量是属于全局的,并可以通过实例对象调用。

sex = 'woman’定义了类对象的数据属性sex,该属性的值可以通过类对象和实例对象调用。

但是如果需要修改sex的属性值时只能通过为"类对象名.属性"赋值修改,示例如下:

# 类对象名.属性 = 值
>>> People.sex = 'male' # 修改类对象的属性
>>> print(People.sex) # 调用类对象的属性
male

如果通过为"实例对象名.属性"进行赋值修改,实际上是为实例对象创建私有属性,并不能修改类属性,示例如下:

# 实例对象名.属性 = 值
>>> lilei = People('lilei', 24, 180) # 创建类的实例对象
>>> lilei.sex = 'male' # 通过实例对象.属性修改
>>> print(lilei.sex) # 调用实例对象的属性,为实例对象创建私有变量
male
>>> print(People.sex) # 调用类对象的属性,类对象的属性并未被修改
woman
  1. 类对象和实例对象的属性有差别,实例对象的属性拥有’age’, ‘height’, 'name’在类对象中没有,这三个属性是在 __ init __ 初始化函数中定义的,为实例对象的私有属性,调用类创建实例对象时需要为实例对象传递这些属性值,否则无法创建实例对象。每个实例对象的这些属性都是不同的,示例如下:
# 创建实例对象lilei
>>> lilei = People('lilei', 24, 180)
>>> print(lilei.name)
lilei
>>> print(lilei.age)
24
>>> print(lilei.height)
180

# 创建实例对象lily
>>> lily = People('lily', 24, 171)
lily
>>> print(lily.name)
24
>>> print(lily.age)
171
>>> print(lily.height)
  1. 类对象和实例对象创建完成后也可以为类对象和实例对象添加属性,示例如下:
# 为类对象添加属性
>>> People.weight = 60
>>> print(People.weight) # 使用类对象调用类属性
60
# 调用类对象创建实例对象
>>> lucy = People('lucy', 25, 171) # 使用实例对象调用添加的类属性
>>> print(lucy.weight)
60

# 使用dir()函数查看类属性
>>> print(dir(People))
['__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__', 'sex', 'speak', 'weight']

6.4.2 对象的方法

类中定义的函数就是类的方法,可以使用类对象调用类的方法,也可以使用实例对象调用类的方法,示例如下:

# 定义类进行加法运算
class Add:
	def add(x, y):
		return x + y
    
	def add2(self, x, y):
    	return x + y  
    
# 通过类对象调用类方法
>>> print(Add.add(2, 3))
5

# 通过实例对象调用类方法
>>> a = Add()
>>> print(a.add2(2, 3))
5

由上例可知,使用实例对象调用类方法时,Python会创建一个叫作实例方法对象的特殊对象,该对象也称作绑定方法对象。当前实例对象会作为一个参数传递给实例方法对象,所以在定义方法时,第一个参数名称通常为self,self代表类的实例,使用self只是惯例,也可以使用其他名称来代替,重要的是它的位置。

使用类对象调用方法时不会将类对象传递给方法,只需要传入方法要求的形式参数即可进行运算,这是与通过实例对象调用方法的区别。

6.4.3 特殊的属性和方法

类方法和属性除了自定义的属性和方法之外还有一系列其他属性和方法,可以用dir()函数查看,实例如下:

# 定义Dog类
class Dog:
    '''
     定义Dog类
    '''
	# 定义类属性
    name = ''
    age = 0
	
	# 定义类方法
    def eat(self):
        pass

    def sleep(self):
        pass
# 使用dir()查看类属性和方法
>>> print(dir(Dog))
['__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', 'eat', 'name', 'sleep']

如上例所示,除了自定义的’age’, ‘eat’, ‘name’, 'sleep’以外,Python还为自定义类添加了一系列特殊属性和方法:

特殊属性:

  • __ class __:返回类对象的类型名称。
  • __ dict __:返回包含类命名空间的字典。
  • __ doc __:返回类的文档字符串。
  • __ module __:返回类所在模块的名称。
  • __ name __: 返回类的名称。
  • __ bases __:返回包含基类的元组。

示例如下:

>>> print(Dog.__class__) # 返回类对象的类型名称
<class 'type'> # Python中用户定义类,都是type类的实例

>>> print(Dog.__dict__) # 返回包含类命名空间的字典
{'__module__': '__main__', 'name': '', 'age': 0, 'eat': <function Dog.eat at 0x000002BD73AFAD30>, 'sleep': <function Dog.sleep at 0x000002BD0C4385E0>, '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None}

>>> print(Dog.__doc__) # 返回类的文档字符串
定义Dog类

>>> print(Dog.__module__) # 返回类所在模块的名称
__main__

>>> print(Dog.__name__) # 返回类的名称
Dog

>>> print(Dog.__bases__) # 返回包含基类的元组
(<class 'object'>,)

特殊方法:

  • __ delattr __():执行del 对象名.属性时调用,删除该对象。
  • __ dir __():执行dir()函数时调用,返回类的属性和方法。
  • __ format __():执行内置函数formae()和str.format()时被调用,返回对象的格式话字符。
  • __ getattribute __():访问对象属性时调用。
  • __ hash __():执行内置函数hash()时调用。
  • __ init __():类的初始化方法,调用类时会首先执行该方法。
  • __ new __():创建一个新的实例对象,然后调用 _ _ init _ _()方法执行初始化操作,完成初始化后再返回实例对象,同时建立变量对实例对象的引用。
  • __ repr __():调用内置函数repr()时调用,返回对象的字符串表示。
  • __ str __():调用函数str()、print()、format()时调用,返回对象的字符串表示。
  • __ setattr __():为对象属性赋值时调用。
  • __ eq __():执行比较运算符"=="时调用。
  • __ ge __():执行比较运算符">="时调用。
  • __ gt __():执行比较运算符">"时调用。
  • __ le __():执行比较运算符"<="时调用。
  • __ lt __():执行比较运算符"<"时调用。
  • __ ne __():执行比较运算符"!="时调用。

6.4.4 “伪私有”属性和方法

在Python中使用"对象名.属性"和"对象名.方法名"可以调用类对象和实例对象的属性和方法,一定程度上会破坏类的封装性,为了解决这个问题,Python又提供了"私有"属性和方法。“私有"属性和方法不能直接在类的外部被调用,一定程度上起到"保护隐私"的作用。“私有"属性和方法实现非常简单,只需要在属性和方法前加双下划线即可:”__属性 = 属性值”,示例如下:

# 定义类
class People:
    # 定义属性
    sex = 'woman'
    __weight = '100kg' # 定义类对象的私有属性

    # 定义初始化方法
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height
        
    # 定义私有方法
    def __speak(self):
        print("%s 说: 我 %d 岁, 身高 %d 厘米" % (self.name, self.age, self.height))
# 使用类对象调用类属性和方法
>>> print(People.__weight)
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    print(People.__weight)
AttributeError: type object 'People' has no attribute '__weight'

使用"类对象.属性名"无法调用私有属性" __weight",一定程度起到了封装的作用。但是对"私有"属性也不是完全没有办法进行访问,可以使用" _ 类名. _ _ 私有属性(方法)名"的方式访问"私有",因此在Python中私有属性也称为"伪私有",示例如下:

>>> print(People._People__weight) # _类名.__私有属性名访问私有属性
100kg

>>> lily = People('lily', 25, 171) #  _类名.__私有方法名访问私有方法
>>> lily._People__speak()
lily 说:25, 身高 171 厘米

使用dir()函数查看类对象和实例对象的属性和方法时其中包含了"私有"属性和方法的真正名称,示例如下:

# 使用dir()函数查看类对象的私有属性和方法
>>> print(dir(People))
['_People__speak', '_People__weight', '__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__', 'sex']

>>> lily = People('lily', 25, 171)
>>> print(dir(lily))
['_People__speak', '_People__weight', '__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', 'height', 'name', 'sex']

6.4.5 初始化方法

定义类时需要使用到 __ init __ ()方法,用于完成对象的初始化。如果没有为类定义初始化方法,Python会自动添加该方法。在调用类对象时,__ init __ ()方法会首先被调用对实例对象进行初始化操作,为实例对象创建的私有属性也需要在该方法中定义,示例如下:

# 定义类
class People:
    # 定义属性
    sex = 'woman'
    # 定义类对象的私有属性
    __weight = '100kg'

    # 定义初始化方法
    def __init__(self, name, age, height): 
        self.name = name # 为实例对象创建私有属性
        self.age = age
        self.height = height
        
    # 定义方法
    def speak(self):
        print("%s 说: 我 %d 岁, 身高 %d 厘米" % (self.name, self.age, self.height))
>>> jhon = People('Jhon', 20, 181) # 调用类对象创建实例对象
>>> print(jhon.name) # 实例对象初始化的属性
Jhon
>>> print(jhon.age) # 实例对象初始化的属性
20
>>> print(jhon.height) # 实例对象初始化的属性
181
>>> print(jhon.sex)
woman

初始化 __ init __ (self, name, age, height)方法中定义了name, age, height三个参数,在调用类对象创建实例对象时需要传入相应的参数,这些参数传入后成为实例对象的属性,只有该实例对象可以调用这些属性。

除此之外,还有 __ new __ ()方法,__ new __ () 是一种负责创建类实例的静态方法,它无需使用 staticmethod 装饰器修饰,且该方法会优先 __ init __ () 初始化方法被调用。__ new __ ()方法接受的参数虽然和 __ init __ ()一样,但 __ init __ ()是在类实例创建之后调用,而 __ new __ () 方法正是创建这个类实例的方法,它会将所请求实例所属的类作为第一个参数,其余的参数会被传递给对象构造器表达式 (对类的调用)。__ new__ ()的返回值应为新对象实例 (通常是 cls的实例)。一般情况下,覆写 __ new__ () 的实现将会使用合适的参数调用其超类的 super().__ new__ (),并在返回之前修改实例。

6.4.6 静态方法

定义类方法时可以使用@staticmethod语句将方法声明为静态方法。静态方法不会接收隐式的第一个参数,静态方法的调用可以在类对象上进行,也可以在实例对象上进行 ,通过类对象和实例对象调用静态方法的效果完全相同。通过实例对象调用静态方法时,不会像正常方法一样将self作为第一个参数传递给方法,示例如下:

# 定义类
class People:

    @staticmethod # 定义静态方法
    def speak(name):
        print('{}说:“好好学习,天天向上”'.format(name))
# 通过类对象调用静态方法
>>> People.speak('李雷')
lilei说:“好好学习,天天向上”

# 通过实例对象调用静态方法
>>> lilei = People()
>>> lilei.speak('李雷')
李雷说:“好好学习,天天向上”

6.5 类的继承

Python 支持类的继承,通过继承,子类可以使用父类的属性和方法。子类也叫派生类(DerivedClassName),父类也叫基类(BaseClassName)或者超类。

继承的基本语法如下:

class DerivedClassName(BaseClassName1, BaseClassName2...): # 将继承的父类放在圆括号中
    <statement-1>
    ...
    <statement-N>

6.5.1 简单继承

简单继承是指只从一个父类中继承,语法如下:

class DerivedClassName(BaseClassName): # 仅从一个父类中继承
    <statement-1>
    ...
    <statement-N>

子类继承父类所有的属性和方法,包括父类的"私有"属性和方法,示例如下:

# 定义People类
class People:
    # 定义属性
    sex = 'Fmale'
    # 定义类对象的私有属性
    __weight = '50kg'

    # 定义初始化方法
    def __init__(self, name, age, height): 
        self.name = name # 为实例对象创建私有属性
        self.age = age
        self.height = height
        
    # 定义方法
    def speak(self):
        print("%s 说: 我 %d 岁, 身高 %d 厘米" % (self.name, self.age, self.height))
	
    # 定义私有方法
    def __eat(self, food):
        print("%s 今天早饭吃了 %s" % (self.name, food))
    
    
# 定义子类Student
class Student(People):
	pass    
# 查看父类People的属性和方法
>>> print(dir(People))
['_People__eat', '_People__weight', '__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__', 'sex', 'speak']

# 查看子类Student的属性和方法
>>> print(dir(Student))
['_People__eat', '_People__weight', '__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__', 'sex', 'speak']

使用子类调用父类的属性和方法,示例如下:

# 使用子类的类对象调用父类的类属性
>>> print(Student.sex)
Fmale

# 使用子类的类对象调用父类的私有属性
>>> print(Student._People__weight)
50kg

# 使用子类实例对象调用父类的属性和方法
>>> lilei = Student('lilei', 20, 181) # 创建子类的实例对象

>>> print(lilei.sex) # 使用子类的实例对象调用父类的属性
Fmale

>>> lilei.speak() # 使用子类的实例对象调用父类的方法
lilei 说:20, 身高 181 厘米
    
>>> print(lilei._People__weight) # 使用子类的实例对象调用父类的私有属性
50kg

>>> lilei._People__eat('bread') # 使用子类的实例对象调用父类的私有方法
lilei 今天早饭吃了 bread

子类除了可以继承父类的属性和方法外,还可以自定义属性和方法,示例如下:

# 定义People类
class People:

    # 定义初始化方法
    def __init__(self, name, age, height):
        self.name = name  # 为实例对象创建私有属性
        self.age = age
        self.height = height

    # 定义方法
    def speak(self):
        print("%s 说: 我 %d 岁, 身高 %d 厘米" % (self.name, self.age, self.height))
    
# 定义子类Student
class Student(People):
    
    def study(self, hour):
        print('{}每天都要学习个{}小时'.format(self.name, hour))
# 调用子类自定义的方法
>>> lilei = Student('lilei', 20, 181) # 创建子类的实例对象
>>> lilei.study(2.5) # 使用实例对象调用子类的study()方法
lilei每天都要学习2.5小时

6.5.2 重写\覆盖

如果子类中定义的属性和方法与父类中的属性和方法同名时会覆盖父类的同名属性和方法,这称为方法的重写或覆盖,示例如下:

# 定义People类
class People:

    # 定义初始化方法
    def __init__(self, name, age, height):
        self.name = name 
        self.age = age
        self.height = height

    # 定义方法
    def speak(self):
        print("%s 说: 我 %d 岁, 身高 %d 厘米" % (self.name, self.age, self.height))


# 定义子类Student
class Student(People):
    
    def speak(self, hour): # 重写父类的同名方法
        print('{}说:“我每天都要学习{}个小时”'.format(self.name, hour)) # 定义People类
# 调用子类创建实例对象
>>> lilei = Student('lilei', 20, 181)
>>> lilei.speak(2.5)
lilei说:“我每天都要学习2.5个小时”

6.5.3 调用父类的初始化函数

子类中没有定义初始化方法时,调用子类创建实例对象时会调用父类的初始化方法,示例如下:

# 定义People类
class People:

    # 定义初始化方法
    def __init__(self, name, age, height):
        self.name = name  
        self.age = age
        self.height = height

    # 定义方法
    def speak(self):
        print("%s 说: 我 %d 岁, 身高 %d 厘米" % (self.name, self.age, self.height))


# 定义子类Student
class Student(People):
	pass
# 调用子类的类对象创建实例对象
>>> lilei = Student() # 子类无初始化方法,调用父类的初始化方法
Traceback (most recent call last):
  File "test.py", line 19, in <module>
    lilei = Student()
TypeError: __init__() missing 3 required positional arguments: 'name', 'age', and 'height'
# 调用父类的初始化方法,需要传入父类 __init__()方法中要求的参数,否则会报错!!!

# 调用子类的类对象创建实例对象
>>> lilei = Student('lilei', 20, 181) # 传入父类初始化方法要求的参数
>>> lilei.speak()
lilei 说:20, 身高 181 厘米

可以在子类中定义自己的初始化方法,示例如下:

# 定义People类
class People:
    # 定义初始化方法
    def __init__(self, name, age, height):
        self.name = name  
        self.age = age
        self.height = height

    # 定义方法
    def speak(self):
        print("%s 说: 我 %d 岁, 身高 %d 厘米" % (self.name, self.age, self.height))


# 定义子类Student
class Student(People):

    def __init__(self, grade, class_num):
        self.grade = grade
        self.class_num = class_num
        
    def study(self):
        print('在{}年级{}班。'.format(self.grade, self.class_num))
>>> lily = Student(3, 2) # 创建实例对象,传入初始化方法要求的参数
>>> lily.study() # 实例对象调用子类的方法3年级2班。

也可以在子类的初始化函数中调用父类的初始化函数,使用super()函数返回父类的类对象,通过它访问父类的方法,示例如下:

# 定义People类
class People:
    # 定义初始化方法
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height


# 定义子类Student
class Student(People):

    def __init__(self, grade, class_num, name, age, height): # 定义子类的初始化函数
        self.grade = grade
        self.class_num = class_num
        super().__init__(name, age, height) # 调用父类的初始化方法

    def study(self):
        print('{}今年{}岁,身高{}厘米,在{}年级{}班学习。'
              .format(self.name, self.age, self.height, self.grade, self.class_num))
>>> lily = Student(3, 2, 'lily', 20, 170) # 创建子类实例对象
>>> lily.study()
lily今年20岁,身高170厘米,在3年级2班学习。

super()函数还可以调用父类的其他方法,示例如下:

# 定义People类
class People:
    # 定义初始化方法
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height

    # 定义方法
    def speak(self):
        print("%s说:我%d岁,身高%d厘米。" % (self.name, self.age, self.height))


# 定义子类Student
class Student(People):

    def __init__(self, grade, class_num, name, age, height):
        self.grade = grade
        self.class_num = class_num
        super().__init__(name, age, height) 

    def study(self):
        super().speak() # 使用super()函数调用父类的方法
        People.speak(self) # 调用父类的方法,功能同上
        print('{}今年{}岁,身高{}厘米,在{}年级{}班学习。'
              .format(self.name, self.age, self.height, self.grade, self.class_num))
>>> lily = Student(3, 2, 'lily', 20, 170)
>>> lily.study()
lily说:我20岁,身高170厘米。 # super().speak()的执行结果
lily说:我20岁,身高170厘米。 # People.speak(self)的执行结果
lily今年20岁,身高170厘米,在3年级2班学习。

6.5.4 多重继承

子类可以同时从多个父类中继承,这称为多继承,语法如下:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    ...
    <statement-N>

需要注意圆括号中父类的顺序,如果父类中有同名的属性或方法,在子类使用时未具体指定父类,Python会按照从左至右的顺序在各个父类中进行搜索 ,示例如下:

# 定义People类
class People:
    # 定义初始化方法
    def __init__(self, sex):
        self.sex = sex

    # 定义方法
    def speak(self):
        print("我的性别是{}" .format(self.sex))


# 定义子类Asian继承People
class Asian(People):

    def __init__(self, hair_color, eye_color, sex):
        self.hair_color = hair_color
        self.eye_color = eye_color
        super().__init__(sex) # 调用父类的初始化函数

    def speak(self):
        print('亚洲{}性的眼睛是{}色,头发是{}色。'.format(self.sex, self.eye_color, self.hair_color))

    def talk(self):
        print('我是中国人,中文是我的母语。')


# 定义子类Teacher
class Teacher:

    def __init__(self, age, course):
        self.age = age
        self.course = course

    def teach(self):
        print('我今年{}岁,在学校教{}课程'.format(self.age, self.course))

    def speak(self):
        print('我在学校教{}课程'.format(self.age, self.course))


class ChineseTeacher(Asian, Teacher): # 多继承

    def __init__(self, name, grade, hair_color, eye_color, sex, age, course):
        self.name = name
        self.grade = grade
        Asian.__init__(self, hair_color, eye_color, sex)
        Teacher.__init__(self, age, course)
>>> lilei = ChineseTeacher('李雷', 3, '黑', '黑', '男', 30, '语文')
>>> lilei.speak() # 调用Asian类中的speak方法
亚洲男性的眼睛是黑色,头发是黑色。

由上例可知,子类ChineseTeacher继承父类Asian和Teacher,调用ChineseTeacher类创建实例化对象后调用speak()方法,父类中有同名的属性或方法会按照从左至右的顺序在各个父类中进行搜索,所以首先调用的是Asian类的speak()方法。

更多推荐

Python面向对象编程