一、面向对象编程
程序等于指令加数据组成(算法加数据结构),Python 支持面向过程式编程也支持面向对象式编程。面向对象编程——Object Oriented Programming,简称 OOP,是一种程序设计思想。OOP 把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
编写程序代码可以选择以指令为核心或以数据位核心进行编写:
以指令为中心:围绕“正在发生什么事”进行编写。
面向过程式编程:程序具有一系列线性步骤,主体思想是代码作用于数据。
以数据为中心:围绕“将影响谁”进行编写。
面向对象式编程:围绕数据及为数据严格定义的接口来组织程序,用数据控制对代码的访问。
在 Python 中,所有数据类型都可以视为对象,也就有了“Python 一切皆对象”的说法,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。
假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个 dict 表示:
1 2 |
std1 = { 'name': 'Michael', 'score': 98 } std2 = { 'name': 'Bob', 'score': 81 } |
而处理学生成绩可以通过函数实现,比如打印学生的成绩:
1 2 |
def print_score(std): print('%s: %s' % (std['name'], std['score'])) |
如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是 Student 这种数据类型应该被视为一个对象,这个对象拥有 name 和 score 这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个 print_score 消息,让对象自己把自己的数据打印出来。
1 2 3 4 5 6 |
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)) |
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:
1 2 3 4 5 6 |
>>> bart = Student('Bart Simpson', 59) >>> lisa = Student('Lisa Simpson', 87) >>> bart.print_score() Bart Simpson: 59 >>> lisa.print_score() Lisa Simpson: 87 |
面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class 是一种抽象概念,比如我们定义的 Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的 Student,比如,Bart Simpson 和 Lisa Simpson 是两个具体的 Student。
所以,面向对象的设计思想是抽象出 Class,根据 Class 创建 Instance。面向对象的抽象程度又比函数要高,因为一个 Class 既包含数据,又包含操作数据的方法。
二、获取对象信息
在 Python 中一切皆对象,程序中存储的所有数据都是对象,Python 中变量名存储在内存中一个位置,而对象存储在一个位置。变量名没有类型仅仅是指向对象的一个引用,只有当变量名引用哪一类对象时就所属了哪一类对象所属的类型。当对象没有被引用时就会被Python中的垃圾回收器回收,但是不会立即回收。
每个对象都有一个身份(id)、一个类型(type)和一个值(value)。例如:school=”Linux”会以Linux创建一个字符串对象(对象都是由内置类或定义类实例化而来的),其身份是指向它在内存中所处位置的指针(指在内存中的地址),而school就是引用这个具体位置的名称。
获取对象信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 定义变量; >>> school1 = "Linux" >>> school2 = "Linux" # 身份; >>> id(school1) 4561188040 >>> id(school2) 4561188040 # 类型; >>> type(school1) <class 'str'> # 值; >>> print(school1) Linux |
可以看到,我们通过变量名获取对象身份时,两个变量指向了同一个内存地址,也就是说Python确实是使用引用的方式来组织数据。
然后,我们使用了type()函数来判断对象类型。但是type()函数返回的是什么类型呢?它返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同:
1 2 3 4 5 6 7 8 9 10 |
>>> type(123)==type(456) True >>> type(123)==int True >>> type('abc')==type('123') True >>> type('abc')==str True >>> type('abc')==type(123) False |
判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> import types >>> def fn(): ... pass ... >>> type(fn)==types.FunctionType True >>> type(abs)==types.BuiltinFunctionType True >>> type(lambda x: x)==types.LambdaType True >>> type((x for x in range(10)))==types.GeneratorType True |
但对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。
1 2 3 4 5 6 |
>>> class A(): ... pass ... >>> class B(A): ... pass ... |
那么,isinstance()就可以告诉我们,一个对象是否是某种类型。先创建2种类型的对象:
1 2 |
>>> a = A() >>> b = B() |
然后,判断:
1 2 |
>>> isinstance(b, B) True |
没有问题,因为b变量指向的就是B对象。
再判断:
1 2 |
>>> isinstance(b, A) True |
b虽然自身是B类型,但由于B是从A继承下来的,所以,b也还是A类型。换句话说,isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。