面向对象的详细解读
一、基本概念
1. 面向过程
(1) 概念:以过程为中心的编程思想,就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
(2) 优点:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。因为程序的流程很清楚,按着模块与函数的方法可以很好的组织。
(3) 缺点:只能用来解决一个问题,代码牵一发而动全身。
(4) 应用:用于基本很少改变的场景,著名的例子有Linux内核、git、以及Apache HTTP Server等。
2. 面向对象
(1) 概念:程序设计的核心是对象,面向对象的方法主要是把事物给对象化,对象包括属性与方法。
(2) 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
(3) 缺点:可控性差,无法像面向过程的程序设计一样可以精准的预测问题的处理流程与结果。
(4) 应用:需求经常变化的软件,一般需求的变化都集中在用户层,如互联网应用、企业内部软件以及游戏等。
二、面向对象中的名词解析
1. 类:类就是一个模板,模板里可以包含多个函数,每个函数都能实现一些功能。
2. 对象:根据模板(类)创建的实例,通过这个实例(对象)可以执行类中的函数。
3. 属性:类中所定义的变量
4. 方法:类中所定义的函数
5. 实例化:通过类创建对象的过程
6. 总结:对象的抽象是类,类的具体化就是对象;也可以说类的实例化是对象,对象是类的实例。
》》》实例说明——类与对象的创建
# -*- encoding:utf-8 -*- ‘‘‘ 实例说明——类与对象的创建 ‘‘‘ class ClassName: #创建类,ClassName为类名 Alary = 10000 #类的一个属性 def Fun1(self): #方法1 pass #方法1的技能 def Fun2(self): #方法2 pass #方法2的技能 obj = ClassName() #创建对象,obj为对象名
三、特征
1. 封装
(1) 概念:封装是将对象运行时所需的资源封装在程序对象中。简单来说,就是将内容封装起来,以后再去调用被封装的内容。
(2) 调用封装的内容有2种方法:
——通过对象直接调用
——通过self间接调用
》》》实例说明——面向对象的特征:封装
# -*- encoding:utf-8 -*- ‘‘‘ 实例说明——面向对象的特征:封装 ‘‘‘ class Student: def __init__(self, name, age): self.name = name self.age = age def detail(self): #调用时对象名会传给self参数,如最后一句 print(self.name) print(self.age) obj1 = Student(‘Jack‘,15) #将‘Jack‘和15分别封装到obj1的self的name和age属性中 print(obj1.name) #通过对象直接调用name属性和age属性 print(obj1.age) obj2 = Student(‘Sahra‘,13) #将‘Sahra‘和13分别封装到obj2的self的name和age属性中 obj2.detail() #通过self间接调用name属性和age属性
2. 继承
(1) 概念:继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。
(2) 多继承
注:Python的类可以继承多个类,而Java和C#中则只能继承一个类
Python的类如果继承了多个类,那么其寻找方法的方式有2种:
- 当类为经典类时会按照深度优先方式查找
- 当类为新式类时会按照广度优先方式查找
》》》实例说明——面向对象的特征:继承
# -*- encoding:utf-8 -*- ‘‘‘ 实例说明——面向对象的特征:继承 ‘‘‘ class Person(object): #定义Person父类 def talk(self): #定义父类的方法 print("Person can talk.") class Chinese(Person): #定义Person父类的一个子类,同时是Characters类的父类 def talkC(Person): #定义方法 print("Chinese can talk Mandarin.") class Characters(Chinese): #定义Chinese父类的一个子类 def people(self): #定义方法 print("Chinese are clever and diligent.") class American(Person): #定义Person父类的一个子类 def talkA(self): #定义方法 print("American can talk English.") C = Characters() #定义父类的子类的子类 A = American() #定义父类的子类 C.talk() #调用继承Person类的方法 A.talkA() #调用本身的方法 C.people() #调用本身的方法
3. 多态
概念:多态指同一个实体同时具有多种形式,在赋值之后,不同的子类对象调用相同的父类方法,产生的执行结果不同。
》》》实例说明——面向对象的特征:多态
1 # -*- encoding:utf-8 -*- 2 ‘‘‘ 实例说明——面向对象的特征:多态 ‘‘‘ 3 import abc 4 5 class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 6 7 @abc.abstractmethod 8 def talk(self): 9 pass 10 11 class People(Animal): #动物的形态之一:人 12 def talk(self): 13 print(‘say hello‘) 14 15 class Dog(Animal): #动物的形态之二:狗 16 def talk(self): 17 print(‘say wangwang‘) 18 19 class Pig(Animal): #动物的形态之三:猪 20 def talk(self): 21 print(‘say aoao‘) 22 23 24 peo = People() #创建People类的对象peo 25 dog = Dog() #创建Dog类的对象dog 26 pig = Pig() #创建Pig类的对象pig 27 peo.talk() #分别使用各种的方法 28 dog.talk() 29 pig.talk()
四、类的成员
1. 简介:类的成员包括字段、方法和属性。在所有成员中,只有字段中的普通字段保存于对象中,因此创建多少个对象在内存中就有几个普通字段;而其他成员都保存在类中,也只占用一份内存空间。
2. 字段:包括普通字段和静态字段,他们在定义和使用都有所区别,而最本质的区别是内存中保存的位置不同。
(1) 普通字段保存在对象中
(2) 静态字段保存在类中
3. 方法:包括普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
(1) 公有方法
调用:可由对象名直接调用;如果通过类名来调用属于对象的公有方法,需要显式为该方法的self参数传递一个对象名,用来明确指定访问哪个对象的数据成员。
特点:至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self。在此方法中可以访问属于类和对象的成员。
(2) 私有方法
调用:只能在属于对象的方法中通过self调用或在外部通过Python支持的特殊方式来调用。在此方法中可以访问属于类和对象的成员。
(3) 静态方法
调用:可以通过类名和对象名调用,但不能直接访问属于对象的成员,只能访问属于类的成员。
特点:无默认参数。
(4) 类方法
调用:可以通过类名和对象名调用,但不能直接访问属于对象的成员,只能访问属于类的成员。
特点:至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls。
4. 属性:其实是普通方法的变种。
5. 类成员的修饰符:下划线
xxx :公有成员,在任何地方都能访问
__xxx or ...__xxx:私有成员,只有类对象自己能访问,子类对象不能直接访问,但在对象外部可以通过“对象名._类名__xxx”这样的特殊方式来访问。
_xxx:受保护成员,不能用‘from module import *‘导入
__xxx__:系统定义的特殊成员
注:Python中不存在严格意义上的私有成员
6. 类的特殊成员
(1) __init__:构造方法,通过类创建对象时,自动触发执行。
(2) __del__:析构方法,当对象在内存中被释放时,自动触发执行,此方法一般无需定义。
(3) 类的属性
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值
(4) 特殊的类属性
类名.__name__:类的名字(字符串)
类名.__doc__:类的文档字符串
类名.__base__:类的第一个父类(在讲继承时会讲)
类名.__bases__:类所有父类构成的元组(在讲继承时会讲)
类名.__dict__:类的字典属性
类名.__module__:类定义所在的模块
类名.__class__:实例对应的类(仅新式类中)
7. 混入机制
(1) 概念:Python类型的动态性使得我们可以动态为自定义类及其对象增加新的属性和行为,俗称混入(mixin)机制。
(2) 》》》实例说明 —— 混入机制
1 # -*- encoding:utf-8 -*- 2 ‘‘‘ 实例说明 —— 混入机制 ‘‘‘ 3 import types 4 class Car: #定义类 5 price = 100000 #定义类属性,属于静态字段,只保存于类中 6 def __init__(self, c): 7 self.color = c #定义实例属性 8 9 mycar = Car("Blue") #实例化对象 10 print(mycar.color, Car.price) #查看实例属性和类属性的值 11 Car.price = 1200 #修改类属性的值 12 Car.name = ‘Roman‘ #动态增加类属性,属于静态字段,只保存于类中 13 mycar.color = "Red" #修改实例属性的值 14 print(Car.price, Car.name, mycar.color) 15 16 def setSpeed(self, s): 17 self.speed = s 18 19 mycar.setSpeed = types.MethodType(setSpeed, mycar) #动态增加成员方法 20 mycar.setSpeed(50) #调用成员方法 21 print(mycar.speed) 22 23 ‘‘‘ 利用特殊的类属性查看类与对象的属性与行为 ‘‘‘ 24 print("Car类的属性与行为如下:\n",Car.__dict__) 25 print("mycar对象的属性与行为如下:\n",mycar.__dict__)
五、使用面向对象思想实现基本操作
1. 三维向量类
(1) 简述:实现向量的加减法、向量与标量的乘除法。
(2) 代码实现:
# --coding: gb2312-- ‘‘‘ 三维向量 ‘‘‘ class vector3: def __init__(self, x_ = 0, y_ = 0, z_ = 0): #构造函数 self.x = x_ self.y = y_ self.z = z_ def __add__(self, obj): #重载+作为加号 return vector3(self.x+obj.x, self.y+obj.y, self.z+obj.z) def __sub__(self, obj): #重载-作为减号 return vector3(self.x-obj.x, self.y-obj.y, self.z-obj.z) def __mul__(self,n): #重载*作为点乘 return vector3(self.x*n, self.y*n, self.z*n) def __truediv__(self, obj): #重载/作为除法 return vector3(self.x/n, self.y/n, self.z/n) def __str__(self): return str(self.x)+‘,‘+str(self.y)+‘,‘+str(self.z) if __name__ == "__main__": n = int(input("请输入一个标量:")) a,b,c = map(int,input("请输入第一个向量:").split()) v1 = vector3(a,b,c) a,b,c = map(int,input("请输入第二个向量:").split()) v2 = vector3(a,b,c) print("两向量的加法:",v1 + v2) print("两向量的减法:",v1 - v2) print("标量与向量的乘法:",v1 * n) print("标量与向量的除法:",v1 / n)
2. 英文字符串处理
(1) 简述:用户输入一段英文,得到这段英文中所以长度为3的单词,并去除重复的单词。
(2) 代码实现:
方法一: 使用 jieba 库
1 # -*- encoding:utf-8 -*- 2 ‘‘‘ 将一段英文中长度为3的单词输出,并去掉重复的单词 ‘‘‘ 3 4 import jieba 5 class ProString: 6 Str = "" 7 Dict = {} 8 Ls = [] 9 def __init__(self,string,length = 3): #初始化 10 self.string = string 11 self.length = length 12 13 def SignalWord(self): #去除重复的单词 14 self.words = jieba.lcut(self.string) #jieba分词 15 for _ in self.words: #与词频算法相似 16 self.Dict[_] = self.Dict.get(_,0) + 1 17 del(self.Dict[‘ ‘]) #删除空格项 18 self.Ls = list(self.Dict.keys()) #字典类型转化成列表类型 19 self.StubbenWord(self.Ls) 20 21 def StubbenWord(self,Ls): #利用去除重复的单词,得到固定长度的单词 22 for _ in Ls: 23 if len(_) == self.length: 24 self.Str += _ + ‘ ‘ 25 self.printf(self.Str) 26 27 def printf(self,Str): 28 print("处理后的字符串为:",Str) 29 30 if __name__ == "__main__": 31 str = input("请输入字符串:") 32 process = ProString(str,3) 33 process.SignalWord()
方法二: 使用 re库 (正则表达式)
1 # -*- encoding:utf-8 -*- 2 ‘‘‘ 将一段英文中长度为3的单词输出,并去掉重复的单词 ‘‘‘ 3 4 import re 5 class ProStr: 6 a = [] 7 def __init__(self, words, length = 3): 8 self.words = words 9 self.length = length 10 11 def process(self): 12 word_list = re.split(‘[\. ]+‘,self.words) 13 for _ in word_list: 14 if len(_) == self.length: 15 if _ not in self.a: 16 self.a.append(_) 17 else: 18 continue 19 self.printf() 20 21 def printf(self): 22 print("处理后的字符串为:", end = ‘‘) 23 for _ in range(len(self.a)): 24 print(self.a[_],end=‘ ‘) 25 26 if __name__ == "__main__": 27 words = input("请输入字符串:") 28 process = ProStr(words, 3) 29 process.process()
<拓展阅读>
1. 定义类方法时一定要有self 参数吗?
答:在Python中,定义类的方法时将第一个参数定义为“self”只是一个习惯,而实际上类的方法中第一个参数的名字是可以变化的,而不一定要使用“self”这个名字,但一般建议编写代码时仍以“self”作为方法的第一个参数名字。
2. 在python中,方法与函数有区别吗?
答:在Python中,函数和方法是有区别的。方法一般指与特定实例绑定的函数,通过对象调用方法时,对象本身将被作为第一个参数隐式传递过去,普通函数并不具备这个特点。
3. 类和对象在内存中如何保存?
答:类以及类中的方法在内存中只占一份内存,而根据类创建的每一个对象在内存中都需要一份内存。如下图:
如上图所示,根据类创建对象时,对象中除了封装 name 和 age 的值之外,还会保存一个类对象指针,该值指向当前对象的类。
当通过 obj1 执行 【方法一】 时,过程如下:
- 根据当前对象中的 类对象指针 找到类中的方法
- 将对象 obj1 当作参数传给 方法的第一个参数 self
4. IDLE中下划线’_’的奇葩用法
(1) 表示解释器中最后一次显示的内容或最后一个语句正确执行的输出结果
(2) 表示不关心的变量的值
(3) 在对象、类名或模块后面加上一个圆点 ‘.‘,稍等一秒钟则会自动列出其所有公开成员。如果在圆点 ‘.‘ 后面再加一个下划线 ‘_‘,则会列出其所有成员,包括私有成员。