一、面向对象三大特性
什么是类的继承?
类的继承跟现实生活中的父、子、孙子、重孙子、继承关系一样,父类又称为基类。
python中类的继承分为:单继承和多继承
1、继承
1 class ParentClass1:
2 pass
3
4 class ParentClass2:
5 pass
6
7 class SubClass(ParentClass1): #单继承
8 pass
9
10 class SubClass(ParentClass1,ParentClass2): #多继承
11 pass
2、子继承到底继承了父类的什么属性?
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 #继承
6 class Dad:
7 '这个是爸爸类'
8 money=10
9 def __init__(self,name):
10 print('爸爸')
11 self.name=name
12
13 def hit_son(self):
14 print('%s 正在打儿子' %self.name)
15
16 '这个是儿子类'
17 class Son(Dad):
18 money = 10000000000000
19
20 s1=Son('alex') #继承父类的属性
21 print(s1.money)
22 print(Dad.money)
23 # print(Dad.__dict__)
24 # print(Son.__dict__)
执行结果:
1 爸爸
2 10000000000000
3 10
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()
3、继承同时具有两种含义
含义一.继承基类的方法,并且做出自己的改变或者扩展(代码重用)
含义二.声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现接口中定义的方法
归一化式继承方式实现示例:
1 #接口继承
2
3 import abc
4 class All_file(metaclass=abc.ABCMeta):
5 @abc.abstractmethod #就是一种规范
6 def read(self):
7 pass
8
9 @abc.abstractmethod #装饰器
10 def write(self):
11 pass
12
13 class Disk(All_file):
14 def read(self):
15 print('disk read')
16
17 def write(self):
18 print('disk write')
19
20 class Cdrom(All_file):
21 def read(self):
22 print('cdrom read')
23
24 def write(self):
25 print('cdrom write')
26
27 class Mem(All_file):
28 def read(self):
29 print('mem read')
30
31 def write(self):
32 print('mem write')
33
34 m1=Mem()
35 m1.read()
36 m1.write()
执行结果:
1 mem read
2 mem write
1、实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
2、继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
3、归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合,就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
4、继承顺序
继承在Python不同版本中是有区别的。
python3: 新式类
pyhon2: 新式类与经典类
新式类示例:
1 #继承顺序就是mro列表定义的顺序
2
3 class A:
4 def test(self):
5 print(' A')
6
7 class B(A):
8 def test(self):
9 print(' B')
10
11 class C(A):
12 def test(self):
13 print(' C')
14
15 class D(B):
16 def test(self):
17 print(' D')
18
19 class E(C):
20 def test(self):
21 print(' E')
22
23 class F(D,E):
24 # def test(self):
25 # print(' F')
26 pass
27 f1=F()
28 f1.test()
29 print(F.__mro__) #python2中没有这个属性
30
31 #新式类继承顺序:F->D->B->E->C->A
32 #经典类继承顺序:F->D->B->A->E->C
经典类示例
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4 class A:
5 def test(self):
6 print('A')
7 pass
8
9 class B(A):
10 def test(self):
11 print('B')
12 pass
13
14 class C(A):
15 def test(self):
16 print('C')
17 pass
18
19 class D(B):
20 def test(self):
21 print('D')
22 pass
23
24 class E(C):
25 def test(self):
26 print('E')
27 pass
28
29 class F(D,E):
30 def test(self):
31 print('F')
32 pass
33 f1=F()
34 f1.test() #经典类:F->D->B->A-->E-->C
执行结果:
1 F->D->B->A-->E-->C
总结:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
5、子类中调用父类方法
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法
方法一:父类名.父类方法() ===>相当于在子类中调用父类的方法
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4 #子类中调用父类的方法
5 class Vehicle:
6 Country='China'
7
8 def __init__(self,name,speed,load,power):
9 self.name=name
10 self.speed=speed
11 self.load=load
12 self.power=power
13
14 def run(self):
15 print('开动啦')
16 print('开动啦')
17
18 class Subway(Vehicle):
19 def __init__(self,name,speed,load,power,line):
20 Vehicle.__init__(self,name,speed,load,power)
21 self.line=line
22
23 def show_info(self):
24 print(self.name,self.speed,self.load,self.power,self.line)
25
26 def run(self):
27 Vehicle.run(self)
28 print('%s %s 线,开动啦' %(self.name,self.line))
29 line13=Subway('北京地铁','100m/s',1000000,'电',13)
30
31 line13.show_info()
32
33 line13.run()
执行结果:
1 北京地铁 100m/s 1000000 电 13
2 开动啦
3 开动啦
4 北京地铁 13 线,开动啦
方法二:用super方法
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 class Vehicle:
6 Country = 'China'
7
8 def __init__(self, name, speed, load, power):
9 self.name = name
10 self.speed = speed
11 self.load = load
12 self.power = power
13
14 def run(self):
15 print('开动啦')
16 print('开动啦')
17
18
19 class Subway(Vehicle):
20 def __init__(self, name, speed, load, power, line):
21 #super用法,传参数和不传参数是一样的
22 super().__init__(name, speed, load, power) #这两个作用是一样的,一个不传参数
23 #super(Subway,self).__init__(name, speed, load, power) #这两个作用是一样的,一个传参数,
24 self.line = line
25
26 def show_info(self):
27 print(self.name, self.speed, self.load, self.power, self.line)
28
29 def run(self):
30 super().run() #用super的方法
31 print('%s %s 线,开动啦' % (self.name, self.line))
32
33
34 line13 = Subway('北京地铁', '100m/s', 1000000, '电', 13)
35 line13.show_info()
36 line13.run()
二、多态
多态:表明了动态绑定的存在,调用了不同的方法。才能展示出来。反映的是运行时候的状态。
什么是多态?
由不同的类实例化得到的对象,调用同一个方法,执行的逻辑不同。
示例1:
1 s1='abc'
2 l=[1,2]
3 s1.__len__()
4 l.__len__()
示例2:
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 #Author: nulige
4
5 class H2O:
6 def __init__(self,name,temperature): #名字,温度
7 self.name=name
8 self.temperature=temperature
9 def turn_ice(self):
10 if self.temperature < 0:
11 print('[%s]温度太低结冰了' %self.name)
12 elif self.temperature >0 and self.temperature < 100:
13 print('[%s]液化成水' % self.name)
14 elif self.temperature > 100:
15 print('[%s]温度太高变成了水蒸气' % self.name)
16
17 class Water(H2O): #继承父类(H2O)
18 pass
19
20 class Ice(H2O):
21 pass
22
23 class Steam(H2O):
24 pass
25
26 w1 = Water('水', 25)
27 i1 = Ice('冰', -20)
28 s1 = Steam('蒸汽', 3000)
29
30 def func(obj):
31 obj.turn_ice()
32
33 func(w1) #---->w1.turn_ice()
34 func(i1) #---->i1.turn_ice()
35 func(s1) #---->s1.turn_ice()
执行结果:
1 [水]液化成水
2 [冰]温度太低结冰了
3 [蒸汽]温度太高变成了水蒸气
面向对象三大特性,封装,多态,继承其实这种逻辑是错的,但很多人都这么说。
类的继承有两层意义:1.改变 2.扩展
1、多态就是类的这两层意义的一个具体的实现机制
3、调用不同的类实例化得对象下的相同的方法,实现的过程不一样。
4、python中的标准类型就是多态概念的一个很好的示范 。
三、封装
封装是啥,抛开面向对象,你单去想什么是装,装就是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,什么是封,封就是把麻袋封上口子。
在面向对象中这个麻袋就是你的类或者对象,类或者对象这俩麻袋内部装了数据属性和函数属性,那么对于类和对象来说‘封’的概念从何而来,其实封的概念代表隐藏。
约定一:任何以单下划线开头的名字都应该是内部的,私有的。
示例1
1 class People:
2 _star='earth' #以单_线开头的,就是隐藏属性(外部看不到他)
3 __star='earth1111111111111111' #python会自动重命名
4
5 def __init__(self,id,name,age,salary):
6 self.id=id
7 self.name=name
8 self._age=age
9 self._salary=salary
10
11 def _get_id(self):
12 print('我是私有方法啊,我找到的id是[%s]' %self.id)
13
14 # print(People.__dict__)
15
16 #封装使用者,看不到里面使用的逻辑
17 p1=People('123123123123','alex','18',1000000000)
18 print(p1._star)
执行结果:
1 earth
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的。
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,仍然可以用。
约定二:双下划线开头的名字
双下划线开头的属性在继承给子类时,子类是无法覆盖的(原理也是基于python自动做了双下滑线开头的名字的重命名工作)
示例2:
1 class People:
2 __star='earth1111111111111111' #python会自动重命名
3 def __init__(self,id,name,age,salary):
4 self.id=id
5 self.name=name
6 self._age=age
7 self._salary=salary
8
9 def _get_id(self):
10 print('我是私有方法啊,我找到的id是[%s]' %self.id)
11
12 #封装使用者,看不到里面使用的逻辑
13 p1=People('123123123123','alex','18',1000000000)
14 print(p1._People__star)
执行结果:
1 earth1111111111111111
第三个层面的封装:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用。(这才是真正的封装。)
1 class People:
2 __star='earth'
3 def __init__(self,id,name,age,salary):
4 self.id=id
5 self.name=name
6 self.__age=age
7 self._salary=salary
8
9 def _get_id(self):
10 print('我是私有方法啊,我找到的id是[%s]' %self.id)
11
12 class Korean(People):
13 __star = '火星'
14 pass
15
16
17 print(People.__dict__)#__star存到类的属性字典中被重命名为_People__star
18 print(Korean.__dict__)
19
20 # print(Korean.__star)#傻逼,这么访问当然报错啦,__star被重命名了,忘记了?
21
22 print(Korean._Korean__star)
23 print(Korean._People__star)
24
25 双下划线开头在继承中的应用
总结:
上面提到有两种不同的编码约定(单下划线和双下划线 )来命名私有属性,那么问 题就来了:到底哪种方式好呢?大多数而言,你应该让你的非公共名称以单下划线开 头。但是,如果你清楚你的代码会涉及到子类,并且有些内部属性应该在子类中隐藏 起来,那么才考虑使用双下划线方案。 但是无论哪种方案,其实python都没有从根本上限制你的访问。
四、面向对象的优点
从编程进化论我们得知,面向对象是一种更高等级的结构化编程方式,它的好处就两点
1:通过封装明确了内外,你作为类的缔造者,你是上帝,上帝造物的逻辑你无需知道(你知道了你tm也成上帝了),上帝想让你知道的你才能知道,这样就明确了划分了等级,物就是调用者,上帝就是物的创造者
2:通过继承+多态在语言层面支持了归一化设计
注意:不用面向对象语言(即不用class),一样可以做归一化(如老掉牙的泛文件概念、游戏行业的一切皆精灵),一样可以封装(通过定义模块和接口),只是用面向对象语言可以直接用语言元素显式声明这些而已;而用了面向对象语言,满篇都是class,并不等于就有了归一化的设计。甚至,因为被这些花哨的东西迷惑,反而更加不知道什么才是设计
五、Python中关于常用的OOP术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态
多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
多态表明了动态(又名,运行时)绑定的存在,允计重载及运行时类型确定和验证。
举例:
水是一个类
不同温度,水被实例化成了不同的状态:冰,水蒸气,雾(然而很多人就理解到这一步就任务此乃多态,错,fuck!,多态是运行时绑定的存在)
(多态体现在由同一个类实例化出的多个对象,这些对象执行相同的方法时,执行的过程和结果是不一样的)
冰,水蒸气,雾,有一个共同的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的两个过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__