Python 面向对象编程
面向对象程序设计
结构化程序设计的缺点
我们为什么要有面向对象程序设计呢?我们使用 C 语言只能实现结构化程序设计,所谓结构化程序设计就是“程序 = 数据结构 + 算法”,而在程序中会有很多可以相互调用的函数和全局变量。
但是我们可以显然地看出,这种编程风格存在不少缺点。首先由于函数之间可以相互调用,任何函数都可以修改全局变量,这就导致了函数、数据结构之间的关系一段乱麻,尤其是当代码量很长的时候,代码的理解也变得极其困难。
- 这个函数是用来操作哪个还是哪些数据数据结构?
- 这个数据结构可以被哪些函数操作,代表什么含义?
- 不同的函数能够相互调用吗?关系是什么?
对于变量来说,有的变量可能是只能被一些函数修改,而不能被某些函数操作,但是由于你不能给变量“上锁”,因此这个变量会很轻松地被修改。当然你可以说可以搞成常量,那我如果要修改呢?搞成常引用?算了吧,这样变量间的关系就跟复杂了。而且,某个数据结构会被多个函数调用,而如果出现了错误,是哪个函数出错了呢?是函数一、函数二、函数 N,还是都有错?除了调试好像还真没啥好办法。
作为一个懒人,如果我的一个程序的某个功能我曾经写过,那么我就很喜欢去把以前的代码搞来用。可是往往这会是一件困难的事情,因为这段代码的源程序的变量和函数间本身就有复杂的逻辑关系,接口可能完全不一样,最后你发现还不如重写一遍。
说了这么多,总之结构化程序设计就是有很多缺点啦!
面向对象程序设计
我们喜欢的程序是,代码的思路清晰、健壮性好利于维护和添加功能、移植性强,这个时候就要请出面向对象程序设计啦。
面向对象程序设计(Object Oriented Programming)作为一种新方法,其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征,只能对事物特征和变化规律的一种抽象,且在它所涉及的范围内更普遍、更集中、更深刻地描述客体的特征。通过建立模型而达到的抽象是人们对客体认识的深化。——百度百科
简单地说,面向对象程序设计就是把某个客观事物的共同特点归纳出来,形成一个数据结构。对象既表示客观世界问题空间中的某个具体事物,有表示软件系统解空间中的基本元素。
“对象 = 属性 + 方法”,对象以 id 为标识,既包含属性(数据),也包含方法(代码),是某一类具体事物的特殊实例。我们怎么来理解呢?
实例:Dog 类
狗狗是我们的好朋友啦,那么对于一只狗它会有什么行为呢?它应该会叫、会坐、会跑,那么我们就来用 python 封装一个 Dog 类。直接扔一段代码:
class Dog():
def __init__(self,name,breed):
#初始化一个 Dog 对象,属性有 name 和 breed
self.name = name
self.breed = breed
def sit(self):
#狗狗蹲下
print(self.name.title() + " is now sitting.")
def roll_over(self):
#狗狗打滚
print(self.name.title() + " rolled over!")
然后拿去交互式解释器运行下,创建一个 Dog 对象,查看它的属性,并指挥它蹲下和打滚。
Python 面向对象编程
Python是完全面向对象的语言。函数、模块、数字、字符串都是对象。并且完全支持继承、重载、派生、多继承,有益于增强源代码的复用性。Python支持重载运算符和动态类型。相对于Lisp这种传统的函数式编程语言,Python对函数式设计只提供了有限的支持。有两个标准库(functools, itertools)提供了Haskell和Standard ML中久经考验的函数式程序设计工具。——百度百科
python 绝对是个面向对象的编程语言啦,因为 python 中从数值类型到代码模块都是以对象的形式存在的,我们来举 3 个例子,打开交互式解释器。
对于 int 类型,我们查看 “1” 的 id、type、和 dir(可操作性 int 的方法):
接下来是字符类型,查看 “a” 的 id、type、和 dir:
接下来我们看个不一样的,在函数不被调用时,就可以认为是一个对象,也会有一些方法可以操作函数,例如查看 “abs()” 函数的 id、type、和 dir:
对象与方法
所谓“对象”,它实现了属性和方法的封装,这是一种数据抽象的机制,这种方式提高了程序的重用性、灵活性、扩展性。
我们需要怎么创建一个对象呢?例如上文的 Dog 类,那么创建一个 Dog 对象的代码为:
a_dog = Dog("bilibili·狗·德川家康·薛定谔·保留","Husky")
应该不难理解,首先需要制定是什么类,然后传入需要的属性进去,例如 Dog 类需要 2 个属性。
引用方法
类中的函数被称为方法,引用的代码格式为:
<对象名>.<属性名>
例如我们要让一个 Dog 对象完成 sit 动作,其代码为:
a_dog.sit()
- Python 是一门动态的编程语言,因此对象可以随时增加或删除属性或方法。
类的定义与调用
class
要创建一个对象,这个对象的类已经被定义时必要条件,所谓“类(class)” 就是用来描述相同的属性和方法的集合,定义了该集合中每个对象共有的属性和方法,而对象则是类的实例。需要注意的是,对于同一个类,类的对象将会具有相同的属性和方法,但是属性的数据和 id 是不同的。
和函数或者 C 语言的结构体有类似之处,类是一系列代码的封装,在 Python 中我们约定俗成,类名需要以大写字母为开头,函数则以小写字母开头,方便我们进行区分。
定义类
定义类我们需要使用 class 语句:
class <类名>
<一系列方法的调用>
初始化类
直接看代码:
class<类名>
def __init__(self,<参数表>):
def 方法名(self,<参数表>):
__ init __
"__ init __" 是一个特殊的方法,每当你需要根据类创建对象时,Python 会自动运行并对对象进行初始化,第一个参数必须是 self。
self
对于一个类,所有的方法中第一个参数一般都是 self,在类的内部,实例化时传入的参数都会直接赋给这个变量。
调用类
当我们调用一个类时,就会创建一个对象,代码:
obj = <类名>(<参数表>)
实例:Force 类
为了方便理解,我们来写一个 “Force” 类。力是矢量,假设现在讨论的力都是二维空间中的力,那么这个力就需要用 x 和 y 两个分量来描述,同时我们还想实现力的合成操作。
类的封装如下:
class Force:
def __init__(self,x,y):
# x 和 y 为力在两个坐标轴的分量
self.fx,self.fy = x,y
def show(self):
#展示力的信息
print("Force<%s,%s>"%(self.fx,self.fy))
def add(self,force2):
#力的合成操作
x = self.fx + force2.fx
y = self.fy + force2.fy
return Force(x,y)
让我们来创建几个对象试试,打开交互式解释器:
Special method
特殊方法
特殊方法,也可以称之为魔术方法(Magic merhod),这是类的定义中已经实现了的方法,使用这些方法我们可以轻松地调用 python 的内置操作。所有特殊方法的名称都是以两个下划线“__”开始和结束。
这里罗列一些常用的特殊方法:
特殊方法 | 操作 |
---|---|
__ init __ | 构造函数,在生成对象时调用 |
__ del __ | 析构函数,释放对象时使用 |
__ repr __ | 打印,转换 |
__ setitem __ | 按照索引赋值 |
__ getitem __ | 按照索引获取值 |
__ len __ | 获得长度 |
__ cmp __ | 比较运算 |
__ call __ | 函数调用 |
__ add __ | 加运算 |
__ sub __ | 减运算 |
__ mul __ | 乘运算 |
__ truediv __ | 除运算 |
__ mod __ | 求余运算 |
__ pow __ | 乘方 |
构造与解构
这里需要强调一下“__ init __ ” 方法和 “ __ del __”,前者是对象的构造器,用于实例化对象时被自动调用,后者是析构器,用于销毁对象。
这里还是用 Dog 类来理解一下这两个方法的作用:
class Dog():
def __init__(self,name,breed):
#初始化一个 Dog 对象,属性有 name 和 breed
self.name = name
self.breed = breed
def __del__(self):
#调用这个方法时,对象会被销毁
del self
实例 1 :Force 类
这次我们使用特殊方法来实现 Force 类:
class Force:
def __init__(self,x,y):
# x 和 y 为力在两个坐标轴的分量
self.fx,self.fy = x,y
def __add__(self,force2):
#力的合成操作
x = self.fx + force2.fx
y = self.fy + force2.fy
return Force(x,y)
def __str__(self):
#展示力的信息
return "Force<%s,%s>"%(self.fx,self.fy)
def __mul__(self,n):
#力扩大 n 倍
x,y = self.fx * n,self.fy * n
return Force(x,y)
def __eq__(self,force2):
#判读力是否相等
return (self.fx == force2.fx) and (self.fy == force2.fy)
测试一下看看,启动交互式解释器:
与上文不同的是,现在我们的 Force 类可以使用诸如 “+”和 “ == ” 运算符来实现一些操作了。这是因为 “__ add __ ” 方法能够让我们可以使用 “+” 运算符来操作 Force 对象, “ __ str __ ” 方法能够让我们可以将 Force 对象的一些信息转换为字符串, “ __ mul __ ” 方法能够让我们可以使用 “*” 运算符来操作 Force 对象, “ __ eq __ ” 方法能够让我们可以使用 “ == ” 运算符来对 Force 对象进行相等的判断。
实例 2 :Student 类
列表排序。
sort()
使用 sort() 方法,该方法用于对原列表进行排序,没有返回值,如果指定参数,则使用比较函数指定的比较函数,语法为:
list.sort( key = None, reverse = False)
参数 | 说明 |
---|---|
list | 需要操作的列表 |
key | 进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序 |
reverse | 排序规则,reverse = True 降序, reverse = False 升序(默认) |
sorted()
使用 sorted() 函数,该函数可对所有可迭代的对象进行排序操作,返回值为重新排序的列表,该函数不影响列表元素在列表中的原始排列顺序,语法为:
sorted(iterable, cmp=None, key=None, reverse=False)
参数 | 说明 |
---|---|
iterable | 可迭代对象 |
cmp | 比较的函数,这个具有两个参数,参数的值都是从可迭代对象中取出,此函数必须遵守的规则为,大于则返回1,小于则返回-1,等于则返回0 |
key | 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序 |
reverse | 排序规则,reverse = True 降序 , reverse = False 升序(默认) |
- sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。list 的 sort 方法返回的是对已经存在的列表进行操作,无返回值,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
- sort 与 sorted 内部是用Timsort算法实现的,Timsort是结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法,它在现实中有很好的效率。Tim Peters 在 2002 年设计了该算法并在 Python 中使用(TimSort 是 Python 中 list.sort 的默认实现)。
__ it __ 方法
Python 是一门拓展性很强的语言,对于每个类来说,都可以定义特殊方法。定义 “it” 方法之后,任意的自定义类都可以进行比较,例如对于 Int 类型就进行数值的比较,string 类型就按照字典序来比较。定义的格式为:
def __init__(self,y)
若方法返回 true,则视为对象比 y 要小,排序的时候就要排在前面,反之排在后面。
自定义对象的排序
现在我们就给出 Student 类的代码,我们需要实现 sort() 方法对 Student 类列表的排序。
class Student:
def __init__(self,name,grade):
self.name = name
self.grade = grade
def __lt__(self,other):
#按照成绩由大到小排序
return self.grade > other.grade
def __str__(self):
return "(%s,%d)" % (self.name,self.grade)
__repr__ = __str__
测试一下,打开交互式解释器:
inheritance
不知道大家还记不记得风火轮,就是那个绝技是“九天雷霆双脚蹬”的摩托(笑)。我还记得有一集风火轮想要强化一下火力,所以就进行了升级,加装了“三星连环炮”,之后风火轮就可以提供火力输出了。我们来分析一下,当风火轮想要变强时,我们是否有必要把风火轮拆了重新做?很明显是不用的,因为我的目的是“风火轮 + 三星连环炮”,因此只需要在原有的风火轮身上加装装备即可。
在实际编写代码的时候,我们也会遇到这样的问题。当一个新的类和我过去实现的类具有很大的相似之处时,我是否需要从头开始写?很明显这样很累,做过的事情何必再做一遍嘛。这时,我们可以沿用我原来写的类,然后往里面添加新的特性,这样不就实现了我的目的了嘛。对于编程,复用已有的类来构建新的类的做法就是面向对象编程的继承特性。
类的继承
编写一个类时,不一定需要从头开始,如果你现在想要实现的类中有的部分已经在另一个现有的类实现了,你可以将现成的类继承过来。当一个类继承另一个类时,将自动获得另一个类的所有属性和方法。被继承的类称为父类,新类被称为子类,同时子类还可以定义自己的属性和方法。利用继承衍生出来的新类可以添加或修改一些新的功能。
方法重写
如果从父类继承的方法不满足子类的需求,可以对其进行重写。
实例:Car 类和 ElentricCar 类
首先我们先写一个 Car 类,这个类可以显示一些汽车的基本信息,并且可以查询里程数。
class Car:
def __init__(self,make,model,year):
#初始化车的属性
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0 #里程数,初始化为 0
def get_descriptive_name(self):
#打印车的相关信息
print(str(self.year) + ' ' + self.make + ' ' + self.model)
def read_odometer(self):
#打印里程信息
print("This car has " + str(self.odometer_reading) + " miles on it.")
def updata_odometer(self,mileage):
#设置里程数,并禁止回调
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
测试一下,打开交互式解析器。
很成功,接下来我们写一个 ElentricCar 类。
由于 ElentricCar 和 Car 有很多相似之处,因此可以继承。同时 ElentricCar 具有一些 Car 不具有的特性,例如我要加一个电池电量,要多描述一些信息,这时就可以进行方法重写。
class Car:
def __init__(self,make,model,year):
#初始化车的属性
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0 #里程数,初始化为 0
def get_descriptive_name(self):
#打印车的相关信息
print(str(self.year) + ' ' + self.make + ' ' + self.model)
def read_odometer(self):
#打印里程信息
print("This car has " + str(self.odometer_reading) + " miles on it.")
def updata_odometer(self,mileage):
#设置里程数,并禁止回调
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
class ElentricCar(Car):
#继承 Car 类
def __init__(self,make,model,year):
#初始化电瓶车的属性
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0 #里程数,初始化为 0
self.battery_size = 100 #电池电量初始化为 100
def describe_battery(self):
#打印电量
print("This car has a " + str(self.battery_size) + "-KWH battery.")
测试一下,打开交互式解析器。
我们可以看到,ElentricCar 类不仅继承了 Car 类的全套代码,而且还能够拥有自己的一些特性!
参考资料
Python 百度百科
《新标准 C++ 程序设计》郭炜 编著,高等教育出版社
《Python语言基础与应用》 陈斌
《Python编程从入门到实践》————[美]Eric Matthes 著,袁国忠 译,人民邮电出版社
菜鸟教程