Python学习面向对象之继承多态封装

http://www.cnblogs.com/linhaifeng/articles/7340153.html

类的继承

面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。

通过继承创建的新类称为子类派生类,被继承的类称为基类父类超类

继承语法

class 派生类名(基类名)
    ...

在python中继承中的一些特点:

  • 1、如果在子类中需要父类的构造方法就需要显示的调用父类的构造方法,或者不重写父类的构造方法。
  • 2、在调用基类的方法时,需要加上基类的类名前缀,且需要带上 self 参数变量。区别在于类中调用普通函数时并不需要带上 self 参数
  • 3、Python 总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。

如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。

语法:

派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:

class SubClassName (ParentClass1[, ParentClass2, ...]):
 1 class Parent:        # 定义父类
 2    parentAttr = 100
 3    def __init__(self):
 4       print "调用父类构造函数"
 5  
 6    def parentMethod(self):
 7       print '调用父类方法'
 8  
 9    def setAttr(self, attr):
10       Parent.parentAttr = attr
11  
12    def getAttr(self):
13       print "父类属性 :", Parent.parentAttr
14  
15 class Child(Parent): # 定义子类
16    def __init__(self):
17       print "调用子类构造方法"
18  
19    def childMethod(self):
20       print '调用子类方法'
21  
22 c = Child()          # 实例化子类
23 c.childMethod()      # 调用子类的方法
24 c.parentMethod()     # 调用父类方法
25 c.setAttr(200)       # 再次调用父类的方法 - 设置属性值
26 c.getAttr()          # 再次调用父类的方法 - 获取属性值
27 输出:
28 调用子类构造方法
29 调用子类方法
30 调用父类方法
31 父类属性 : 200
实例

接口继承:

接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口, 使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”一 这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合就好 象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。

 1 import abc                 #接口就是个函数
 2 class All_file(metaclass=abc.ABCMeta):
 3     @abc.abstractmethod
 4     def read(self):
 5         pass
 6 
 7     @abc.abstractmethod
 8     def write(self):
 9         pass
10 
11 class Disk(All_file):
12     def read(self):
13         print('disk read')
14 
15     def write(self):
16         print('disk write')
17 
18 class Cdrom(All_file):
19     def read(self):
20         print('cdrom read')
21 
22     def write(self):
23         print('cdrom write')
24 
25 
26 class Mem(All_file):
27     def read(self):
28         print('mem read')
29 
30     def write(self):
31         print('mem write')
32 #
33 m1=Mem()
34 m1.read()      --》mem read
35 m1.write()     --》mem write
接口继承

 (注:接口类没必要实例化,只是规范)

继承顺序

在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)

如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先

 1 class A(object):
 2     def test(self):
 3         print('from A')
 4 class B(A):
 5     def test(self):
 6         print('from B')
 7 class C(A):
 8     def test(self):
 9         print('from C')
10 class D(B):
11     def test(self):
12         print('from D')
13 class E(C):
14     def test(self):
15         print('from E')
16 class F(D,E):
17     # def test(self):
18     #     print('from F')
19     pass
20 f1=F()
21 f1.test()
22 print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性#(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
23 
24 #新式类继承顺序:F->D->B->E->C->A
25 #经典类继承顺序:F->D->B->A->E->C
26 #python3中统一都是新式类
27 #pyhon2中才分新式类与经典类
继承顺序

 1 class Vehicle:
 2     Country='China'
 3     def __init__(self,name,speed,load,power):
 4         self.name=name
 5         self.speed=speed
 6         self.load=load
 7         self.power=power
 8     def run(self):
 9         print('开动啦')
10         print('开动啦')
11 class Subway(Vehicle):
12         def __init__(self,name,speed,load,power,line):
13            Vehicle.__init__(self,name,speed,load,power)
14            self.line=line
15         def show_info(self):
16             print(self.name,self.speed,self.load,self.power,self.line)
17         def run(self):
18             Vehicle.run(self)
19             print('%s %s 线,开动啦' %(self.name,self.line))
20 line13=Subway('北京地铁','10km/s',1000000000,'',13)
21 line13.show_info()
22 line13.run()
23 输出:
24 北京地铁 10km/s 1000000000 电 13
25 开动啦
26 开动啦
27 北京地铁 13 线,开动啦
在子类中调用父类方法 
 1 #通过super调用父类方法好处:随意改变父类名字,底部代码不用更改;不用传self参数
 2 
 3 class Vehicle1:
 4     Country='China'
 5     def __init__(self,name,speed,load,power):
 6         self.name=name
 7         self.speed=speed
 8         self.load=load
 9         self.power=power
10     def run(self):
11         print('开动啦')
12         print('开动啦')
13 class Subway(Vehicle1):
14         def __init__(self,name,speed,load,power,line):
15            # Vehicle.__init__(self,name,speed,load,power)   #下面两句super()均可替代此句
16            super().__init__(name,speed,load,power)  
17            super(Subway,self).__init__(name,speed,load,power)
18            self.line=line
19         def show_info(self):
20             print(self.name,self.speed,self.load,self.power,self.line)
21         def run(self):
22             # Vehicle.run(self)
23             super().run()      #super()替代上句
24             print('%s %s 线,开动啦' %(self.name,self.line))
25 line13=Subway('北京地铁','10km/s',1000000000,'',13)
26 line13.show_info()
27 line13.run()
28 print(line13.__class__)   ==><class '__main__.Subway'>
super调用父类方法

 http://www.cnblogs.com/linhaifeng/articles/7340687.html

多态, 不同的 子类对象调用 相同的 父类方法,产生 不同的 执行结果,可以增加代码的外部 调用灵活度,

  • 多态以 继承 和 重写 父类方法 为前提
  • 多态是调用方法的技巧,不会影响到类的内部设计

多态就是参数虽然设置为父类的类型,但仍可以传入其子类的类型 
因为python 不能声明参数类型(动态语言),所以对多态体现的不强,而其使用的是鸭子类型(“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”)

{类的继承有两层意义: 1.改变2.扩展
多态就是类的这两层意义的一个具体的实现机制
即, 调用不同的类实例化得对象下的相同的方法,实现的过程不一一样
python中的标准类型就是多态概念的一个很好的示范}

 1 #多态指的是一类事物有多种形态,动物有多种形态:人,狗,猪
 2 import abc
 3 class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
 4     @abc.abstractmethod
 5     def talk(self):
 6         pass
 7 class People(Animal): #动物的形态之一:人
 8     def talk(self):
 9         print('say hello')
10 class Dog(Animal): #动物的形态之二:狗
11     def talk(self):
12         print('say wangwang')
13 class Pig(Animal): #动物的形态之三:猪
14     def talk(self):
15         print('say aoao')
16 #文件有多种形态:文本文件,可执行文件
17 import abc
18 class File(metaclass=abc.ABCMeta): #同一类事物:文件
19     @abc.abstractmethod
20     def click(self):
21         pass
22 class Text(File): #文件的形态之一:文本文件
23     def click(self):
24         print('open file')
25 class ExeFile(File): #文件的形态之二:可执行文件
26     def click(self):
27         print('execute file')
多态
 1 多态性是指在不考虑实例类型的情况下使用实例
 2 在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
 3 
 4 比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
 5 #为什么要用多态性(多态性的好处)
 6 其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的,这么做的好处是什么呢?
 7 1.增加了程序的灵活性
 8   以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
 9 2.增加了程序额可扩展性
10   通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用   
多态性
 1 其实大家一直在享受着多态性带来的好处,比如Python的序列类型有多种形态:字符串,列表,元组,多态性体现如下
 2 #str,list,tuple都是序列类型
 3 s=str('hello')
 4 l=list([1,2,3])
 5 t=tuple((4,5,6))
 6 
 7 #我们可以在不考虑三者类型的前提下使用s,l,t
 8 s.__len__()
 9 l.__len__()
10 t.__len__()
11 
12 len(s)
13 len(l)
14 len(t)
多态性的体现
 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     def aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(self):
13         pass
14 
15 class Water(H2O):
16     pass
17 class Ice(H2O):
18     pass
19 class Steam(H2O):
20     pass
21 
22 w1=Water('',25)
23 i1=Ice('',-20)
24 s1=Steam('蒸汽',3000)
25 
26 # w1.turn_ice()
27 # i1.turn_ice()
28 # s1.turn_ice()
29 
30 def func(obj):
31     obj.turn_ice()
32 
33 func(w1)  #---->w1.turn_ice()
34 func(i1)  #---->i1.turn_ice()
35 # def func(obj):
36 #     obj.turn_ice()
37 # func(w1)
38 # func(i1)
39 # func(s1)
示例

 封装:

封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。

注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”

真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明

(注意:对外透明的意思是外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)

{第一个层面的封装:类就是麻袋,这本身就是一种封装;第二个层面的封装:类中定义私有的,只在类的内部使用,外部无法访问(_   __通过添加单双下划线);第三个层面的封装:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用(这才是真正的封装,具体实现,会在面向对象进阶中讲)

 1 #fengzhuang.py文件下内容:
 2 class People:
 3     __star='earth111111111111'
 4     __star1='earth111111111111'
 5     __star2='earth111111111111'
 6     __star3='earth111111111111'
 7     def __init__(self,id,name,age,salary):
 8         print('----->',self.__star)
 9         self.id=id
10         self.name=name
11         self.age=age
12         self.salary=salary
13     def get_id(self):
14         print('我是私有方法啊,我找到的id是[%s]' %self.id)
15     #访问函数
16     def get_star(self):
17         print(self.__star)
18 
19 p1=People('123123123123','alex','18',100000000)
20 # print(p1.__star)
21 print(People.__dict__)
22 # print(p1.__star)
23 print(p1._People__star)
24 #
25 # p1.get_star()
26 p1.get_star()
27 
28 #test.py文件下内容:
29 from fengzhuang  import People
30 
31 p1=People('123123123123','alex','18',100000000)
32 p1.get_id()
示例
 1 #在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
 2 #其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形
 3 #类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式:
 4 
 5 class A:
 6     __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
 7     def __init__(self):
 8         self.__X=10 #变形为self._A__X
 9     def __foo(self): #变形为_A__foo
10         print('from A')
11     def bar(self):
12         self.__foo() #只有在类内部才可以通过__foo的形式访问到.
13 
14 #A._A__N是可以访问到的,
15 #这种,在外部是无法通过__x这个名字访问到。
先看如何隐藏

这种变形需要注意的问题是:

1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。

2.变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形

3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

#正常情况
>>> class A:
...     def fa(self):
...         print('from A')
...     def test(self):
...         self.fa()
... 
>>> class B(A):
...     def fa(self):
...         print('from B')
... 
>>> b=B()
>>> b.test()
from B
 

#把fa定义成私有的,即__fa
>>> class A:
...     def __fa(self): #在定义时就变形为_A__fa
...         print('from A')
...     def test(self):
...         self.__fa() #只会与自己所在的类为准,即调用_A__fa
... 
>>> class B(A):
...     def __fa(self):
...         print('from B')
... 
>>> b=B()
>>> b.test()
from A

(注:python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的

其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点

python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__,详见面向对象进阶

封装与扩展性

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

 1 #类的设计者
 2 class Room:
 3     def __init__(self,name,owner,width,length,high):
 4         self.name=name
 5         self.owner=owner
 6         self.__width=width
 7         self.__length=length
 8         self.__high=high
 9     def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
10         return self.__width * self.__length
11 
12 
13 #使用者
14 >>> r1=Room('卧室','egon',20,20,20)
15 >>> r1.tell_area() #使用者调用接口tell_area
16 
17 
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 * self.__high
28 
29 
30 #对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
31 >>> r1.tell_area()
示例

 

1   从编程进化论我们得知,面向对象是一种更高等级的结构化编程方式,它的好处就两点
2   1:通过封装明确了内外,你作为类的缔造者,你是上帝,上帝造物的逻辑你无需知道(你知道了你tm也成上帝了),上帝想让你知道的你才能知道,这样就明确了划分了等级,物就是调用者,上帝就是物的创造者2:通过继承+多态在语言层面支持了归一化设计
3   注意:不用面向对象语言(即不用class),一一样可以做归一化(如老掉牙的泛文件概念、游戏行业的一切皆精灵),一样可以封装(通过定义模块和接口》,只是用面向对象语言可以直接用语言元素显式声明这些而已;而用了面向对象语言,满篇都是class,并不等于就有了归一化的设计。甚至,因为被这些花哨的东西迷惑,反而更加不知道什么才是设计。
面向对象的优点

 

posted @ 2018-08-03 15:53  一只小妖  阅读(259)  评论(0)    收藏  举报