Python面向对象程序设计讲座【修订】
概述
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
【附、面向过程的程序设计、事件驱动程序设计、面向对象程序设计与函数式程序设计
☆面向过程的程序设计(Process oriented programming),也称为结构化程序设计(Structured programming),有时会被视为是指令式编程(Imperative programming)的同义语。。编写程序可以说是这样一个过程:从系统要实现的功能入手把复杂的任务分解成子任务,把子任务再分解成更简单的任务,层层分解来完成。可以采用函数(function)或过程(procedure)一步步细化调用实现。程序流程结构可分为顺序(sequence)、选择(selection)及重复(repetition)或循环(loop)。
许多语言可以支持过程式程序设计,Python也可以, Python中不用类的常见的程序设计就是。
☆事件驱动程序设计(Event-driven programming)
基于事件驱动的程序设计在图形用户界面(GUI)出现很久前就已经被应用于程序设计中,可是只有当图形用户界面广泛流行时,它才逐渐形演变为一种广泛使用的程序设计模式。
在过程式的程序设计中,代码本身就给出了程序执行的顺序,尽管执行顺序可能会受到程序输入数据的影响。
在事件驱动的程序设计中,程序中的许多部分可能在完全不可预料的时刻被执行。往往这些程序的执行是由用户与正在执行的程序的互动激发所致。
事件驱动常常用于用户与程序的交互,通过图形用户接口(鼠标、键盘、触摸板)进行交互式的互动。属于事件驱动的编程语言有:VB、C#、Java(Java Swing的GUI)、Python基于tkinter的GUI编程等。
事件(event)表示程序某件事发生的信号。事件分为:
外部事件:由外部用户动作产生的事件。例如,点击鼠标、按键盘。
内部事件:由系统内部产生的事件。例如,定时器事件。
系统预定义一些事件类型如按键、点击、鼠标移动等,事件类型代表是什么事件。事件触发时可以运行事件处理代码。
☆面向对象程序设计(Object Oriented Programming),是围绕着问题域中的对象(Object)来设计,对象包含属性、方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关联的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象。
面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作一个小型的“机器”,即对象。目前已经被证实的是,面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目设计中广为应用。此外,支持者声称面向对象程序设计要比以往的做法更加便于学习,因为它能够让人们更简单地设计并维护程序,使得程序更加便于分析、设计、理解。反对者在某些领域对此予以否认。
当我们提到面向对象的时候,它不仅指一种程序设计方法。它更多意义上是一种程序开发方式、设计思想。在这一方面,我们必须了解更多关于面向对象系统分析和面向对象设计(Object Oriented Design,简称OOD)方面的知识。许多流行的编程语言是面向对象的,它们的风格就是会由对象来创出实例。
面向对象(Object Oriented)它是一种设计思想。从20世纪60年代提出面向对象的概念到现在,它已经发展成为一种比较成熟的编辑思想。
☆函数式程序设计(functional programming),顺便指出,不要误认为使用了函数就是函数式程序设计。函数式程序设计(functional programming)或称函数编程,是一种编程范式(programming paradigm),它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ演算(lambda calculus)为该语言最重要的基础,λ演算的函数可以接受函数当作输入(引数)和输出(传出值),主要思想是把运算过程尽量写成一系列嵌套的函数调用。举例来说,现在有这样一个数学表达式:
(1 + 2) * 3 – 4
传统的过程式编程,可能这样写:
var a = 1 + 2;
var b = a * 3;
var c = b – 4;
函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:
var result = subtract(multiply(add(1,2), 3), 4);
函数式编程允许把函数本身作为参数传入另一个函数,还允许返回一个函数!函数式编程强调没有”副作用”(side effect),意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
在函数式编程中,函数是第一类对象(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
Python对面向过程、面向对象编程与函数式编程都支持,关于Python函数式编程可参见
https://docs.python.org/zh-cn/3/howto/functional.html
】
面向过程和面向对象编程的不同之处
下面以一个例子来说明
面向过程
和面向对象在程序设计上的不同之处。
处理学生的成绩情况,为突出重点对问题进行了简化。
先看面向过程编程
(也称为结构化程序设计)情况:
可以用一个dict表示表示一个学生的成绩:
std1 = { ‘name’: ‘Michael’, ‘score’: 98 }
std2 = { ‘name’: ‘Bob’, ‘score’: 81 }
def print_score(std):
print(‘%s: %s’ % (std[‘name’], std[‘score’]))
print_score(std1)
print_score(std2)
运行之,参见下图:
如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。
定义一个类如下
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print(‘%s: %s’ % (self.name, self.score))
bart = Student(‘Michael’,98)
lisa = Student(‘Bob’,81)
bart.print_score()
lisa.print_score()
运行之,参见下图:
下面是python类和对象的简要思维导图:
python的类
创建一个新类(class)意味着创建一个新的对象类型(type of object),从而允许创建一个该类型的新实例(instances)。 每个类的实例可以拥有保存自己状态的属性(attributes)。 一个类的实例也可以有改变自己状态的(定义在类中的)方法(methods)。
在 Python 中,有两种有效的属性名称(attribute names):数据属性(data attributes)和方法(methods)。
数据属性,也称为属性(attribute) 对应于 Smalltalk 中的“实例变量(instance variables)”,以及 C++ 中的“数据成员(data members)”。
方法是“从属于”对象(object)的函数(function)。
关于Python的类,可参见官网介绍
https://docs.python.org/zh-cn/3/tutorial/classes.html#
先直观看了解一下python类成员
实例变量(instance variable)用于每个实例的唯一数据。定义位置类中,所有函数内部:以“self.变量名”的方式定义的变量。
类变量(class variable)用于类的所有实例共享的属性和方法。定义位置类中、所有函数之外。
局部变量,对于OOP,是class的方法(class的函数)中且没有self.的变量,对于面向过程编程,是函数中的变量。【对于面向过程编程,还有全局变量——函数外的变量】
下面给出类变量和实例变量示例
class Dog:
kind = ‘canine’ #类变量
def __init__(self, name):
total=2 #局部变量
self.name = name #实例变量
下面给出类中方法示例
class MyClass(object):
# 实例方法
def instance_method(self):
print(‘instance method called’, self)
# 类方法
@classmethod
def class_method(cls):
print(‘class method called’, cls)
# 静态方法
@staticmethod
def static_method():
print(‘static method called’)
关于Python中的Property
【官网
https://docs.python.org/3/library/functions.html#property
】
class C:
def __init__(self):
self._x = None
@property
def x(self):
“””I’m the ‘x’ property.”””
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
☆
成员变量
也被称为数据成员,保存了类或对象的数据。
☆
初始化方法
是一种特殊的函数,即类中的__init__()方法,用于初始化类的成员变量。【
注
:相当于其它语言的构造函数,故也称为构造方法】
☆
成员方法
是在类中定义的函数。
☆
property
(特征、特性)是对类进行封装而提供的实现机制,可以提升类封装的灵活性。
类方法
可以划分为实例方法(普通方法)、类方法和静态方法。
其表象区别是:
类方法前用@classmethod修饰
静态方法前用@staticmethod修饰
不加任何修饰的就是实例方法(普通方法)
【@classmethod 和 @staticmethod 都是函数装饰器】
有关
Python类的成员
情况,可参见
https://blog.csdn.net/cnds123/article/details/130898914
☆
实例方法(普通方法)
普通方法是我们最常用的方法,它定义时最少要包含一个约定为self的参数,用于绑定调用此方法的实例对象(所谓绑定,即用实例调用的时候,不需要显式的传入)——当实例调用方法的时候,会默认(无需显示指明实例名)将实例本身传入到这个参数self。
简单示例源码:
#定义类
class A():
name = '类成员name'
#定义(普通)方法
def instance_method(self, x):
print(self.name) #普通方法可以访问类成员
print(x)
a = A() #实例化类
a.instance_method('Hi')
运行输出:
类成员name
Hi
☆类方法
类方法有一个特点,就是这个方法必须要有@classmethod来修饰。和实例方法类似,至少也要传一个参数,约定为cls,Python会自动将类本身绑定到这个参数上面。
简单示例源码:
#定义类
class A(object):
name = '类成员name'
#定义类方法
@classmethod
def class_method(cls, s):
print(cls.name) # 类方法可以访问类成员
print(s)
a = A() #实例化类
a.class_method('Hi')
运行输出:
类成员name
Hi
☆静态方法
静态方法是使用@staticmethod修饰的方法,它不能访问任何类属性和类方法,因为它不含self或cls这些特殊参数,因此也无法访问类或实例中的成员。
简单示例源码:
#定义类
class A():
name = '类成员name'
#定义静态方法,静态方法不能访问类成员name,否则报错
@staticmethod
def static_method(b):
print(b)
a = A() #实例化类
a.static_method('Hi')
运行输出:
Hi
创建类
使用 class 语句来创建一个新类,class 之后为类的名称并以冒号结尾,简化格式为:
class ClassName[(父类名)]:
‘类的帮助信息’ #类文档字符串
class_suite #类体
其中
class 定义类的关键字,小写。
ClassNam 你定义类的名称,应符合标识符要求。
按照惯例类名通首字母常是大写
。
class_suite 组成类体的语句,可以使用空语句pass——不做任何事情。
[(父类名)] 是可选的,父类名 即继承的父类名称。
class 语句行尾有有一个英文冒号。
面向对象最重要的概念就是类(Class)和实例(Instance),在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self, 且为第一个参数,self 代表的是类的实例。
python类的实例化语法
obj=ClassName(x,x)
其中,obj是类的实例化对象,ClassName是类名,若类中有定义含有参数的初始化方法 __init__ (),括号还需要有初始化变量的实参。
下面给出一个例子
#类定义
class people:
#定义基本属性
name = ”
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print(“%s 说: 我 %d 岁。” %(self.name,self.age))
# 实例化类
p = people(‘runoob’,10,30)
p.speak()
运行之,参见下图:
方法是定义在类中的函数。
与函数的区别
在于:
定义方法时,方法的第一个形参有特别用途——表示调用该方法的实例对象,且调用时不需为之显式地提供实参,一般命名为self,也可以改用其它名称但不建议。
调用方法时,系统自动将调用该方法的实例对象作为实参传递给第一个形参——不需为这个形参显式地提供实参,第一个实参会传递给第二个形参,第二个实参会传递给第三个形参,依次类推。
类实例化后,可以使用其属性和方法,语法:obj.name即对象名.属性名或方法名。
前面的例子的类中有一个名为 __init__() 的特殊方法(初始化方法/构造方法),是类的专有方法之一,该方法在类实例化时会自动调用,注意,__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,
有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去。
并且,self就指向创建的实例本身,self.__class__ 则指向类。参见下例:
class Complex:
#定义类的特殊方法,即构造方法
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
#定义类的方法
def prt(self):
print(“self代表的是类的实例,代表当前对象的地址:”,self)
print(“self.__class__指向类:”,self.__class__)
x = Complex(8.3, 10) #实例化类
print(x.r, x.i)
x.prt()
运行之,参见下图:
python类中__init__( )方法说明:
用于执行”实例对象的初始化工作”,即对象创建后,初始化当前对象的属性,无返回值。格式:
def __init__ (self[,形参列表]):
函数体
例如
def __init__(self, n, a):
self.n = n
self.a = a
说明如下:
★__init__方法是一个特殊的方法(init是单词初始化initialization的省略形式)
★第一个参数都是self(习惯上就命为self,你也可以写成其它的东西,比如写成me也是可以的,这样的话下面的self.就要写成me.),这个self,表示对象本身,谁调用,就表示谁,调用时,不需为它指定实参。从第二参数开始均可设置变长参数、默认值等。
★self.n = n,表示赋值给对象属性,等号前后两个n是不同的两个变量,self.n是self的属性。
_init__不能有返回值。
★在创建类时,我们可以手动添加一个 __init__() 方法,称为初始化方法,即便不手动为类添加任何构造方法,Python 也会自动为类添加一个仅包含 self 参数的构造方法,称为类的默认构造方法。
此方法的方法名中,开头和结尾各有 2 个下划线,且中间不能有空格。
__init__() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。也就是说,类的构造方法最少也要有一个 self 参数。
可参见Python Constructors – default and parameterized:
https://beginnersbook.com/2018/03/python-constructors-default-and-parameterized/
Python Constructors – default and parameterized
★python类的初始化方法的调用示例
# 定义类
class Demo:
# 手动为类添加构造方法
def __init__(self, name, age, gender):
print(‘构造方法被执行了。’, name, age, gender)
# 定义 info() 方法
def info(self,):
print(‘info() 方法被执行了。’)
Demo(‘李萌’, 16, ‘女’) # 创建 Demo 类的对象,将会隐式调用构造方法并传入参数值
demo = Demo(‘阿杰’, 18, ‘男’) # 创建 Demo 类的对象,将会隐式调用构造方法并传入参数值
运行结果:
在类中是否使用了构造方法:__init__的区别
以完成同一功能的代码为例来看
先看在类中使用了构造方法情况:
class RectangleA:
“””
定义一个矩形类,
需要长和宽两个参数,
拥有计算周长和面积两个方法。
“””
def __init__(self, x, y): #构造方法
self.x = x
self.y = y
def getPeri(self): #定义方法
val = (self.x + self.y) * 2
return val
def getArea(self): #定义方法
val = self.x * self.y
return val
rect=RectangleA(3,4) #实例化类需要为参数赋值
print(rect.getPeri()) #调用方法getPeri不需要为参数赋值
print(rect.getArea()) #调用方法getArea不需为参数要赋值
注意:方法中用到的参数前有前缀self,实例化类时不需要为参数赋值。运行之,参见下图:
再看在类中不使用初始化方法情况:
class RectangleB:
“””
定义一个矩形类,
需要长和宽两个参数,
拥有计算周长和面积两个方法。
“””
def getPeri(self,x,y): #定义方法
val = (x + y) * 2
return val
def getArea(self,x,y): #定义方法
val = x * y
return val
rect=RectangleB() #实例化类不需要为参数赋值
print(rect.getPeri(3,4)) #调用方法getPeri需要为参数赋值
print(rect.getArea(3,4)) #调用方法getArea需要为参数赋值
注意:实例化类时需要为参数赋值。运行之,参见下图:
【类的特殊方法(也叫魔法方法):
__init__ : 初始化方法,在生成对象时调用
__del__ : 释放对象时使用
__repr__ : 打印,转换
__setitem__ : 按照索引赋值
__getitem__: 按照索引获取值
__len__: 获得长度
__cmp__: 比较运算
__call__: 函数调用
__add__: 加运算
__sub__: 减运算
__mul__: 乘运算
……
Python内置类属性
当创建一个类之后,系统就自带了一些属性,叫内置类属性。常见的内置类属性:
__dict__ : 类的属性(包含一个字典,由类的数据属性组成)
__doc__ :类的文档字符串
__name__: 类名
__module__: 类定义所在的模块(类的全名是’__main__.className’,如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod)
__bases__ : 类的所有父类构成元素(包含了一个由所有父类组成的元组)
……
注意:前后分别有两个下划线!】
类变量、实例变量、局部变量和全局变量
类变量(class variable),也叫成员变量、类属性——定义在类中,并且在方法外的变量。
实例变量(instance variable)也叫实例属性——在类中方法内通过self.变量名定义的变量。
定义在类中的变量也称为属性,定义在类中的函数也称为方法。
方法中加self的变量可以看成是类的属性,或者是特性。使用方法改变和调用属性,作用域是当前实例。方法中不加self的变量可以看成一个局部变量,作用域是当前函数,该变量不能被直接引用。
全局变量:在模块内、在所有函数外面、在class外面,这就是全局变量。
例、
big_temp = ‘123456788’ # 全局变量
class Test:
global_temp = ‘123’ # 类变量
def __init__(self):
self.temp = ‘321’ # 实例变量
mytemp = ‘345’ # 局部变量
print(big_temp)
print(‘———–‘)
t = Test()
print(t.global_temp)
print(‘———–‘)
print(t.temp)
#想想为何用print(t.mytemp或print(mytemp)都出错
运行之,参见下图:
现在我们介绍python类的一些细节
没有继承其它类的类,会自动继承系统中object类。在Python3中,object是所有类的超类(或者说基类)。
下面定义一个空语句类
class MyClassA:
pass #空语句
print(dir(MyClassA)) #dir() 使用Python 内置函数dir() ,列出对象的所有属性及方法。
运行之,参见下图:
在python类中可以调用类自身的方法,调用方式:
⑴类的内部调用:self.<方法名>(参数列表)
⑵在类的外部调用:<实例名>.<方法名>(参数列表)
注意:以上两种调用方法中,提供的参数列表中都不用包括self——定义类方法时必须包含一个参数,约定用self ,代表的是类的实例,但在调用时不必指明传入相应的参数。。
python中可以在类外定义的函数——全局函数,将其赋值给类中局部变量也是可以的。例如
def coord_chng(x,y): #定义全局函数
return (abs(x),abs(y))
class Ant: #定义类Ant
def __init__(self,x=0,y=0): #定义初始化方法__init__,并对两个形参初始化
self.x = x
self.y = y
self.disp_point() #调用类中的方法disp_point
def move(self,x,y): #定义方法或叫类函数move
x,y = coord_chng(x,y) #调用全局函数
self.edit_point(x,y) #调用类中的方法edit_point
self.disp_point() #调用类中的方法disp_point
def edit_point(self,x,y):#定义方法或叫类函数edit_point
self.x += x
self.y += y
def disp_point(self): #定义方法或叫类函数disp_point
print(“当前位置:(%d,%d)” % (self.x,self.y))
ant_a = Ant() #将Ant类实例为ant_a
ant_a.move(2,4) #调用实例ant_a的方法move
ant_a.move(-9,6) #调用实例ant_a的方法move
运行之,参见下图:
下面给出一个比较实用的例子,使用了tkinter,关于参见“Python基于tkinter的GUI编程”一讲。
一个简单的带图形界面的计算器:
源码如下
“””tkcalc.pyw 简单的计算器
“””
import tkinter as tk
class Calc(tk.Tk):
“””计算器窗体类”””
def __init__(self):
“””初始化实例”””
tk.Tk.__init__(self)
self.title(“计算器”)
self.memory = 0 # 暂存数值
self.create()
def create(self):
“””创建界面”””
btn_list = [“C”, “M->”, “->M”, “/”,
“7”, “8”, “9”, “*”,
“4”, “5”, “6”, “-“,
“1”, “2”, “3”, “+”,
“+/-“, “0”, “.”, “=”]
r = 1
c = 0
for b in btn_list:
self.button = tk.Button(self, text=b, width=5,
command=(lambda x=b: self.click(x)))
self.button.grid(row=r, column=c, padx=3, pady=6)
c += 1
if c > 3:
c = 0
r += 1
self.entry = tk.Entry(self, width=24, borderwidth=2,
bg=”yellow”, font=(“Consolas”, 12))
self.entry.grid(row=0, column=0, columnspan=4, padx=8, pady=6)
def click(self, key):
“””响应按钮”””
if key == “=”: # 输出结果
result = eval(self.entry.get())
self.entry.insert(tk.END, ” = ” + str(result))
elif key == “C”: # 清空输入框
self.entry.delete(0, tk.END)
elif key == “->M”: # 存入数值
self.memory = self.entry.get()
if “=” in self.memory:
ix = self.memory.find(“=”)
self.memory = self.memory[ix + 2:]
self.title(“M=” + self.memory)
elif key == “M->”: # 取出数值
if self.memory:
self.entry.insert(tk.END, self.memory)
elif key == “+/-“: # 正负翻转
if “=” in self.entry.get():
self.entry.delete(0, tk.END)
elif self.entry.get()[0] == “-“:
self.entry.delete(0)
else:
self.entry.insert(0, “-“)
else: # 其他键
if “=” in self.entry.get():
self.entry.delete(0, tk.END)
self.entry.insert(tk.END, key)
if __name__ == “__main__”:
Calc().mainloop()
运行之,参见下图:
Python中的封装、继承和多态
下面简要介绍面向对象编程有三大重要特征:封装、继承和多态。
★
封装(Encapsulation)
封装是指将数据与具体操作的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现,外界只能通过接口使用该对象,而不能通过任何形式修改对象内部实现,正是由于封装机制,程序在使用某一对象时不需要关心该对象的数据结构细节及实现操作的方法。使用封装能隐藏对象实现细节,使代码更易维护,同时因为不能直接调用、修改对象内部的私有信息,在一定程度上保证了系统安全性。类通过将函数和变量封装在内部,实现了比函数更高一级的封装。
例
class Student:
classroom = ‘101’
address = ‘beijing’
def __init__(self, name, age):
self.name = name
self.age = age
def print_age(self):
print(‘%s: %s’ % (self.name, self.age))
# 类将它内部的变量和方法封装起来,阻止外部的直接访问
# 以下是错误的用法,将报NameError: name ‘xxx’ is not defined
#print(classroom)
#print(address)
#print_age()
# 可以如下用
stu=Student(‘张三’,’18’) #实例化
print(stu.classroom)
print(stu.address)
stu.print_age()
★
继承(Inheritance)
继承机制实现了代码的复用,多个类公用的代码部分可以只在一个类中提供,而其他类只需要继承这个类即可。
在OOP程序设计中,当我们定义一个新类的时候,新的类称为子类(Subclass),而被继承的类称为基类、父类或超类(Base class、Super class)。继承最大的好处是子类获得了父类的全部变量和方法的同时,又可以根据需要进行修改、拓展。其语法结构如下:
class Foo(superA, superB,superC….):
class DerivedClassName(moduleName.BaseClassName): ## 当父类定义在另外的模块(module)时
继承示例
# 父类定义
class people:
def __init__(self, name, age, weight):
self.name = name
self.age = age
self.__weight = weight
def speak(self):
print(“%s 说: 我 %d 岁。” % (self.name, self.age))
# 继承
class student(people):
def __init__(self, name, age, weight, grade):
# 调用父类的实例化方法
people.__init__(self, name, age, weight)
self.grade = grade
# 重写父类的speak方法
def speak(self):
print(“%s 说: 我 %d 岁了,我在读 %d 年级” % (self.name, self.age, self.grade))
s = student(‘ken’, 10, 30, 3)
s.speak()
运行之,参见下图:
子类在调用某个方法或变量的时候,首先在自己内部查找,如果没有找到,则开始根据继承机制在父类里查找。
Python是支持多继承。一个类继承多个父类,同时这些类都有相同的方法名,但是具有不同的方法体,这种多重继承情况下,新式类会按照广度优先查找(在Python3中所有的类都是新式类,我们这里只介绍新式类的多继承)。
多重继承示例
class A1(object):
def fun1(self):
print(‘A1_fun1’)
class A2(object):
def fun1(self):
print(‘A2_fun1’)
def fun2(self):
print(‘A2_fun2’)
class B1(A1,A2):
def fun2(self):
print(“B1_fun2”)
class B2(A1,A2):
pass
class C(B1,B2):
pass
c=C()
c.fun1() # 实例c调用fun1()时,搜索顺序是 c->C->B1->B2->A1
c.fun2() # 实例c调用fun2()时,搜索顺序是 c->C->B1
运行之,参见下图:
本例的广度优先遍历示意图如下:
★
多态(Polymorphism)
多态意味着可以对不同的对象使用同样的操作,但它们可能会以多种形态呈现出结果——不同的子类对象调用相同的父类方法,产生不同的执行效果。类的多态特性,需要满足以下 2 个前提条件:
继承:多态一定是发生在子类和父类之间;
重写:子类重写了父类的方法。
例、
class CLanguage:
def say(self):
print(“调用的是 Clanguage 类的say方法”)
class CPython(CLanguage):
def say(self):
print(“调用的是 CPython 类的say方法”)
class CLinux(CLanguage):
def say(self):
print(“调用的是 CLinux 类的say方法”)
a = CLanguage()
a.say()
a = CPython()
a.say()
a = CLinux()
a.say()
运行之,参见下图:
可以看到,CPython 和 CLinux 都继承自 CLanguage 类,且各自都重写了父类的 say() 方法。从运行结果可以看出,同一变量 a 在执行同一个 say() 方法时,由于 a 实际表示不同的类实例对象,因此 a.say() 调用的并不是同一个类中的 say() 方法,这就是多态。
Python 在多态的基础上,衍生出了一种更灵活的编程机制。为了领略 Python 类使用多态特性的精髓,对上面的程序进行改写:
class WhoSay:
def say(self,who):
who.say()
class CLanguage:
def say(self):
print(“调用的是 Clanguage 类的say方法”)
class CPython(CLanguage):
def say(self):
print(“调用的是 CPython 类的say方法”)
class CLinux(CLanguage):
def say(self):
print(“调用的是 CLinux 类的say方法”)
a = WhoSay()
#调用 CLanguage 类的 say() 方法
a.say(CLanguage())
#调用 CPython 类的 say() 方法
a.say(CPython())
#调用 CLinux 类的 say() 方法
a.say(CLinux())
运行之,参见下图:
此程序中,通过给 WhoSay 类中的 say() 函数添加一个 who 参数,其内部利用传入的 who 调用 say() 方法。这意味着,当调用 WhoSay 类中的 say() 方法时,我们传给 who 参数的是哪个类的实例对象,它就会调用那个类中的 say() 方法。
附录
python里的super()和__init__()及self 可参见
https://blog.csdn.net/cnds123/article/details/127623653
Python类中self和__init__()介绍
https://blog.csdn.net/cnds123/article/details/130506922
Python基础-9 类
https://cloud.tencent.com/developer/article/2186796
Python 类和对象—面向对象编程
https://bbs.huaweicloud.com/blogs/279955
Python的特性(property)
https://www.cnblogs.com/blackmatrix/p/5646778.html
Python 从attribute到property详解
https://cloud.tencent.com/developer/article/1741854