第7章 面向对象编程

7.1 问题:如何实现不重复造轮子?

7.2 类与实例

在面向对象编程中,首先编写类,然后,基于类创建实例对象,并根据需要给每个对象一些其它特性。

7.2.1 创建类

创建类的格式如下:
class class_name:
'''类的帮助信息''' #类文档字符串
statement #类体
定义类无需def关键字,类名后也无需小括号(),如果要继承其它类,要添加小括号,类的继承后面将介绍。
下面以创建表示人的类,它保存人的基本信息及使用这些信息的方法。

创建类要注意的几个问题:
①按约定,在Python中,类的首字母一般大写
②方法__init__()
类中的函数称为方法,__init__()是一个特殊方法,init的前后都是两个下划线,被称为类的构造函数或初始化方法,实例化类时将自动调用该方法。
在方法__init__()中,有三个形参,分别是self、name、age,其中self表示实例本身,而且必须放在其它形参的前面,调用方法时,该参数将自动传入,所以调用方法时,无需写这个实参。self与实例的关系,如图7-1所示。

图7-1 self表示实例本身
③形参name、age
把这两个形参,分别赋给两个带self前缀的两个变量,即self.name、self.age。带self前缀的变量,将与实例绑定,类中的所有方法都可调用它们。这样的变量又称为实例属性。
④方法display()
方法display()只有一个self形参,它引用了两个实例属性。

7.2.2 创建类的实例

其它编程语言实例化类一般用关键字 new,但在 Python 中无需这个关键字,类的实例化类似函数调用方式。以下将类Person实例化,并通过 __init__() 方法接收参数、初始化参数。

根据类Person创建实例p1,使用实参"李白",28调用方法__init__()。访问实例中的方法或属性,使用实例名加句点的方法即可,比如方法name属性及display()方法。

根据实参可以创建不同的实例

7.2.3 访问属性

属性根据在类中定义的位置,又可分为类的属性和实例属性。类属性是定义在类中,但在各方法外的属性。实例属性是方法内,带self前缀的属性。
(1)创建类
在类Person定义一个类属性percount,如下列代码。

(2)实例化并访问类属性和实例属性

类属性可以通过类名或实例名访问。

7.2.4 访问限制

类Person中pernum是类的属性,因各实例都可访问,又称为类的公有属性,公有属性各实例可以访问,也可以修改。如下例

这样对一些属性就不安全了,为了提高一些类属性或实例属性的安全级别,可以设置私有属性,只要命名时,加上两个下划线为前缀即可,如:__percount。私有属性只能在类内部访问,实例不能访问。

类的私有属性__percount、实例的私有属性__pwd只能在类的内部使用,实例及类的外部不能访问。

7.2.5类的专有方法

__init__ : 构造函数,在生成对象时调用
__del__ : 析构函数,释放对象时使用

7.3 继承

继承是面向对象的重要特征之一,继承是两个类或者多个类之间的父子关系,子进程继承了父进程的所有公有实例变量和方法。继承实现了代码的重用。重用已经存在的数据和行为,减少代码的重新编写,python在类名后用一对圆括号表示继承关系, 括号中的类表示父类,如果父类定义了__init__方法。带双下划线 __ 的方法都是特殊方法,除了 __init__ 还有很多,几乎所有的特殊方法(包括 __init__)都是隐式调用的(不直接调用)。则子类必须显示地调用父类的__init__方法,如果子类需要扩展父类的行为,可以添加__init__方法的参数。下面演示继承的实现

运行结果:
fruit's color: red
apple's color: red
grow ...
fruit's color: yellow
banana's color: yellow
banana grow...

7.4 调用父类的init方法

子类(派生类)并不会自动调用父类(基类)的init方法,需要在子类中调用父类的init函数。
(1)如果子类没有定义自己的初始化函数,父类的初始化函数会被默认调用;但是如果要实例化子类的对象,则只能传入父类的初始化函数对应的参数,否则会出错。

(2)如果子类定义了自己的初始化函数,而在子类中没有显示调用父类的初始化函数,则父类的属性不会被初始化

在子类中没有显示调用父类的初始化函数,则父类的属性不会被初始化,因而此时调用子类中name属性不存在:
AttributeError: ‘Child’ object has no attribute ‘name’

(3)如果子类定义了自己的初始化函数,在子类中显示调用父类,子类和父类的属性都会被初始化。

子类定义了自己的初始化函数,显示调用父类,子类和父类的属性都会被初始化的输出结果:
create an instance of: Parent
name attribute is: tom
call __init__ from Child class
create an instance of: Child
name attribute is: data from Child
data from Child

(4) 调用父类的init方法
方法1,父类名硬编码到子类

方法2,利用super调用

运行结果
Parent
Child
HelloWorld from Parent
Child bar fuction
I'm the parent.

7.5 把类放在模块中

为了永久保存函数,需要把函数存放在模块中。同样,要保存类,也需要把定义类的脚本保存到模块中,使用时,根据需要导入相关内容。

7.5.1 导入类

把定义类Person及Student的代码,保存在当前目录的文件名为class_person的py文件中。通过import语句可以导入我们需要的类或方法或属性等。

7.5.2 在一模块中导入另一个模块

创建名为train_class.py的主程序,存放在当前目录下,在主程序中导入模块class_person中的Student类,具体代码如下:

在命令行运行该主程序:

输入一所大学名称: 清华大学
Student(姓名:张华,年龄:21,所在大学:清华大学)

7.6 实例1:使用类和包

这节通过几个实例来加深大家对Python相关概念的理解和使用。

7.6.1 概述

创建一个Person父类,两个继承这个父类的子类:Student和Tencher,它们之间的关系如图7-3 所示。

图7-3 类之间的继承关系

7.6.2 实例功能介绍

(1)创建Person类
属性有姓名、年龄、性别,创建方法displayinfo,打印这个人的信息。
(2)创建Student类
继承Person类,属性所在大学college,专业profession,重写父类displayinfo方法,调用父类方法打印个人信息外,将学生的学院、专业信息也打印出来。
(3)创建Teacher类
继承Person类,属性所在学院college,专业profession,重写父类displayinfo方法,调用父类方法打印个人信息外,将老师的学院、专业信息也打印出来。
(4)创建二个学生对象,分别打印其详细信息
(5)创建一个老师对象,打印其详细信息

7.6.3 代码实现

代码放在当前目录的createclasses,具体包括存放__init__.py和classes.py。另外,在
当前目录存放主程序run_inst.py。以下是各模块的详细实现。
(1)模块classes.py的代码

(2)主程序run_inst.py代码

7.9 练习

(1)高铁售票系统
高铁某车厢有13行、每行有5列,每个座位初始显示“有票”,用户输入座位(如9,1)后,按回车,对应座位显示为“已售”。
(2)创建一个由有序数值对(x, y) 组成的 Point 类,它代表某个点的 X 坐标和 Y 坐标。X 坐标和 Y 坐标在实例化时被传递给构造函数,如果没有给出它们的值,则默认为坐标的原点。
(3)创建一个名为User的类,其中包含属性first_name和last_name,还有用户简介通常会存储的其他几个属性。在类User中定义一个名为describe_user()的方法,它打印用户信息摘要;再定义一个名为greet_user()的方法,它向用户发出个性化的问候。
创建用户实例,调用上述两个方法。