继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python
支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
子类会“”
遗传
”
父类的属性,从而解决代码重用问题。
1
2
3
4
5
6
7
8
9
10
11
|
class ParentClass1: #定义父类
pass
class ParentClass2: #定义父类
pass
class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
pass
|
查看继承
1
2
3
4
|
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
|
在开发程序的过程中,如果我们定义了一个类A
,然后又想新建立另外一个类
B
,但是类
B
的大部分内容与类
A
的相同时
我们不可能从头开始写一个类B
,这就用到了类的继承的概念。
通过继承的方式新建类B
,让
B
继承
A
,
B
会
‘
遗传
’A
的所有属性
(
数据属性和函数属性
)
,实现代码重用
2. 多继承
· Python
的类可以继承多个类,
Java
和
C#
中则只能继承一个类
· Python
的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:
深度优先
和
广度优先
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml5116\wps1.png
·
当类是经典类时,多继承情况下,会按照深度优先方式查找
·
当类是新式类时,多继承情况下,会按照广度优先方式查找
经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果
当前类或者父类继承了object
类
,那么该类便是新式类,否则便是经典类。
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml5116\wps2.png file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml5116\wps3.png
1.
只有在
python2
中才分新式类和经典类,
python3
中统一都是新式类
2.
在
python2
中,没有显式的继承
object
类的类,以及该类的子类,都是经典类
3.
在
python2
中,显式地声明继承
object
的类,以及该类的子类,都是新式类
4.
在
python3
中,无论是否继承
object
,都默认继承
object
,即
python3
中所有类均为新式
类
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml5116\wps4.png
经典类多继承
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml5116\wps5.png
新式类多继承
3. 继承原理
python
到底是如何实现继承的,对于你定义的每一个类,
python
会计算出一个方法解析顺序
(MRO)
列表,这个
MRO
列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #
等同于
F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python
会在
MRO
列表上从左到右开始查找基类
,
直到找到第一个匹配这个属性的类为止。
而这个MRO 列表的构造是通过一个 C3 线性化算法来实现的。我们不去深究这个算法的数学原理 , 它实际上就是合并所有父类的 MRO 列表并遵循如下三条准则 :
1. 子类会先于父类被检查
2. 多个父类会根据它们在列表中的顺序被检查
3. 如果对下一个类存在两个合法的选择 , 选择第一个父类
而这个MRO 列表的构造是通过一个 C3 线性化算法来实现的。我们不去深究这个算法的数学原理 , 它实际上就是合并所有父类的 MRO 列表并遵循如下三条准则 :
1. 子类会先于父类被检查
2. 多个父类会根据它们在列表中的顺序被检查
3. 如果对下一个类存在两个合法的选择 , 选择第一个父类
4. 在子类中调用父类的方法
·
指明道姓
1
2
3
4
5
6
7
8
9
10
11
|
class Dad(object):
money = 100000000
def __init__(self, name, age):
self.name = name
self.age = age
def hit_son(self):
print('%s 正在打儿子' % self.name)
class Son(Dad):
def __init__(self, name, age):
Dad.__init__(self, name, age)
|
· super
1
2
3
4
5
6
7
8
9
10
11
|
class Dad(object):
money = 100000000
def __init__(self, name, age):
self.name = name
self.age = age
def hit_son(self):
print('%s 正在打儿子' % self.name)
class Son(Dad):
def __init__(self, name, age):
super().__init__(name, age)
|
5. 接口与归一化设计
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml5116\wps6.png java
的接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处在于:
1.
归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2.
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
2.1
:就好象
linux
的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出
“
字符设备
”
和
“
块设备
”
,然后做出针对性的设计:细致到什么程度,视需求而定)。
2.2
:再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
在python
中根本就没有一个叫做
interface
的关键字,如果非要去模仿接口的概念可以借助第三方模块:
文档https://zopeinterface.readthedocs.io/en/latest/
设计模式:https://github.com/faif/python-patterns
也可以使用继承:
继承的两种用途
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
二:声明某个子类兼容于某基类,定义一个接口类(模仿java
的
Interface
),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml5116\wps7.png View Code
上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口
,这就用到了
抽象类
1 什么是抽象类
与
java
一样,
python
也有抽象类的概念但是同样需要借助模块实现,
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
2 为什么要有抽象类
如果说
类是从
一堆
对象
中抽取相同的内容而来的,那么
抽象类
就
是从
一堆
类
中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:
抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法
。这一点与接口有点类似,但其实是不同的,即将揭晓答案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
#一切皆文件
import abc #利用abc模块实现抽象类
class All_file(metaclass=abc.ABCMeta):
all_type='file'
@abc.abstractmethod #定义抽象方法,无需实现功能
def read(self):
'子类必须定义读功能'
pass
@abc.abstractmethod #定义抽象方法,无需实现功能
def write(self):
'子类必须定义写功能'
pass
# class Txt(All_file):
# pass
#
# t1=Txt() #报错,子类没有定义抽象方法
class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('文本数据的读取方法')
def write(self):
print('文本数据的读取方法')
class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('硬盘数据的读取方法')
def write(self):
print('硬盘数据的读取方法')
class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('进程数据的读取方法')
def write(self):
print('进程数据的读取方法')
wenbenwenjian=Txt()
yingpanwenjian=Sata()
jinchengwenjian=Process()
#这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()
print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
|
二、多态
多态指的是一类事物有多种形态
动物有多种形态:人,狗,猪
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #动物的形态之一:人
def talk(self):
print('say hello')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('say aoao')
|
三、封装1. 隐藏
在python
中用双下划线开头的方式将属性隐藏起来(设置成私有的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形
#类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式:
class A:
__N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
def __init__(self):
self.__X=10 #变形为self._A__X
def __foo(self): #变形为_A__foo
print('from A')
def bar(self):
self.__foo() #只有在类内部才可以通过__foo的形式访问到.
#A._A__N是可以访问到的,
#这种,在外部是无法通过__x这个名字访问到。
|
2. 封装
封装的真谛在于
明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,让外部能够间接地用到我们隐藏起来的属性,那这么做的意义何在???
1
:封装数据
:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Teacher:
def __init__(self,name,age):
# self.__name=name
# self.__age=age
self.set_info(name,age)
def tell_info(self):
print('姓名:%s,年龄:%s' %(self.__name,self.__age))
def set_info(self,name,age):
if not isinstance(name,str):
raise TypeError('姓名必须是字符串类型')
if not isinstance(age,int):
raise TypeError('年龄必须是整型')
self.__name=name
self.__age=age
t=Teacher('egon',18)
t.tell_info()
t.set_info('egon',19)
t.tell_info()
|
2
:封装方法
:
目的是隔离复杂度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
#隔离了复杂度,同时也提升了安全性
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('用户认证')
def __input(self):
print('输入取款金额')
def __print_bill(self):
print('打印账单')
def __take_money(self):
print('取款')
def withdraw(self):
self.__card()
self.__auth()
self.__input()
self.__print_bill()
self.__take_money()
a=ATM()
a.withdraw()
隔离复杂度的例子
|
3. 特性(propety)
property
是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
import math
class Circle:
def __init__(self,radius): #圆的半径radius
self.radius=radius
@property
def area(self):
return math.pi * self.radius**2 #计算面积
@property
def perimeter(self):
return 2*math.pi*self.radius #计算周长
c=Circle(10)
print(c.radius)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''
#注意:此时的特性arear和perimeter不能被赋值
c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Foo:
def __init__(self,val):
self.__NAME=val #将所有的数据属性都隐藏起来
@property
def name(self):
return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
@name.setter
def name(self,value):
if not isinstance(value,str): #在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
@name.deleter
def name(self):
raise TypeError('Can not delete')
f=Foo('egon')
print(f.name)
# f.name=10 #抛出异常'TypeError: 10 must be str'
del f.name #抛出异常'TypeError: Can not delete'
|
文章转载至:https://www.cnblogs.com/lsf123456/p/11197800.html
更多技术资讯可关注:gzitcast