2015/9/22 Python基础(18):组合、派生和继承
一个类被定义后,目标就是把它当成一个模块来使用,并把这些对象嵌入到你的代码中去,同其他数据类型及逻辑执行流混合使用。
有两种方法可以在你的代码中利用类。
第一种是组合,就是让不同的类混合并加入到其他类中,来增强功能和代码重用性。你可以在一个大点的类中创建你自己的类的实例,实现一些其他属性和方法来增强原来的类对象。
另一种是派生,通过子类从基类继承核心属性,不断地派生扩展功能实现。
组合
举例来说,我们想对之前做过的地址本类作加强性设计。如果在设计的过程中,为names、addresses等创建了单独的类,那么最后我们可能想把这些工作集成到AddrBookEntry类中去,而不是重新设计每一个需要的类。
下面是一个例子
class NewAddrBookEntry(object):
'new address book entry class'
def __init__ (self, nm, ph):
self.name = Name(nm)
self.phone = Phone(ph)
print 'Created instance for:', self.name
NewAddrBookEntry 类由它自身和其他类组合而成。这就在一个类和其他组成类之间定义了一个“has-a”关系。比如说,我们的NewAddrBookEntry类“有一个”Name类实例和一个Phone实例。
创建复合对象就可以实现这些附加的功能,并且很有意义,因为这些类都不相同。每一个类管理他们自己的名字空间和行为。不过当对象之间有更接近的关系是,派生的概念可能更有意义。
这里没有写清楚如何使用组合这个概念,我就上面的部分写一个例子:
>>> class Name(object):
def __init__(self, nm):
self.name = nm >>> class Phone(object):
def __init__(self, ph):
self.phone = ph
>>> class NewAddrBookEntry(object):
def __init__(self, nm, ph):
self.name = Name(nm)
self.phone = Phone(ph)
print 'Created instance for:', self.name
print 'Created instance for:', self.name.name
>>> foo = NewAddrBookEntry('Paul', 123456)
Created instance for: <__main__.Name object at 0x02B75FD0>
Created instance for: Paul
子类和派生
当类和类之间有显著的不同,并且(较小的类)是较大的类所需要的组件时,组合表现的不错,但如果要设计“相同的类但由一些不同的功能”时,派生就是更合理的选择了。
OOP的更强大的功能之一是能够使用一个已经定义好的类,扩展它或者对其进行修改,而不会影响系统中使用现存类的其他代码片段。
OOD允许类特征在子孙类或子类中进行继承。这些子类从基类(或称祖先类、超类)继承他们的核心属性。而且,这些派生可能会扩展到多代。在一个层次的派生关系中的相关类是父类和子类的关系,同一个父类派生出来的这些类是同胞关系。父类和所有高层类都被认为是祖先。
创建子类
创建子类的语法看起来与普通类没有区别,一个类名,后跟一个或多个需要从其中派生的父类:
class SubClassName (ParentClass1[, ParentClass2...):
'optional class documentation string'
class_suite
如果你的类没有从任何祖先类派生,可以使用object作为父类名字。这也就是新式类的创建。
下面是子类派生的一个例子:
>>> class Parent(object):
def parentMethod(self):
print 'calling parent method' >>> class Child(Parent):
def childMethod(self):
print 'calling child method' >>> p = Parent()
>>> p.parentMethod()
calling parent method
>>> c = Child()
>>> c.childMethod()
calling child method
>>> c.parentMethod()
calling parent method
继承
继承描述了基类的属性如何“遗传”给派生类。一个子类可以继承它的积累的任何属性,不管是数据属性还是方法。
举例如下,P是一个没有属性的简单类,C从P继承而来,也没有属性:
>>> class P(object):
pass >>> class C(P):
pass >>> c = C()
>>> c.__class__
<class '__main__.C'>
>>> P.add = 123
>>> c.add
123
我们给父类添加数据属性时,子类也继承到了这个属性。
假如P添加一些属性:
>>> class P:
'P class'
def __init__(self):
print 'created an instance of',\
self.__class__.__name__ >>> class C(P):
pass >>> p = P()
created an instance of P
>>> a = P()
created an instance of P
>>> p.__class__
<class __main__.P at 0x02A10490>
>>> P.__bases__
()
>>> P.__doc__
'P class'
>>> c = C()
created an instance of C
>>> c.__class__
<class __main__.C at 0x02BA8880>
>>> C.__bases__
(<class __main__.P at 0x02A10490>,)
>>> C.__doc__
>>>
C没有声明__init__()方法,然而在类C的实例c被创建时,还是会有输出星系。原因在于C集成了P的__init__()。__bases__元组列出父类P。需要的是文档字符串对类,函数/方法,还有模块来说都是唯一的,所以特殊属性__doc__不会从基类中继承过来。
__bases__类属性
__bases__类属性是一个包含其父类的集合的元组。这里的父类是相对所有基类而言的。那些没有父类的类,__bases__属性为空。
>>> class A(object):
pass >>> class B(A):
pass >>> class C(B):
pass >>> class D(B, A):
pass >>> A.__bases__
(<type 'object'>,)
>>> C.__bases__
(<class '__main__.B'>,)
>>> D.__bases__
(<class '__main__.B'>, <class '__main__.A'>)
对于D,继承方向,我们将在之后详细讲解。
通过继承覆盖方法
我们在P中再写一个函数,然后在其子类中对它进行覆盖。
>>> class P(object):
def foo(self):
print 'call P-foo()' >>> p = P()
>>> p.foo()
call P-foo()
>>> class C(P):
def foo(self):
print 'call C-foo()' >>> c = C()
>>> c.foo()
call C-foo()
尽管C继承了P的方法,但因为C定义了自己的foo()方法,所以P中的foo()方法被覆盖。
那我们怎么去调用那个别覆盖的基类的方法呢?
有这样几种方法:
>>> P.foo(c)
call P-foo()
这里我们没有用P的实例调用方法,而是用了P的子类C的实例c来调用。一般我们不会用这种方法调用。一般如下:
>>> class C(P):
def foo(self):
P.foo(self)
print 'call C-foo()'
>>> c = C()
>>> c.foo()
call P-foo()
call C-foo()
这种方法需要我们知道C的父类,还有一个更好的方法是用super()内建函数
>>> class C(P):
def foo(self):
super(C, self).foo()
print 'call C-foo()' >>> c = C()
>>> c.foo()
核心笔记:重写__init__不会自动调用基类的__init__
类似于上面的覆盖非特殊方法,当从一个带构造器__init__()的类派生,如果你不去覆盖__init__(),它将会被继承并自动调用。但如果你在子类中覆盖了__init__(),子类被实例化时,基类的__init__()就不会被自动调用。如果还想调用基类的__init__(),需要向上边我们说的那样,明确指出,使用一个子类的实例去调用基类(未绑定)方法:
class C(P):
def __init__(self):
P.__init__(self)
C__init__suite
这是一种很普遍的调用做法,这个规则的意义是,你希望被继承的类的对象在子类构造器运行前能够很好地被初始化或做好准备工作,因为它可能需要或设置继承属性。
当然我们也可以用super()内建函数替代P的使用,这样可以不提供基类的名字。
从标准类型派生
经典类中,一个最大的问题是,不能对标准类型进行子类化。后来随着类型和类的统一和新式类的引入,这一点已经被修正。
1.不可变类型的例子
金融应用中处理一个浮点数的子类,每次你得到一个货币值,你都需要四舍五入,变为带两位小数位的数值。你的类可以这样写:
class RoundFloat(float):
def __new__(cls, val):
return float.__new__(cls, round(val, 2))
我们了__new__()特殊方法来定制我们的对象,使之和标准Python浮点数有一些区别:我们使用了round()内建函数对元浮点数进行舍入操作,然后实例化我们的float, RoundFloat。我们是通过调用父类的构造器来创建真实的对象的,float.__new__()。注意所有的__new__()方法都是类方法,我们要显式传入类作为第一个参数。
以下是一些样例输出
>>> RoundFloat(1.5987)
1.6
>>> RoundFloat(0.4567)
0.46
>>> RoundFloat(-1.2334)
-1.23
2.可变类型的例子
子类化一个可变类型于此相似,你可能不需要使用__new__()(甚至不用__init__()),因为通常设置不多。一般情况下,所继承到的类型默认行为就是你想要的。下例是一个字典类型:
class SortedKeyDict(dict):
def keys(self):
return sorted(super(SortedKeyDict, self).keys())
下面是使用新字典的例子:
>>> d = SortedKeyDict({'Anna':68, 'John':86,'Frank':78,'Cindy':88})
>>> print 'By iterator:',[key for key in d]
By iterator: ['Frank', 'John', 'Anna', 'Cindy']
>>> print 'By keys():',d.keys()
By keys(): ['Anna', 'Cindy', 'Frank', 'John']
当然,这种类方法调用有点多此一举,不如这样:
def keys(self):
return sorted(self.keys())
多重继承
Python也允许子类继承多个基类。这种特性就是通常所说的多重继承。使用多重继承时,有两个不同的方面要记住,一是要找到合适的属性,二是重写方法时,如何调用父类方法让它们发挥作用,同时子类处理好自己的义务。
1.方法解释顺序(MRO)
在Python2.2以前的版本中,算法非常简单:深度优先,从左至右进行搜索,取得在子类中使用属性。其他Python算法只是覆盖被找到的名字,多重继承则取找到的第一个名字。
而现在的Python应用了C3算法
C3最早被提出是用于Lisp的,应用在Python中时为了解决深度优先搜索算法不满足本地优先级,和单调性问题。
本地优先级:指声明时父类的顺序,C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后查找B类。
单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序。
在Python官网中MRO的作者举了例
F=type('Food', (), {remember2buy:'spam'})
E=type('Eggs', (F,), {remember2buy:'eggs'})
G=type('GoodFood', (F,E), {})
根据本地优先级在调用G类对象属性时应该优先查找F类,而在Python2.3之前的算法给出的顺序是G E F O,而在心得C3算法中通过阻止类层次不清晰的声明来解决这一问题,以上声明在C3算法中就是非法的。
C3算法
判断mro要先确定一个线性序列,然后查找路径由序列中类的顺序决定,所以C3算法就是生成一个线性序列。
如果继承至一个基类:
class B(A)
这时B的mro序列为[B,A]
如果继承至多个基类
class B(A1,A2,A3 ...)
这时B的mro序列 mro(B) = [B] + merge(mro(A1), mro(A2), mro(A3) ..., [A1,A2,A3])
merge操作就是C3算法的核心。
遍历执行merge操作的序列,如果一个序列的第一个元素,在其他序列中也是第一个元素,或不在其他序列出现,则从所有执行merge操作序列中删除这个元素,合并到当前的mro中。
merge操作后的序列,继续执行merge操作,直到merge操作的序列为空。
如果merge操作的序列无法为空,则说明不合法。
例子:
class A(O):pass
class B(O):pass
class C(O):pass
class E(A,B):pass
class F(B,C):pass
class G(E,F):pass
A、B、C都继承至一个基类,所以mro序列依次为[A,O]、[B,O]、[C,O]
mro(E) = [E] + merge(mro(A), mro(B), [A,B])
= [E] + merge([A,O], [B,O], [A,B])
执行merge操作的序列为[A,O]、[B,O]、[A,B]
A是序列[A,O]中的第一个元素,在序列[B,O]中不出现,在序列[A,B]中也是第一个元素,所以从执行merge操作的序列([A,O]、[B,O]、[A,B])中删除A,合并到当前mro,[E]中。
mro(E) = [E,A] + merge([O], [B,O], [B])
再执行merge操作,O是序列[O]中的第一个元素,但O在序列[B,O]中出现并且不是其中第一个元素。继续查看[B,O]的第一个元素B,B满足条件,所以从执行merge操作的序列中删除B,合并到[E, A]中。
mro(E) = [E,A,B] + merge([O], [O])
= [E,A,B,O]
2.简单属性查找示例
下面这个例子将对两种类的方案不同处做一展示。脚本由一组父类,一组子类,还有一个子孙类组成。
首先是经典类:
>>> class P1: #(object):
def foo(self):
print 'called P1-foo()' >>> class P2: #(object):
def foo(self):
print 'called P2-foo()' >>> class P2(object):
def foo(self):
print 'called P2-foo()'
def bar(self):
print 'called P2-bar()' >>> class C1(P1,P2):
pass >>> class C2(P1, P2):
def bar(self):
print 'called C2-bar()' >>> class GC(C1, C2):
pass
在经典类中,我们如下执行:
>>> gc = GC()
>>> gc.foo() # GC => C1 =>P1
called P1-foo()
>>> gc.bar() # GC => C1 => P1 => P2
called P2-bar()
在这种搜索方式下,foo()是很容易被理解的,而bar()的寻找则是通过GC,C1,P1后,找不到,就到P2中找到,C2根本不会被检索。
新式类,也就是在声明时加上(object)
结果如下:
>>> gc = GC()
>>> gc.foo() # GC => C1 => C2 => P1
called P1-foo()
>>> gc.bar() # GC => C1 => C2
called C2-bar()
新式类采用了一种广度优先的方式,查找顺序如图所示。同时,新式类有一个__mro__属性,可以告诉你查找顺序。
>>> GC.__mro__
(<class '__main__.GC'>, <class '__main__.C1'>, <class '__main__.C2'>, <class '__main__.P1'>, <class '__main__.P2'>, <type 'object'>)
为什么新式类的MRO方式和经典类出现了不同呢?这是因为菱形效应,如果继续沿袭经典类的深度优先搜索,可能会导致不能有效继承。
关于多重继承的顺序,我暂时也不是很清楚,等之后搞明白了这件事再写吧。
类、实例和其他对象的内建函数
issubclass()
这是布尔函数,判断一个类是不是另一个类的子类或者子孙类,语法如下:
issubclass(sub, sup)
True则是sub是sup的不严格子类,False则代表sub不是sup的子类。
isinstance()
这个布尔函数判定一个对象是否是另一个给定类的实例。语法如下:
isinstance(obj1, obj2)
isinstance()在obj1是obj2的一个实例或者是obj2子类的一个实例时,返回一个True。这里第二个参数必须是类,不然会得到TypeError.如果第二个参数是类型对象,这是允许的,我们也常常这样用它:
>>> isinstance(3, int)
True
>>> isinstance(2.2, int)
False
hasattr()、getattr()、setattr()、delattr()
这些函数可以在各种对象下工作,不只是类和实例。
hasattr()函数是布尔型的,它的目的是为了决定一个对象是否有一个特定的属性,一般用于访问某些属性前做一下检查。getattr()和setattr()函数相应地取得和赋值给对象的属性,getattr()在你试图读取一个不存在的属性时,会引发AttributeError异常。setattr()将要加入一个新的属性,要么取代一个已存在的属性。而delattr()函数会从一个对象中删除属性。
dir()和super()
这两个内建函数之前都已经提到,在此不多加说明。
vars()
该内建函数与dir()相似,只是给定的对象参数必须有一个__dict__属性。vars()返回一个字典,它包含了对象存储于其__dict__中的属性(键)和值。
2015/9/22 Python基础(18):组合、派生和继承的更多相关文章
- 十八. Python基础(18)常用模块
十八. Python基础(18)常用模块 1 ● 常用模块及其用途 collections模块: 一些扩展的数据类型→Counter, deque, defaultdict, namedtuple, ...
- python基础——18(面向对象2+异常处理)
一.组合 自定义类的对象作为另一个类的属性. class Teacher: def __init__(self,name,age): self.name = name self.age = age t ...
- python基础语法15 面向对象2 继承,多态,继承json模块中JSONEncoder,并派生出新的功能
继承 1.什么是继承? 继承是一种新建类的方式,新建的类称之为子类或派生类,继承的父类称之为基类或超类. - 在Python中,一个子类可以继承多个父类.(面试可能会问) - 在其它语言中,一个子类只 ...
- Python面向对象(组合、菱形继承、多态)
今日内容: 1.组合 2.菱形继承 3.多态与多态性 昨天内容重点回顾: 1)调用也叫实例化:发生了2件事 1.创造空对象 2.触发对象下的__init__方法,然后将p连同参数一同传给init ...
- python基础之组合继承多态
组合 1.什么是组合 组合就是一个类的对象具备一个指向另外一个类的对象的属性 2.为何用组合 组合可以减少代码冗余 3.如何使用 class People: def __init__(self,nam ...
- 2015/9/29 Python基础(20):类的授权
类的授权 1.包装包装在Python编程世界中时经常会被提到的一个术语.它是一个通用的名字,意思是对一个已存在的对象进行包装,不管它是数据类型,还是一段代码,可以是对一个已存在的对象,增加新的,删除不 ...
- 2015/9/28 Python基础(19):类的定制和私有性
用特殊方法定制类前面我们讲了方法的两个重要方面:首先,方法必须在调用前被绑定(到它们相应类的某个实例中):其次,有两个特殊方法可以分别作为构造器和解构器的功能,分别名为__init__()和__del ...
- 【Python基础】lpthw - Exercise 44 继承与组合
一.继承 原则:大部分使用继承的场合都可以用组合取代或者简化,而多重继承则需要不惜一切的避免. 1. 什么是继承 继承:用于指明一个类的大部分或者全部功能都是从一个父类获得的.此时,父类的实例的所有动 ...
- 2015/10/9 Python基础(21):可调用和可执行对象
在Python中有多种运行外部程序的方法,比如,运行操作系统命令或另外的Python脚本,或执行一个磁盘上的文件,或通过网络来运行文件.这完全取决于想要干什么.特定的环境包括: 在当前脚本继续运行 创 ...
随机推荐
- linux awk,sort,uniq,wc,cut命令详解
1.awk awk是行处理器: 相比较屏幕处理的优点,在处理庞大文件时不会出现内存溢出或是处理缓慢的问题,通常用来格式化文本信息 $ 表示当前行 $ 表示第一列 NF 表示一共有多少列 $NF 表示最 ...
- P4语法(5) Package
Package 对于package这个概念,类似于将一个框架中各组成部件以一个规律进行打包,以正常运转. 基于一个架构去编写一个新的pipeline的时候,需要先了解初始化的时候需要提供那些东西,pa ...
- OOP 2.2 构造函数
1.概念 成员函数的一种 名字与类名相同,可以有参数,没有返回值(void也不行) 作用:对对象进行初始化,如给成员函数赋初始值 如果定义时没有构造函数,则编译器生成一个默认无参数的构造函数 默认构造 ...
- 关于解决乱码问题的一点探索之二(涉及Unicode(utf-16)和GBK)
在上篇日志中(链接),我们讨论了utf-8编码和GBK编码之间转化的乱码问题,这一篇我们讨论Unicode(utf-16编码方式)与GBK编码之间转换的乱码问题. 在Windows系统 ...
- Strust2: 工作流程
以下为Struts2的体系结构图: Struts2框架处理用户请求,大体分为以下几个过程: (1)用户发出一个HttpServletRequest请求 (2)请求经过一系列过滤器,最后达到Filter ...
- Scrum Meeting Beta - 1
Scrum Meeting Beta - 1 NewTeam 2017/11/28 地点:主南201 任务反馈 团队成员 完成任务 计划任务 安万贺 详细讨论Beta阶段的任务和具体分工 了解缓存的相 ...
- IBM存储降级告警等一些服务器问题/dd/ethtool
1.IBM存储降级告警 一般两种情况 a.端口降级 例如模块16G->8G(IBM储存端口自适应) b.系统在作raid后,有硬盘损坏,降级 黄灯告警 2. dimm error dimm内存插 ...
- BGP与BGP机房 国内网络运营商的主流网关解决方案
边界网关协议(BGP)是运行于 TCP 上的一种自治系统的路由协议. BGP 是唯一一个用来处理像因特网大小的网络的协议,也是唯一能够妥善处理好不相关路由域间的多路连接的协议. BGP 构建在 EGP ...
- [您有新的未分配科技点]数位DP:从板子到基础(例题 bzoj1026 windy数 bzoj3131 淘金)
只会统计数位个数或者某种”符合简单规律”的数并不够……我们需要更多的套路和应用 数位dp中常用的思想是“分类讨论”思想.下面我们就看一道典型的分类讨论例题 1026: [SCOI2009]windy数 ...
- DjangoORM使用mysql注意
注意事项1:需要在project下的setting里面做设置.让Django生成MySQL类型的数据库. 注意事项2:在Django内部,连MySQL的时候,需要添加下面2句代码: 4.******* ...