十.python面向对象(基础)
1.三大编程范式
编程范式即编程的方法论,标识一种编程风格
三大编程范式为:
- 面向过程编程
- 函数式编程
- 面向对象编程
2.编程进化论
- 编程最开始就是无组织无结构,从简单控制流中按步写指令
- 从上述的指令中提取重复的代码块或逻辑,组织到一起(比方说,你定义了一个函数),便实现了代码重用,且代码由无结构走向了结构化,创建程序的过程变得更具逻辑性
- 我们定义函数都是独立于函数外定义变量,然后作为参数传递给函数,这意味着:数据与动作是分离的
- 如果我们把数据和动作内嵌到一个结构(函数或类)里面,那么我们就有了一个‘对象系统’(对象就是数据与函数整合到一起的产物)。
3.面向对象编程
3.1类和对象的概念
什么是类?
- 把一类事物的相同的 特征 和 动作 整合到一起就是类。类是一个抽象的概念。就好比一个模型,该模型用来表述一类事物(事物即数据和动作的结合体),用它来生产真实的物体(实例&对象)。
什么叫对象?
- 现实中,我们看到的任何事物都是对象,对象就是基于类而创建的具体的事物(具体存在的),也是特征和动作整合到一起
类与对象有什么关系呢?
- 我们把类比作是人类,人有很多,每个具体的人我们称为对象,类是一类事物的总体,而事物下每个个体就是对象。类是虚拟的,而对象是真是存在的。
什么叫实例化呢?
- 实例本质就是对象,我们可以把实例叫做对象,比如人类,而我们每个人是由人类生成的一个个的个体,这个个体称为对象,也就是我们每个人,但是由类生成对象的这个过程,我们就称为是实例化的过程。
3.2面向对象的优缺点
优点:
- 易维护,采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来是非常方便和较低成本的。
- 质量高,在设计时,可重用现有的,在以前的项目的领域中已被测试过的类使系统满足业务需求并具有较高的质量。
- 效率高,在软件开发时,根据设计的需要对现实世界的事物进行抽象,产生类。使用这样的方法解决问题,接近于日常生活和自然的思考方式,势必提高软件开发的效率和质量。
- 易扩展,由于继承、封装、多态的特性,自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低。
缺点:
- 性能低,和面向过程的编程相比,相对性能较低。
3.3应用场景
- 需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
4.类与对象定义与使用
上面已经介绍,先得有类,然后根据类生成对象。以下分开理解类和对象的使用,分别有什么特点,又有什么关联,还有相同之处和不同之处。
4.1类
4.1.1 声明类
1 ''' 2 class 类名: 3 '类的文档字符串' 4 类体 5 ''' 6 7 #创建一个类 8 class people: 9 pass 10 11 #用类Data实例化出一个对象p1 12 13 p1 = people()
4.1.2属性
类是用来描述一类事物,类的对象指的是这一类事物中的一个个体
是事物就要有属性,在类中属性分为
- 数据属性:就是变量
- 函数属性:就是函数,在面向对象里通常称为方法
注意:类和对象均用点来访问自己的属性
类的数据属性
1 class Robot: #定义一个机器人的类 2 Type = '机器' #机器人都是机器,Type就是机器人的数据属性 3 4 print(Robot.Type) #查看类的数据属性 5 6 #注意:类的数据属性是所有对象共享的
结果:
1 机器
类的函数属性(方法)
1 class Robot: 2 Type = '机器' 3 def jian_la_ji(self): #函数属性,也称为类的方法,捡垃圾是机器人的动作 4 print("地球太脏了,机器人每天都在捡垃圾。") 5 6 R1 = '捡垃圾机器人' 7 print(Robot.jian_la_ji(R1)) #带参函数,所以调用时需要传入参数,将R1传给self 8 #类中的方法必须要有一个参数,我们经常用到self,其实self就是对象本身,后面对象会说到。
结果:
1 地球太脏了,机器人每天都在捡垃圾。 2 None
查看类属性
我们定义的类的属性到底存到哪里了?有两种方式查看
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值
1 class Robot: 2 Type = '机器' #数据属性 3 def jian_la_ji(self): #函数属性,也称为类的方法,捡垃圾是机器人的动作 4 print("地球太脏了,机器人每天都在捡垃圾。") 5 6 7 print(Robot.__dict__) #查看类的属性字典 8 print(Robot.__dict__['Type']) #属性字典查看数据属性 9 print(Robot.__dict__['jian_la_ji']('捡垃圾的机器人'))#属性字典查看函数属性
结果:
1 {'__module__': '__main__', 'Type': '机器', 'jian_la_ji': <function Robot.jian_la_ji at 0x00000000028819D8>, '__dict__': <attribute '__dict__' of 'Robot' objects>, '__weakref__': <attribute '__weakref__' of 'Robot' objects>, '__doc__': None} 2 机器 3 地球太脏了,机器人每天都在捡垃圾。 4 None
特殊的类属性
1 class Robot: 2 '这是一个机器人类' 3 Type = '机器' #数据属性 4 def jian_la_ji(self): #函数属性,也称为类的方法,捡垃圾是机器人的动作 5 print("地球太脏了,机器人每天都在捡垃圾。") 6 7 print(Robot.__name__) #类Robot的名字(字符串) 8 print(Robot.__doc__) #类Robot的文档字符串 9 print(Robot.__base__) #类Robot的第一个父类(在讲继承时会讲) 10 print(Robot.__bases__) #类Robot的所有父类构成的元组(在讲继承时会讲) 11 print(Robot.__dict__) #类Robot的属性 12 print(Robot.__module__)#类Robot定义所在的模块 13 print(Robot.__class__) #实例C对应的类(仅新式类中)
结果:
1 Robot 2 这是一个机器人类 3 <class 'object'> 4 (<class 'object'>,) 5 {'__module__': '__main__', '__doc__': '这是一个机器人类', 'Type': '机器', 'jian_la_ji': <function Robot.jian_la_ji at 0x00000000028719D8>, '__dict__': <attribute '__dict__' of 'Robot' objects>, '__weakref__': <attribute '__weakref__' of 'Robot' objects>} 6 __main__ 7 <class 'type'>
4.2 对象
上面说过,对象是由类实例化而来,类实例化的结果称为一个实例或者称作一个对象
4.2.1实例化的实现
1 class Robot: 2 '这是一个机器人类' 3 Type = '机器' #数据属性 4 def jian_la_ji(self): #函数属性,也称为类的方法,捡垃圾是机器人的动作 5 print("机器人在捡垃圾。") 6 7 r1 = Robot() #类名加上括号就是实例化(可以理解为函数的运行,返回值就是一个实例)
4.2.2构造函数实现
上面学过类,类有数据属性和函数属性,这里先查看下对象有什么属性。用类.__dict__()查看
1 class Robot: 2 '这是一个机器人类' 3 Type = '机器' #数据属性 4 def jian_la_ji(self): #函数属性,也称为类的方法,捡垃圾是机器人的动作 5 print("机器人在捡垃圾。") 6 7 r1 = Robot() #类名加上括号就是实例化(可以理解为函数的运行,返回值就是一个实例) 8 print(r1.__dict__)
结果:
1 {}
这里可以看出对象r1目前是没有属性的,如何为实例定制数据属性?
可以使用类的一个内置方法__init__(),该方法在类实例化是会自动执行,不必调用。
构造方法__init__()为实例定制数据属性。
1 class Robot: 2 '这是一个机器人类' 3 Type = '机器' 4 def __init__(self,name,age): 5 self.name = name 6 self.age = age 7 8 def jian_la_ji(self): 9 print("机器人在捡垃圾。") 10 11 r1 = Robot('zhangsan','100') #因为__init__()会自动执行,所以这里要传相应的参数。 12 print(r1.__dict__) #查看实例的属性
结果:
1 {'name': 'zhangsan', 'age': '100'}
这里可以看出,我们为实例定义了两个数据属性。
注意:在说实例化的时候说过,执行类()会自动返回一值,这个值就是实例,而类()会自动执行__init__,所以一定不要在该函数内定义返回值,会冲突。
4.2.3 实例属性
1.实例只有数据属性(实例的函数属性严格来说是类的函数属性)
2.del 实例/对象,只是回收了实例的数据属性,函数属性是属于类的,是不会回收。
实例化的过程实际就是执行__init__的过程,这个函数内部只是为实例本身即self设定了一堆数据(变量),所以实例只有数据属性。
1 class Robot: 2 '这是一个机器人类' 3 Type = '机器' 4 def __init__(self,name,age): 5 self.name = name 6 self.age = age 7 8 def jian_la_ji(self): 9 print("机器人在捡垃圾。") 10 11 r1 = Robot('zhangsan','100') #因为__init__()会自动执行,所以这里要传相应的参数。 12 print(r1.__dict__) #查看实例的属性,发现里面确实只有数据属性 13 print(r1.name,r1.age) #访问实例属性
结果:
1 {'name': 'zhangsan', 'age': '100'} 2 zhangsan 100
那么我们想访问实例的函数属性(其实是类的函数属性),如何实现呢?
类结合实例访问,这里主要介绍self的这个参数。
1 class Robot: 2 '这是一个机器人类' 3 Type = '机器' 4 def __init__(self,name,age): 5 self.name = name 6 self.age = age 7 8 def jian_la_ji(self): 9 print("机器人%s在捡垃圾。" %self.name) 10 11 r1 = Robot('张三','100') #因为__init__()会自动执行,所以这里要传相应的参数。 12 print(r1.__dict__) #查看实例的属性字典 13 print(Robot.__dict__) #查看类的属性字典 14 Robot.jian_la_ji(r1) #这里用类来调用jian_la_ji方法,可以看出其实self就是实例的本身,不传入会报错。
结果:
1 {'name': '张三', 'age': '100'} 2 {'__module__': '__main__', '__doc__': '这是一个机器人类', 'Type': '机器', '__init__': <function Robot.__init__ at 0x00000000028A19D8>, 'jian_la_ji': <function Robot.jian_la_ji at 0x00000000028A1A60>, '__dict__': <attribute '__dict__' of 'Robot' objects>, '__weakref__': <attribute '__weakref__' of 'Robot' objects>} 3 机器人张三在捡垃圾。
总结***:这个例子可以看出self这个参数其实就是实例的本身,生成一个实例,那么这个实例就是self
实例访问类的函数属性。
1 class Robot: 2 '这是一个机器人类' 3 Type = '机器' 4 def __init__(self,name,age): 5 self.name = name 6 self.age = age 7 8 def jian_la_ji(self): 9 print("机器人%s在捡垃圾。" %self.name) 10 11 12 r1 = Robot('张三','100') #因为__init__()会自动执行,所以这里要传相应的参数。 13 print(r1.__dict__) #实例里面只有数据属性 14 print(Robot.__dict__) #类里面既有数据属性也有函数属性 15 Robot.jian_la_ji(r1) #我们只能通过类去调用类的函数属性,然后把实例当做变量传递给self 16 r1.jian_la_ji() #其实就是Robot.jian_la_ji(r1)
结果:
1 {'name': '张三', 'age': '100'} 2 {'__module__': '__main__', '__doc__': '这是一个机器人类', 'Type': '机器', '__init__': <function Robot.__init__ at 0x00000000028619D8>, 'jian_la_ji': <function Robot.jian_la_ji at 0x0000000002861A60>, '__dict__': <attribute '__dict__' of 'Robot' objects>, '__weakref__': <attribute '__weakref__' of 'Robot' objects>} 3 机器人张三在捡垃圾。 4 机器人张三在捡垃圾。
个人总结:这里我们看到的r1调用类的函数属性,其实就是用类调用类的函数属性,然后再把我们的对象当成self传进去,所r1.jian_la_ji() 等于 Robot.jian_la_ji(r1)
5.静态属性、类方法、静态方法
5.1 静态属性
作用:静态属性是通过@property装饰器实现,把一个方法变成一个静态属性,用途是封装内部逻辑。
示例:
1 #普通调用函数方法 2 class Dog: 3 def __init__(self,name,Type): 4 self.name = name 5 self.type = Type 6 def jiao(self): 7 print("一只叫%s的%s在汪 汪 汪..." %(self.name,self.type)) 8 9 d1 = Dog('张三','京巴') 10 d1.jiao() #这里明确标识了调用的是个方法 11 12 #把方法变成静态属性。 13 class Dog1: 14 def __init__(self,name,Type): 15 self.name = name 16 self.type = Type 17 @property #把类方法变成静态属性 18 def jiao(self): 19 print("一只叫%s的%s在汪 汪 汪..." %(self.name,self.type)) 20 21 d1 = Dog1('张三','京巴') 22 d1.jiao #这里不加(),表示调用的是个静态变量。 23 #这里就是封装内部逻辑,我们在外面并不知道内部是方法还是属性。
结果:
1 一只叫张三的京巴在汪 汪 汪... 2 一只叫张三的京巴在汪 汪 汪...
5.2 类方法
作用:类方法通过@classmethod装饰器实现,类方法和普通方法的区别是, 类方法只能访问类的相关属性,不能访问实例属性;类方法在外部可以用类直接调用。
1 #################################################################################### 2 #普通方式调用类方法 3 class Dog: 4 def __init__(self,name,Type): 5 self.name = name 6 self.type = Type 7 def jiao(self): 8 print("一只叫%s的%s在汪 汪 汪..." %(self.name,self.type)) 9 10 #这里普通方式调用 11 d1 = Dog('张三','京巴') 12 Dog.jiao(d1) #用类调用方法,必须传入实例本身。 13 ##################################################################################### 14 #@classmethod调用类方法 15 class Dog1: 16 def __init__(self,name,Type): 17 self.name = name 18 self.type = Type 19 def jiao(self): 20 print("一只叫%s的%s在汪 汪 汪..." %(self.name,self.type)) 21 @classmethod #这里定义类方法 22 def chi(cls,name,Type): 23 print("一条叫%s的%s正在吃..." %(name,Type)) 24 25 #这里用类方法调用 26 Dog1.chi('李四','腊肠') 27 #注意***:这里注意类方法的参数是cls,并不是self,所以类方法不能访问实例属性,例如self.name,self.type。调用会报错。 28 #####################################################################################
结果:
1 一只叫张三的京巴在汪 汪 汪... 2 一条叫李四的腊肠正在吃...
5.3 静态方法
作用:静态方法是通过@staticmethod装饰器实现,静态方法只是名义上的归属类管理,不能访问类变量和实例变量,是类的工具包。
静态方法定义:
1 class Dog: 2 def __init__(self,name,Type): 3 self.name = name 4 self.type = Type 5 6 @staticmethod #静态方法的定义 7 def jiao(): 8 print("一只狗在汪汪汪。。。") 9 Dog.jiao() 10 #静态方法在类调用的时候不需要传入self参数。
静态方法的特性:
1 class Dog: 2 def __init__(self,name,Type): 3 self.name = name 4 self.type = Type 5 6 @staticmethod #静态方法的定义 7 def jiao(self): #这里写self以为着调用实例,会报错,说明静态方法不能访问实例变量。 8 print("一只%s在汪汪汪。。。" %self.name) 9 10 Dog.jiao()
结果:
1 Traceback (most recent call last): 2 File "E:/BaiduYunDownload/python_study/module/test.py", line 105, in <module> 3 Dog.jiao() 4 TypeError: jiao() missing 1 required positional argument: 'self'
说明:事实上以上代码运行会出错的,说jiao需要一个self参数,但调用时却没有传递,没错,当jiao方法变成静态方法后,再通过实例调用时就不会自动把实例本身当作一个参数传给self了。
以上代码不报错有两种解决方法。
1.在调用时将实例本身传给 jiao()
1 class Dog: 2 def __init__(self,name,Type): 3 self.name = name 4 self.type = Type 5 6 @staticmethod #静态方法的定义 7 def jiao(self): 8 print("一只%s在汪汪汪。。。" %self.name) 9 d1 = Dog('张三','京巴') 10 11 Dog.jiao(d1) #这里传递实例本身
2.在方法jiao中去掉self,但这也意味着,在jiao中不能通过self.调用实例中的其它变量了
1 class Dog: 2 def __init__(self,name,Type): 3 self.name = name 4 self.type = Type 5 6 @staticmethod #静态方法的定义 7 def jiao(): #方法里面没有self参数了,说明此方法和类没有什么关系了。 8 print("一只狗在汪汪汪。。。") 9 d1 = Dog('张三','京巴') 10 11 d1.jiao()
静态方法总结:普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类变量,但静态方法是不可以访问实例变量或类变量的,一个不能访问实例变量和类变量的方法,其实相当于跟类本身已经没什么关系了,它与类唯一的关联就是需要通过类名来调用这个方法,所以可以理解为用类调用方法的一个工具包。
6.组合
6.1 组合的概念
定义一个人的类,人有头,躯干,手脚等数据属性,这几个属性又可以是通过一个类实例化的对象,这就是组合。 #简单理解:就是大类包含小类,就是组合。
简单理解:就是大类包含小类,就是组合。
用途:
- 把类做关联。
- 小类组成大类。
组合的示例:
1 class Hand: 2 pass 3 4 class Foot: 5 pass 6 7 class Trunk: 8 pass 9 10 class Head: 11 pass 12 13 class Person: 14 def __init__(self,id_num,name): 15 self.id_num=id_num #身份证号码 16 self.name=name 17 self.hand=Hand() 18 self.foot=Foot() 19 self.trunk=Trunk() 20 self.head=Head() 21 22 p1=Person('111111','zhangsan') #给实例传值print(p1.__dict__) #查看实例的属性字典 23 print(p1.__dict__)
结果:
1 {'foot': <__main__.Foot object at 0x0000028CC3B74470>, 'head': <__main__.Head object at 0x0000028CC3B744E0>, 'name': 'zhangsan', 'trunk': <__main__.Trunk object at 0x0000028CC3B744A8>, 'hand': <__main__.Hand object at 0x0000028CC3B74438>, 'id_num': '111111'}
总结:类和类之间没有共同点,但是之间有关联就需要用组合,比如学校和老师,没有共同点,但是有关联性。
7.面向对象三大特性
7.1 继承
7.1.1 继承基本原理
1) 什么是类的继承?
定义:类的继承跟现实生活中的父、子、孙子、重孙子、继承关系一样,父类又称为基类。
继承的示例:
1 #在python中继承分为单继承和多继承,如下: 2 class ParentClass1: 3 pass 4 5 class ParentClass2: 6 pass 7 8 class SubClass(ParentClass1): #单继承 9 pass 10 11 class SubClass(ParentClass1,ParentClass2): #多继承 12 pass
2) 子类继承了父类的什么属性?
1 class Father: 2 '这个是父亲类' 3 money = 10000 4 def __init__(self,name): 5 self.name = name 6 7 def play_basketball(self): 8 print("%s 在打篮球(父类)" %self.name) 9 '这个是儿子类' 10 class Son(Father): 11 money = 100 12 def play_basketball(self): 13 print("%s 在打篮球(子类)" %self.name) 14 15 s1 = Son('张三') 16 print(s1.money) #子类自己有属性就用自己的,没有就用父类的,父类没有就会报错 17 print(Father.money) #父类的属性不变 18 s1.play_basketball() #子类继承了父类的方法。
结果:
1 100 2 10000 3 张三 在打篮球(子类)
总结:子类可以继承父类的数据属性和函数属性,当子类内部有相同的数据和函数属性的时候默认用内部的。其次用父类的。
3) 什么时候用继承?
1.当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
例如:描述一个机器人类,机器人这个大类是由很多互不相关的小类组成,如机械胳膊类、腿类、身体类、电池类
2.当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好
例如
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,如下所示:
1 class 猫: 2 3 def 喵喵叫(self): 4 print '喵喵叫' 5 6 def 吃(self): 7 # do something 8 9 def 喝(self): 10 # do something 11 12 def 拉(self): 13 # do something 14 15 def 撒(self): 16 # do something 17 18 class 狗: 19 20 def 汪汪叫(self): 21 print '汪汪叫' 22 23 def 吃(self): 24 # do something 25 26 def 喝(self): 27 # do something 28 29 def 拉(self): 30 # do something 31 32 def 撒(self): 33 # do something
上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:
动物:吃、喝、拉、撒
猫:喵喵叫(猫继承动物的功能)
狗:汪汪叫(狗继承动物的功能)
1 class 动物: 2 3 def 吃(self): 4 # do something 5 6 def 喝(self): 7 # do something 8 9 def 拉(self): 10 # do something 11 12 def 撒(self): 13 # do something 14 15 # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 16 class 猫(动物): 17 18 def 喵喵叫(self): 19 print '喵喵叫' 20 21 # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 22 class 狗(动物): 23 24 def 汪汪叫(self): 25 print '汪汪叫'
实现:
1 class Animal:
2
3 def eat(self):
4 print("%s 吃 " %self.name)
5
6 def drink(self):
7 print ("%s 喝 " %self.name)
8
9 def shit(self):
10 print ("%s 拉 " %self.name)
11
12 def pee(self):
13 print ("%s 撒 " %self.name)
14
15
16 class Cat(Animal):
17
18 def __init__(self, name):
19 self.name = name
20 self.breed = '猫'
21
22 def cry(self):
23 print('喵喵叫')
24
25 class Dog(Animal):
26
27 def __init__(self, name):
28 self.name = name
29 self.breed='狗'
30
31 def cry(self):
32 print('汪汪叫')
33
34
35 # ######### 执行 #########
36
37 c1 = Cat('小白家的小黑猫')
38 c1.eat()
39
40 c2 = Cat('小黑的小白猫')
41 c2.drink()
42
43 d1 = Dog('胖子家的小瘦狗')
44 d1.eat()
7.1.2 继承同时具有两种含义
含义一.继承基类的方法,并且做出自己的改变或者扩展(代码重用)
含义二.声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现接口中定义的方法
接口继承实现示例:
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2017/8/26 18:48 4 # @Author : All is well 5 # @Site : 6 # @File : 接口继承.py 7 # @Software: PyCharm 8 9 import abc 10 class Interface(metaclass=abc.ABCMeta):#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 11 @abc.abstractmethod #装饰器,含义是让子类代码必须有read这个方法 12 def read(self): #定接口函数read 13 pass 14 15 @abc.abstractmethod #装饰器,含义是让子类代码必须有write这个方法 16 def write(self): #定义接口函数write 17 pass 18 19 20 class Txt(Interface): #文本,具体实现read和write 21 def read(self): 22 print('文本数据的读取方法') 23 24 def write(self): 25 print('文本数据的读取方法') 26 27 class Sata(Interface): #磁盘,具体实现read和write 28 def read(self): 29 print('硬盘数据的读取方法') 30 31 def write(self): 32 print('硬盘数据的读取方法') 33 34 class Process(Interface): #进程,具体实现read和write 35 def read(self): 36 print('进程数据的读取方法') 37 38 def write(self): 39 print('进程数据的读取方法') 40 41 p1 = Process() 42 p1.read() 43 p1.write() 44 45 # 结果: 46 # 进程数据的读取方法 47 # 进程数据的读取方法
总结:
1、实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
2、继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
3、归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合,就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
7.1.3 继承的顺序
继承在python不通版本中的区别:
python2.x:新式类与经典类
python3.x:新式类(定义继承object)
因为在python3.x中都是新式类,一下用python2.x的版本介绍新式类与经典类继承顺序的区别。
示例图:

新式类:
1 class A(object): 2 3 def test(self): 4 print('A') 5 6 class B(A): 7 def test(self): 8 print('B') 9 10 class C(A): 11 def test(self): 12 print('C') 13 14 class D(B): 15 def test(self): 16 print('D') 17 18 class E(C): 19 def test(self): 20 print('E') 21 22 class F(D,E): 23 def test(self): 24 print('F') 25 26 f1=F() 27 f1.test() 28 print(F.__mro__) #python2中没有这个属性,可以查看类的继承顺序 29 #新式类继承顺序:F->D->B->E->C->A
经典类:
1 class A: 2 3 def test(self): 4 print('A') 5 6 class B(A): 7 def test(self): 8 print('B') 9 10 class C(A): 11 def test(self): 12 print('C') 13 14 class D(B): 15 def test(self): 16 print('D') 17 18 class E(C): 19 def test(self): 20 print('E') 21 22 class F(D,E): 23 def test(self): 24 print('F') 25 26 f1=F() 27 f1.test() 28 #经典类继承顺序:F->D->B->A->E->C
总结:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
7.1.4 子类调用父类的方法
子类继承了父类的方法,然后想进行修改,注意是基于原有的基础上修改,那么就需要在子类中调用父类的方法
方法一:父类名.父类方法() ===>相当于在子类中调用父类的方法
1 class Person: 2 def __init__(self,name,age,gender): 3 self.name = name 4 self.age = age 5 self.gender = gender 6 7 class Student(Person): 8 def __init__(self,name,age,gender,score): 9 Person.__init__(self,name,age,gender) #子类继承父类方法。 10 self.score = score #这里只填写子类要增加的属性。 11 12 s1 = Student('all is well','23','M',99) 13 14 print(s1.name) 15 print(s1.age) 16 print(s1.gender) 17 print(s1.score)
结果:
1 all is well 2 23 3 M 4 99
方法二:super方法
1 class Person: 2 def __init__(self,name,age,gender): 3 self.name = name 4 self.age = age 5 self.gender = gender 6 7 class Student(Person): 8 def __init__(self,name,age,gender,score): 9 super().__init__(name,age,gender) #子类继承父类方法。super方法 10 #super(Student, self).__init__(name,age,gender) #子类继承父类方法。super方法 11 self.score = score #这里只填写子类要增加的属性。 12 13 s1 = Student('all is well','23','M',99) 14 15 print(s1.name) 16 print(s1.age) 17 print(s1.gender) 18 print(s1.score)
结果:
1 all is well
2 23
3 M
4 99
总结:从运行结果上看,普通继承和super继承是一样的。但是其实它们的内部运行机制不一样,这一点在多重继承时体现得很明显。在super机制里可以保证公共父类仅被执行一次,至于执行的顺序,是按照mro进行的(E.__mro__)。
注意:super继承只能用于新式类,用于经典类时就会报错。
7.2 多态
7.2.1 多态简介
定义:多态可以理解为继承的扩展,由不同的类实例化得到的对象,调用同一个方法,执行的逻辑却不同。
表现:
1.调用不同的类实例化得对象下的相同的方法,实现的过程不一样。
2.python中的标准类型就是多态概念的一个很好的示范 。
标准类型多态示例:
1 s1='all is well' 2 l=[1,2] 3 print(s1.__len__()) 4 print(l.__len__()) 5 6 #结果 7 11 8 2 9 10 #调用同样的__len__方法,结果却不一样。
自定义示例1:
1 class H2O: 2 def __init__(self,name,temperature): #名字,温度 3 self.name=name 4 self.temperature=temperature 5 def turn_ice(self): 6 if self.temperature < 0: 7 print('[%s]温度太低结冰了' %self.name) 8 elif self.temperature >0 and self.temperature < 100: 9 print('[%s]液化成水' % self.name) 10 elif self.temperature > 100: 11 print('[%s]温度太高变成了水蒸气' % self.name) 12 13 class Water(H2O): #继承父类(H2O) 14 pass 15 16 class Ice(H2O): 17 pass 18 19 class Steam(H2O): 20 pass 21 22 w1 = Water('水', 25) 23 i1 = Ice('冰', -20) 24 s1 = Steam('蒸汽', 3000) 25 26 def func(obj): 27 obj.turn_ice() 28 29 func(w1) #---->w1.turn_ice() 30 func(i1) #---->i1.turn_ice() 31 func(s1) #---->s1.turn_ice()
结果:
1 [水]液化成水 2 [冰]温度太低结冰了 3 [蒸汽]温度太高变成了水蒸气
自定义示例2:
1 class Person: 2 def __init__(self, name, gender): 3 self.name = name 4 self.gender = gender 5 def whoAmI(self): 6 return 'I am a Person, my name is %s' % self.name 7 8 class Student(Person): 9 def __init__(self, name, gender, score): 10 super(Student, self).__init__(name, gender) 11 self.score = score 12 def whoAmI(self): 13 return 'I am a Student, my name is %s' % self.name 14 15 class Teacher(Person): 16 def __init__(self, name, gender, course): 17 super(Teacher, self).__init__(name, gender) 18 self.course = course 19 def whoAmI(self): 20 return 'I am a Teacher, my name is %s' % self.name 21 22 def who_am_i(x): 23 print(x.whoAmI()) 24 25 p = Person('zhangsan', 'Male') 26 s = Student('lisi', 'Male', 88) 27 t = Teacher('wangwu', 'Female', 'English') 28 who_am_i(p) 29 who_am_i(s) 30 who_am_i(t)
结果:
1 I am a Person, my name is zhangsan 2 I am a Student, my name is lisi 3 I am a Teacher, my name is wangwu
7.3 封装
7.3.1 封装简介
什么是封装?
从字面上理解装,装的意思就是把所有东西装起来,就比如皮箱,把所有衣服都装进皮箱,而封就是把皮箱的拉链拉上,这样衣服就被封装起来了。
在面向对象中这个皮箱就是你的类或者对象,类或者对象这俩皮箱内部装了数据属性和函数属性,那么对于类和对象来说‘封’的概念从何而来,其实封的概念代表隐藏。
定义:“封装”就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体(即类);封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,一特定的访问权限来使用类的成员。
7.3.2 python中的约定
约定一:任何以单下划线开头的名字都应该是内部的,私有的。(只是个约定)
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2017/8/20 21:18 4 # @Author : All is well 5 # @Site : 6 # @File : 封装.py 7 # @Software: PyCharm 8 9 class People: 10 _star='earth' #以单_线开头的,就是隐藏属性(外部看不到他),例如import * 就会报错。 11 12 def __init__(self,id,name,age,salary): 13 self.id=id 14 self.name=name 15 self.age=age 16 self.salary=salary 17 18 19 #封装使用者,看不到里面使用的逻辑 20 p1=People('19951221','all is well','23',10000) 21 print(p1._star) #这里p1.star会报错,但是p1._start可以调用,只是约定,不是强制性。 22 23 #结果:earth
约定二:双下划线开头的名字
双下划线开头的属性在继承给子类时,子类是无法覆盖的(原理也是基于python自动做了双下滑线开头的名字的重命名工作)
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2017/8/20 21:18 4 # @Author : All is well 5 # @Site : 6 # @File : 封装.py 7 # @Software: PyCharm 8 9 class People: 10 __star='earth' #以双__线开头的,python会自动重命名。 11 12 def __init__(self,id,name,age,salary): 13 self.id=id 14 self.name=name 15 self.age=age 16 self.salary=salary 17 18 19 #封装使用者,看不到里面使用的逻辑 20 p1=People('19951221','all is well','23',10000) 21 #print(p1.__star) #这里调用会报错,因为python会重命名 22 print(p1.__dict__) #查看属性字典发现,__star被重命名为_People__star 23 print(p1._People__star) #但是这么也可以调用,仅仅也是一种约定。 24 25 #结果:earth
总结:python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的。
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,仍然可以用。
遵循约定:遵循以上约定的才是真正的封装,明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用。(这才是真正的封装。不仅仅是封装这个层面)
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2017/8/20 21:18 4 # @Author : All is well 5 # @Site : 6 # @File : 封装.py 7 # @Software: PyCharm 8 9 #封装实际的意义就是给用户来用,但是我们知道用户需要知道的我就不封装,用户不需要的我就封装起来,这样才切合实际。 10 #比如把用户经常用的东西封装起来了,那么用户用的时候我就需要在封装好逻辑上开一个口,让用户来用,多了就会开很多口子,后期会导致程序可读性变低。 11 12 #封装 13 #1.我定义好这个房间的属性 14 #2.其中房间的长宽高我想给别人看,设置为私有 15 #3 用户有需求要用到我的私有属性 16 #4 为了让用户调用我的自由属性,我不得不开一个缺口 17 #5 开口就以为着私有属性不能更改了,因为已经开好缺口了,改变会影响开缺口的方法不可用。 18 19 class Room: 20 def __init__(self,name,owner,width,length,high): 21 self.name = name 22 self.owner = owner 23 self.__width = width 24 self.__length = length 25 self.__high = high 26 def tell_area(self): #这里要求用户需要的面积 27 return "房间的面积为",self.__width * self.__length #在内部可以调用封装的属性 28 def tell_volume(self): 29 return "房间的体积为", self.__width * self.__length * self.__high # 在内部可以调用封装的属性 30 31 #这里一个房子属性封装好了 32 r1 = Room("一居室",'all is well',100,100,1000) 33 34 #比如这个时候用户要获取房间的面积。 35 #area = r1.__width * r1.__length #这样做会报错。 36 37 #所以我们就需要开缺口,(tell_area) 38 area = r1.tell_area() 39 print(area) 40 #如果用户要获取房间的体积呢?我们不得不又要开缺口。(tell_volume) 41 volume = r1.tell_volume() 42 print(volume) 43 44 #总结:真正意义上的封装就是要有内有外,内部实现逻辑外部无法知晓,特例可以开缺口,但是定义上尽量不要把不该封装的封装,造成以后不必要的麻烦。
总结:
上面提到有两种不同的编码约定(单下划线和双下划线 )来命名私有属性,那么问 题就来了:到底哪种方式好呢?大多数而言,你应该让你的非公共名称以单下划线开 头。但是,如果你清楚你的代码会涉及到子类,并且有些内部属性应该在子类中隐藏 起来,那么才考虑使用双下划线方案。 但是无论哪种方案,其实python都没有从根本上限制你的访问。
8.Python中关于常用的OOP术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。 继承描述了子类属性从祖先类继承这样一种方式 继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承 泛化表示所有子类与其父类及祖先类有一样的特点。 特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态
多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
多态表明了动态(又名,运行时)绑定的存在,允计重载及运行时类型确定和验证。
举例:
水是一个类
不同温度,水被实例化成了不同的状态:冰,水蒸气,雾(然而很多人就理解到这一步就任务此乃多态,错,fuck!,多态是运行时绑定的存在)
(多态体现在由同一个类实例化出的多个对象,这些对象执行相同的方法时,执行的过程和结果是不一样的)
冰,水蒸气,雾,有一个共同的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的两个过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__

浙公网安备 33010602011771号