?? 引言
?? 面向对象编程
?? 类
?? 实例
?? 绑定与方法调用
?? 子类,派生和继承
?? 内建函数
?? 定制类
?? 私有性
?? 授权与包装
?? 新式类的高级特性
?? 相关模块
13.1 介绍
在Python 中,面向对象编程主要有两个主题,就是类和类实例
类与实例
如何创建一个类:
class MyNewObjectType(bases):
‘define MyNewObjectType class‘
class_suite #类体
关键字是class,紧接着是一个类名。随后是定义类的类体代码。这里通常由各种各样的定义和声明组成。
所有新式类必须继承至少一个父类,参数bases可以是一个(单继承)或多个(多重继承)用于继承的父类。
object 是“所有类之母”。
如果你的类没有继承任何其他父类,object 将作为默认的父类。它位于所有类继承结构的最上层。
如果你没有指定一个父类,或者如果所子类化的基本类没有父类,你这样就是创建了一个经典类。
新式类和经典类
新式类必须继承至少一个父类,可以继承 一个(单继承)或多个(多重继承)父类。
class MyNewObjectType(bases):
‘define MyNewObjectType class‘
class_suite #类体
经典类没有指定一个父类,或者如果所子类化的基本类没有父类。
class MyNewObjectType:
‘define MyNewObjectType classic class‘
class_suite
实例化
创建一个实例的过程称作实例化(没有使用new 关键字)
myClass = new MyClass()
类名使用我们所熟悉的函数操作符(()),以“函数调用”的形式出现。
名称空间容器
只要你需要,类可以很简单,也可以很复杂。最简单的情况,类仅用作名称空间(namespaces)。
这意味着你把数据保存在变量中,对他们按名称空间进行分组,使得他们处于同样的关系空间中。
所谓的关系是使用标准Python 句点属性标识。
>>> class MyClass:
... pass
...
>>> myClass = MyClass()
>>> myClass.x = 1
>>> myClass.y = 2
>>> y = myClass.x + myClass.y
>>> print(y)
3
>>> print(myClass.x,myClass.y)
1 2
>>>
方法
定义类(和方法)
>>> class MyFoo(object):
... ‘with method named bar‘
... def bar(self): #self
... print(‘you invoked method bar()‘)
...
>>> myFoo = MyFoo()
>>> myFoo.bar()
you invoked method bar()
>>>
方法__init__()
__init__()类似于类构造器,它在创建一个新的对象时被调用。
在Python 中,__init__() 实际上不是一个构造器。
创建一个类(类定义)
>>> class Foo(object):
... def __init__(self,x,y):
... self.x = x
... self.y = y
... print(‘you invoked __init__(x,y)‘)
... def Show(self):
... print(‘x + y = %s‘ %(self.x + self.y))
...
>>>
创建实例(实例化)
>>> foo = Foo(1,1)
you invoked __init__(x,y)
>>>
如果不存在默认的参数,那么传给__init__()的两个参数在实例化时是必须的。
访问实例属性
>>> foo.x + foo.y
2
>>>
方法调用(通过实例)
>>> foo.Show()
x + y = 2
>>>
创建子类
>>> class Foo(object):
... def __init__(self,x):
... self.x = x
... def show(self):
... print(‘Foo.x = %s‘ %self.x)
...
>>> class Bar(Foo):
... def __init__(self,x,y):
... Foo.__init__(self,x) #base.__init__
... self.y = y
... def showbar(self):
... print(‘Bar.x = %s, Bar.y = %s‘ %(self.x,self.y))
...
>>> bar = Bar(1,2)
>>> bar.showbar()
Bar.x = 1, Bar.y = 2
>>> bar.show()
Foo.x = 1
>>>
核心笔记:命名类、属性和方法
类名通常由大写字母打头。这是标准惯例,可以帮助你识别类,特别是在实例化过程中(有时看
起来像函数调用)。还有,数据属性(译者注:变量或常量)听起来应当是数据值的名字,方法名应
当指出对应对象或值的行为。另一种表达方式是:数据值应该使用名词作为名字,方法使用谓词(动
词加对象)。数据项是操作的对象、方法应当表明程序员想要在对象进行什么操作。在上面我们定义
的类中,遵循了这样的方针,数据值像“name”,“phone”和“email”,行为如“updatePhone”,
“updateEmail”。这就是常说的“混合记法(mixedCase)”或“骆驼记法(camelCase)”。Python 规
范推荐使用骆驼记法的下划线方式,比如,“update_phone”,“update_email”。类也要细致命名,
像“AddrBookEntry”,“RepairShop”等等就是很好的名字。
13.2 面向对象编程
编程的发展已经从简单控制流中按步的指令序列进入到更有组织的方式中,依靠代码块可以形
成命名子程序和完成既定的功能。结构化的或过程性编程可以让我们把程序组织成逻辑块,以便重
复或重用。创建程序的过程变得更具逻辑性;选出的行为要符合规范,才可以约束创建的数据。迪
特尔父子(这里指DEITEL 系列书籍作者Harvey M.Deitel 和Paul James Deitel 父子,译者注)认
为结构化编程是“面向行为”的,因为事实上,即使没有任何行为的数据也必须“规定”逻辑性。
然而,如果我们能对数据加上动作呢?如果我们所创建和编写的数据片段,是真实生活中实体
的模型,内嵌数据体和动作呢?如果我们能通过一系列已定义的接口(又称存取函数集合)访问数据
属性,像自动取款机卡或能访问你的银行帐号的个人支票,我们就有了一个“对象”系统,从大的
方面来看,每一个对象既可以与自身进行交互,也可以与其它对象进行交互。
面向对象编程踩上了进化的步伐,增强了结构化编程,实现了数据与动作的融合:数据层和逻
辑层现在由一个可用以创建这些对象的简单抽象层来描述。现实世界中的问题和实体完全暴露了本
质,从中提供的一种抽象,可以用来进行相似编码,或者编入能与系统中对象进行交互的对象中。
类提供了这样一些对象的定义,实例即是这些定义的实现。二者对面向对象设计(object-oriented
design,OOD)来说都是重要的,OOD 仅意味来创建你采用面向对象方式架构来创建系统。
面向对象设计与面向对象编程的关系
面向对象设计(OOD)不会特别要求面向对象编程(OOP)语言。
OOD 可以由纯结构化语言来实现,比如C,但如果想要构造具备对象性质和特点的数据类型,就需要在程序上作更多的努力。
一门面向对象的语言不一定会强制你写OO 方面的程序。
*常用术语
抽象/实现
抽象/实现 抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 描绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。 对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装/接口 封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端 直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python 中,所有的类属 性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防 措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装 的数据属性。
合成
合成 合成扩充了对类的描述,使得多个不同的类合成为一个大的类,来解决现实问题。合成描述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。比如,RepairShop“有一个”技工(应该至少有一个 吧),还“有一个”顾客(至少一个)。
派生/继承/继承结构
派生/继承/继承结构 派生描述了子类的创建,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它 的自定义操作,都不会修改原类的定义。继承描述了子类属性从祖先类继承这样一种方式。从前面 的例子中,技工可能比顾客多个汽车技能属性,但单独的来看,每个都“是一个”人,所以,不管 对谁而言调用talk()都是合法得,因为它是人的所有实例共有的。继承结构表示多“代”派生,可 以描述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
泛化/特化 泛化表示所有子类与其父类及祖先类有一样的特点,所以子类可以认为同祖先类是“是一个” 的关系,因为一个派生对象(实例)是祖先类的一个“例子”。比如,技工“是一个”人,车“是一 个”交通工具,等等。在上面我们间接提到的族谱图中,我们可以从子类到祖先类画一条线,表示 “是一个”的关系。特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态
多态 多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的 类。多态表明了动态(又名,运行时)绑定的存在,允计重载及运行时类型确定和验证。
自省/反射
自省/反射 自省表示给予你,程序员,某种能力来进行像“手工类型检查”的工作,它也被称为反射。这 个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么 能力,这样的功能不是很好吗?这是一项强大的特性,在本章中,你会时常遇到。如果Python 不 支持某种形式的自省功能,dir()和type()内建函数,将很难正常工作。请密切关注这些调用,还 有那些特殊属性,像__dict__,__name__及__doc__。可能你对其中一些已经很熟悉了!
13.3 类
类是一种数据结构,是现实世界的抽象的实体以编程形式出现。
实例是把数据值和行为特性融合在一起的这些对象的具体化。
创建类
Python 类使用class 关键字来创建。
class ClassName(bases):
‘class documentation string‘ #‘类文档字符串‘
class_suite #类体
基类是一个或多个用于继承的父类的集合;
类体由所有声明语句,类成员定义,数据属性和函数组成。
类通常在一个模块的顶层进行定义,以便类实例能够在类所定义的源代码文件中的任何地方被创建。
声明与定义
对于Python 函数来说,声明与定义类没什么区别,因为他们是同时进行的,
定义(类体)紧跟在声明(含class 关键字的头行[header line])和可选(但总是推荐使用)的文档字符串后面。
Python 并不支持纯虚函数(像C++)或 者抽象方法(如在JAVA 中),这些都强制程序员在子类中定义方法。
13.4 类属性
属性就是属于另一个对象的数据或者函数元素,可以通过我们熟悉的句点属性标识法来访问。
类的数据属性
数据属性仅仅是所定义的类的变量,即静态变量,或者是静态数据,表示这些数据是与它们所 属的类对象绑定的,不依赖于任何类实例。
静态成员通常仅用来跟踪与类相关的值,大多数情况下,你会考虑用实例属性,而不是类属性。
>>> class Foo(object):
... bar = 100
...
>>> Foo.bar
100
>>> Foo.bar += 100
>>> Foo.bar
200
>>> foo = Foo()
>>> foo.bar
200
>>> foo.bar+=100
>>> Foo.bar
200
>>> foo.bar
300
>>>
类的函数属性
为与OOP 惯例保持一致,Python 严格要求,没有实例,方法是不能被调用的,即Python所描述的绑定概念(binding),在此,方法必须绑定(到一个实例)才能直接被调用。
非绑定的方法 可能可以被调用,但实例对象一定要明确给出,才能确保调用成功。
不管是否绑定,方法都是它所在的类的固有属性,即使它们几乎总是通过实例来调用的。
>>> class Foo(object):
... def bar(self):
... pass
...
>>> Foo.bar
<unbound method Foo.bar>
>>> foo = Foo()
>>> foo.bar
<bound method Foo.bar of <__main__.Foo object at 0x000000000217A2B0>>
>>> Foo.bar()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method bar() must be called with Foo instance as first argument (got nothing instead)
>>> foo.bar()
>>>
决定类的属性
dir()内建函数
类的字典属性__dict__
特殊的类属性
13.5 实例
初始化:通过调用类对象来创建实例
实例化的实现,可以使 用函数操作符
>>> class MyClass(object): # define class 定义类
... pass
>>> mc = MyClass() # instantiate class 初始化类
__init__() "构造器"方法
__init__()是在解释器为你创建一个实例后调用的第一个方法
__init__() "构造器"方法 当类被调用,实例化的第一步是创建实例对象。一旦对象创建了,Python 检查是否实现了 __init__()方法。默认情况下,如果没有定义(或覆盖)特殊方法__init__(),对实例不会施加任 何特别的操作.任何所需的特定操作,都需要程序员实现__init__(),覆盖它的默认行为。如果 __init__()没有实现,则返回它的对象,实例化过程完毕。 然而,如果__init__()已经被实现,那么它将被调用,实例对象作为第一个参数(self)被传递 进去,像标准方法调用一样。调用类时,传进的任何参数都交给了__init__()。实际中,你可以想 像成这样:把创建实例的调用当成是对构造器的调用。 总之,你没有通过调用new 来创建实例,你也没有定义一个构造器。是Python 为你创建了 对象; __init__(),是在解释器为你创建一个实例后调用的第一个方法,在你开始使用它之前, 这一步可以让你做些准备工作。 __init__()是很多为类定义的特殊方法之一。其中一些特殊方法是预定义的,缺省情况下,不 进行任何操作,比如__init__(),要定制,就必须对它进行重载,还有些方法,可能要按需要去实 现。本章中,我们会讲到很多这样的特殊方法。你将会经常看到__init__()的使用,在此,就不举 例说明了。
__new__() “构造器”方法
__new__()必须返回一个合法的实例,这样解释器在调用__init__()时,就可以把这个实例作为self 传给它。
__new__()和__init__()在类创建时,都传入了(相同)参数。
__new__() “构造器”方法 与__init__()相比,__new__()方法更像一个真正的构造器。类型和类在版本2.2 就统一了, Python 用户可以对内建类型进行派生,因此,需要一种途径来实例化不可变对象,比如,派生字符 串,数字,等等。 在这种情况下,解释器则调用类的__new__()方法,一个静态方法,并且传入的参数是在类实例 化操作时生成的。__new__()会调用父类的__new__()来创建对象(向上代理)。 为何我们认为__new__()比__init__()更像构造器呢?这是因为__new__()必须返回一个合法 的实例,这样解释器在调用__init__()时,就可以把这个实例作为self 传给它。调用父类的__new__() 来创建对象,正像其它语言中使用new 关键字一样。 __new__()和__init__()在类创建时,都传入了(相同)参数。
__del__() "解构器"方法
由于Python 具有垃圾对象回收机制(靠引用计数),__del__() 函数要直到该实例对象所有的引用都被清除掉后才会执行。
Python 中的解构器是在实例释放前提供特殊处理功能的方法,它们通常没有被实现,因为实例很少被显式释放。
解构器只能被调用一次,一旦引用计数为0,则对象就被清除了。因为系统中任何对象都只被分配及解构一次。
总结:
?? 不要忘记首先调用父类的__del__()。
?? 调用 del x 不表示调用了x.__del__() -----前面也看到,它仅仅是减少x 的引用计数。
?? 如果你有一个循环引用或其它的原因,让一个实例的引用逗留不去,该对象的__del__()可
能永远不会被执行。
?? __del__()未捕获的异常会被忽略掉(因为一些在__del__()用到的变量或许已经被删除了)。
不要在__del__()中干与实例没任何关系的事情。
?? 除非你知道你正在干什么,否则不要去实现__del__()。
?? 如果你定义了__del__,并且实例是某个循环的一部分,垃圾回收器将不会终止这个循环—
—你需要自已显式调用del。
跟踪实例 Python 没有提供任何内部机制来跟踪一个类有多少个实例被创建了,或者记录这些实例是些什 么东西。如果需要这些功能,你可以显式加入一些代码到类定义或者__init__()和__del__()中去。 最好的方式是使用一个静态成员来记录实例的个数。靠保存它们的引用来跟踪实例对象是很危险的, 因为你必须合理管理这些引用,不然,你的引用可能没办法释放(因为还有其它的引用)!
13.6 实例属性
实例仅拥有数据属性(方法严格来说是类属性),后者只是与某个类的实例相关联的数据值,并且可以通过句点属性标识法来访问。
这些值独立于其它实例或类。当一个实例被释放后,它的属性同时也被清除了。
“实例化”实例属性(或创建一个更好的构造器)
设置实例的属性可以在实例创建后任意时间进行,也可以在能够访问实例的代码中进行。
构造器__init()__是设置这些属性的关键点之一。
构造器是最早可以设置实例属性的地方,因为__init__()是实例创建后第一个被调用的方法。
__init__()应当返回None
核心笔记:实例属性
能够在“运行时”创建实例属性,是Python 类的优秀特性之一,从C++或Java 转过来的人会被
小小的震惊一下,因为C++或Java 中所有属性在使用前都必须明确定义/声明。
Python 不仅是动态类型,而且在运行时,允许这些对象属性的动态创建。这种特性让人爱不释
手。当然,我们必须提醒读者,创建这样的属性时,必须谨慎。
一个缺陷是,属性在条件语句中创建,如果该条件语句块并未被执行,属性也就不存在,而你
在后面的代码中试着去访问这些属性,就会有错误发生。故事的精髓是告诉我们,Python 让你体验
从未用过的特性,但如果你使用它了,你还是要小心为好。
查看实例属性
内建函数dir()可以显示类属性,同样还可以打印所有实例属性。
实例也有一个__dict__特殊属性(可以调用vars()并传入一个实例来获取),它是实例属性构成的一个字典。
特殊的实例属性
实例仅有两个特殊属性。
对于任意对象I:
I.__class__ 实例化I 的类
I.__dict__ I 的属性
__dict__属性由一个字典组成,包含一个实例的所有属性。
键是属性名,值是属性相应的数据 值。
字典中仅有实例属性,没有类属性或特殊属性。
>>> ‘hello‘.__class__
<type ‘str‘>
>>> str.__class__
<type ‘type‘>
>>>
核心风格:修改__dict__
对类和实例来说,尽管__dict__属性是可修改的,但还是建议你不要修改这些字典,除非你知
道你的目的。这些修改可能会破坏你的OOP,造成不可预料的副作用。使用熟悉的句点属性标识来访
问及操作属性会更易于接受。需要你直接修改__dict__属性的情况很少,其中之一是你要重载
__setattr__特殊方法。实现__setattr__()本身是一个冒险的经历,满是圈套和陷阱,例如无穷递
归和破坏实例对象。这个故事还是留到下次说吧。
内建类型属性
内建类型也是类,有像类一样的属性。
对内建类型也可以使用 dir(),与任何其它对象一样,可以得到一个包含它属性名字的列表。
>>> x = 1 + 2J
>>> x.__class__
<class ‘complex‘>
>>> dir(x)
[‘__abs__‘, ‘__add__‘, ‘__bool__‘, ‘__class__‘, ‘__delattr__‘, ‘__dir__‘, ‘__div
mod__‘, ‘__doc__‘, ‘__eq__‘, ‘__float__‘, ‘__floordiv__‘, ‘__format__‘, ‘__ge__‘
, ‘__getattribute__‘, ‘__getnewargs__‘, ‘__gt__‘, ‘__hash__‘, ‘__init__‘, ‘__int
__‘, ‘__le__‘, ‘__lt__‘, ‘__mod__‘, ‘__mul__‘, ‘__ne__‘, ‘__neg__‘, ‘__new__‘, ‘
__pos__‘, ‘__pow__‘, ‘__radd__‘, ‘__rdivmod__‘, ‘__reduce__‘, ‘__reduce_ex__‘, ‘
__repr__‘, ‘__rfloordiv__‘, ‘__rmod__‘, ‘__rmul__‘, ‘__rpow__‘, ‘__rsub__‘, ‘__r
truediv__‘, ‘__setattr__‘, ‘__sizeof__‘, ‘__str__‘, ‘__sub__‘, ‘__subclasshook__
‘, ‘__truediv__‘, ‘conjugate‘, ‘imag‘, ‘real‘]
>>>
实例属性 vs 类属性
类和实例都是名字空间,类是类属性的名字空间,实例则是实例属性的。
访问类属性
类属性可通过类或实例来访问。
Python 首先会在实例中搜索名字,然后是类,再就是继承树中的基类。
从实例中访问类属性须谨慎
与通常Python 变量一样,任何对实例属性的赋值都会创建一个实例属性(如果不存在的话)并且对其赋值。
如果类属性中存在同名的属性,有趣的副作用即产生。(经典类和新式类都存在)--“隐藏”类属性
类属性持久性
修改类属性需要使用类名,而不是实例名。
13.7 从这里开始校对----------绑定和方法调用
方法仅仅是类内部定义的函数。(方法是类属性而不是实例属性)。
方法只有在其所属的类拥有实例时,才能被调用,当存在一个实例时,方法才被认为是绑定到那个实例了,没有实例时方法就是未绑定的。
任何一个方法定义中的第一个参数都是变量self,它表示调用此方法的实例对象。
核心笔记:self 是什么?
self 变量用于在类实例方法中引用方法所绑定的实例。因为方法的实例在任何方法调用中总是
作为第一个参数传递的,self 被选中用来代表实例。你必须在方法声明中放上self(你可能已经注
意到了这点),但可以在方法中不使用实例(self)。如果你的方法中没有用到self , 那么请考虑创建
一个常规函数,除非你有特别的原因。毕竟,你的方法代码没有使用实例,没有与类关联其功能,
这使得它看起来更像一个常规函数。在其它面向对象语言中,self 可能被称为 this。
调用绑定方法
方法,不管绑定与否,都是由相同的代码组成的,唯一的不同在于是否存在一个实例可以调用此方法,在很多情况下,程序员调用的都是一个绑定的方法。
当你在实例中调用一个绑定的方法 时,self 不需要明确地传入了。
当你还没有一个实例并且需要调用一个非绑定方法的时候你必须传递self 参数。
调用绑定方法 方法,不管绑定与否,都是由相同的代码组成的。唯一的不同在于是否存在一个实例可以调用 此方法。在很多情况下,程序员调用的都是一个绑定的方法。假定现在有一个 MyClass 类和此类的 一个实例 mc,而你想调用MyClass.foo()方法。因为已经有一个实例,你只需要调用mc.foo()就可 以。记得self 在每一个方法声明中都是作为第一个参数传递的。当你在实例中调用一个绑定的方法 时,self 不需要明确地传入了。这算是"必须声明self 作为第一个参数"对你的报酬。当你还没有 一个实例并且需要调用一个非绑定方法的时候你必须传递self 参数。
调用绑定方法
调用非绑定方法
调用非绑定方法并不经常用到。
需要调用一个还没有任何实例的类中的方法的一个主要的场景 是:你在派生一个子类,而且你要覆盖父类的方法,这时你需要调用那个父类中想要覆盖掉的构造方法。
调用非绑定方法 调用非绑定方法并不经常用到。需要调用一个还没有任何实例的类中的方法的一个主要的场景 是:你在派生一个子类,而且你要覆盖父类的方法,这时你需要调用那个父类中想要覆盖掉的构造方 法。 这是调用非绑定方法的最佳地方了。我们将在子类构造器中调用父类的构造器并且明确地传递 (父类)构造器所需要的self 参数(因为我们没有一个父类的实例)。子类中 __init__() 的第一 行就是对父类__init__()的调用。我们通过父类名来调用它,并且传递给它 self 和其他所需要的 参数。一旦调用返回,我们就能定义那些与父类不同的仅存在我们的(子)类中的(实例)定制。
调用非绑定方法
>>> class Foo(object):
... def __init__(self):
... self.foo = ‘foo‘
...
>>> class Bar(Foo):
... def __init__(self):
... Foo.__init__(self) #调用非绑定方法
... self.bar = ‘bar‘
... def show(self):
... print(self.foo + ‘\r\n‘ + self.bar)
...
>>> bar = Bar()
>>> bar.show()
foo
bar
>>>
13.8 静态方法和类方法
使用模块函数 比使用静态类方法更加常见。
通常的方法需要一个实例(self)作为第一个参数,并且对于(绑定的)方法调用来 说,self 是自动传递给这个方法的。
对于类方法而言,需要类而不是实例作为第一个参数,它是由解释器传给方法,类不需要特别地命名, 类似self,不过很多人使用cls 作为变量名字。
staticmethod()和classmethod()内建函数
一对内 建函数被引入,用于将作为类定义的一部分的某一方法声明“标记”(tag),“强制类型转换”(cast) 或者“转换”(convert)为这两种类型的方法之一。
>>> class C(object):
... def foo():
... print ‘static method foo()‘
... foo = staticmethod(foo)
... def bar(cls):
... print ‘class method bar()‘
... bar = classmethod(bar)
...
>>> C.foo()
static method foo()
>>> C.bar()
class method bar()
>>> c = C()
>>> c.foo()
static method foo()
>>> c.bar()
class method bar()
>>> C.bar(C)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bar() takes exactly 1 argument (2 given)
>>>
使用函数修饰符
函数修饰符可以把一个函数应用到另个函数对象上, 而且新函数对象依然绑定在原来的变量。
通过使用decorators,可以避免像重新赋值。
>>> class C(object):
... @staticmethod
... def foo():
... print ‘static method foo‘
... @classmethod
... def bar(cls):
... print ‘class method bar‘, cls.__class__
...
>>> C.foo()
static method foo
>>> C.bar()
class method bar <type ‘type‘>
>>>
13.9 组合
一个类被定义后,目标就是要把它当成一个模块来使用,并把这些对象嵌入到你的代码中去,同其它数据类型及逻辑执行流混合使用。
有两种方法可以在你的代码中利用类。
第一种是组合 (composition)。
就是让不同的类混合并加入到其它类中,来增加功能和代码重用性,你可以在一个大点的类中创建你自已的类的实例,实现一些其它属性和方法来增强对原来的类对象。
另一种方法是通过派生(derivation)。
13.10 子类和派生
当类之间有显著的不同,并且(较小的类)是较大的类所需要的组件时,组合表现得很好,
当你设计“相同的类但有一些不同的功能”时,派生就是一个更加合理的选择了。
OOP 的更强大方面之一是能够使用一个已经定义好的类,扩展它或者对其进行修改,而不会影响 系统中使用现存类的其它代码片段。
OOD 允许类特征在子孙类或子类中进行继承。
创建子类
创建子类的语法看起来与普通(新式)类没有区别,一个类名,后跟一个或多个需要从其中派生的父类:
class SubClassName (ParentClass1[, ParentClass2, ...]):
‘optional class documentation string‘
class_suite
如果你的类没有从任何祖先类派生,可以使用object 作为父类的名字。
经典类的声明唯一不同 之处在于其没有从祖先类派生---此时,没有圆括号:
class ClassicClassWithoutSuperclasses:
pass
13.11 继承
继承描述了基类的属性如何“遗传”给派生类。
一个子类可以继承它的基类的任何属性,不管是数据属性还是方法。
需要注意的是文档字符串对类,函数/方法, 还有模块来说都是唯一的,所以特殊属性__doc__不会从基类中继承过来。
__bases__类属性
对任何(子)类,它是一个包含其父类 (parent)的集合的元组。
注意,我们明确指出“父类”是相对所有基类(它包括了所有祖先类) 而言的,那些没有父类的类,它们的__bases__属性为空。
通过继承覆盖(Overriding)方法
子类中的方法会覆盖基类的同名方法
调用被覆盖的基类方法
调用一个未绑定的基类方法
使用super()内建方法
>>> class P(object):
... def __init__(self):
... print("calling P‘s constructor")
...
>>> class C(P):
... def __init__(self):
... super(C,self).__init__()
... print("calling C‘s constructor")
...
>>> p = P()
calling P‘s constructor
>>> c = C()
calling P‘s constructor
calling C‘s constructor
>>>
核心笔记:重写__init__不会自动调用基类的__init__
类似于上面的覆盖非特殊方法,当从一个带构造器 __init()__的类派生,如果你不去覆盖
__init__(),它将会被继承并自动调用。但如果你在子类中覆盖了__init__(),子类被实例化时,
基类的__init__()就不会被自动调用。这可能会让了解JAVA 的朋友感到吃惊。
class P(object):
def __init__(self):
print "calling P‘s constructor"
class C(P):
def __init__(self):
print "calling C‘s constructor"
>>> c = C()
calling C‘s constructor
如果你还想调用基类的 __init__(),你需要像上边我们刚说的那样,明确指出,使用一个子
类的实例去调用基类(未绑定)方法。相应地更新类C,会出现下面预期的执行结果:
class C(P):
def __init__(self):
P.__init__(self)
print "calling C‘s constructor"
>>> c = C()
calling P‘s constructor
calling C‘s constructor
上边的例子中,子类的__init__()方法首先调用了基类的的__init__()方法。这是相当普遍(不
是强制)的做法,用来设置初始化基类,然后可以执行子类内部的设置。这个规则之所以有意义的
原因是,你希望被继承的类的对象在子类构造器运行前能够很好地被初始化或作好准备工作,因为它
(子类)可能需要或设置继承属性。
对C++熟悉的朋友,可能会在派生类构造器声明时,通过在声明后面加上冒号和所要调用的所有
基类构造器这种形式来调用基类构造器。而在JAVA 中,不管程序员如何处理,子类构造器都会去调
用基类的的构造器。
Python 使用基类名来调用类方法,对应在JAVA 中,是用关键字super 来实现的,这就是super()
内建函数引入到Python 中的原因,这样你就可以“依葫芦画瓢”了:
class C(P):
def __init__(self):
super(C, self).__init__()
print "calling C‘s constructor"
使用super()的漂亮之处在于,你不需要明确给出任何基类名字...“跑腿事儿”,它帮你干了!
使用super()的重点,是你不需要明确提供父类。这意味着如果你改变了类继承关系,你只需要改一
行代码(class 语句本身)而不必在大量代码中去查找所有被修改的那个类的名字。
从标准类型派生
可变类型
class RoundFloat(float):
def __new__(cls, val):
return float.__new__(cls, round(val, 2)) # 调用一个未绑定的类方法
class RoundFloat(float):
def __new__(cls, val):
return super(RoundFloat, cls).__new__(cls, round(val, 2)) # 使用super()内建方法
不可变类型
class SortedKeyDict(dict):
def keys(self):
return sorted(super( SortedKeyDict, self).keys())
多重继承
方法解释顺序(MRO)
Python 2.2 使用一种唯一但不完善的MRO Python 2.2 是首个使用新式MRO 的版本,它必须取代经典类中的算法,原因在上面已谈到过。 在2.2 版本中,算法基本思想是根据每个祖先类的继承结构,编译出一张列表,包括搜索到的类, 按策略删除重复的。然而,在Python 核心开发人员邮件列表中,有人指出,在维护单调性方面失败 过(顺序保存),必须使用新的C3 算法替换,也就是从2.3 版开始使用的新算法。 在Python 2.2 以前的版本中,算法非常简单:深度优先,从左至右进行搜索,取得在子类中使 用的属性。其它Python 算法只是覆盖被找到的名字,多重继承则取找到的第一个名字。由于类,类 型和内建类型的子类,都经过全新改造, 有了新的结构,这种算法不再可行.
Python 2.2 使用一种唯一但不完善的MRO
经典类\新式类\菱形效应为难MRO\为什么经典类MRO 会失败
13.12 类、实例和其他对象的内建函数
issubclass()
issubclass() 布尔函数判断一个类是另一个类的子类或子孙类。
语法: issubclass(sub, sup)
这个函数也允许“不严格”的子类,意味着,一个类可视为其自身的子类。
从Python 2.3 开始,issubclass()的第二个参数可以是可能的父类组成的tuple(元组)
只要第一个参数是给定元组中任何一个候选类的子类时,就会返回True。
isinstance()
isinstance() 布尔函数在判定一个对象是否是另一个给定类的实例。
语法: isinstance(obj, cls)
同issubclass()一样,isinstance()也可以使用一个元组(tuple)作为第二个参数。
如果第一个参数是第二个参数中给定元组的任何一个候选类型或类的实例时,就会返回True。
第二个参数应当是类;不然,你会得到一个TypeError,但如果第二个参数是一个类型对 象,则不会出现异常,因为你也可以使用isinstance()来检查一个对象obj1 是否是obj2 的类型。
hasattr(), getattr(),setattr(), delattr()
*attr()系列函数可以在各种对象下工作,不限于类(class)和实例(instances)。
hasattr()函数
hasattr()函数是Boolean 型的,它的目的就是为了决定一个对象是否有一个特定的属性,一 般用于访问某属性前先作一下检查。
getattr()和setattr()函数
getattr()和setattr()函数相应地取得和赋值给对象的属性,getattr()会在你试图读取一个不存在的属性时,引发AttributeError 异常,除非给出那个可选的默认参数。
setattr()将要么加入一个新的属性,要么取代一个已存在的属性。
delattr()函数
delattr()函数会从一个对象中删除属性。
*attr()系列函数可以在各种对象下工作,不限于类(class)和实例(instances)。然而,因 为在类和实例中使用极其频繁,就在这里列出来了。需要说明的是,当使用这些函数时,你传入你 正在处理的对象作为第一个参数,但属性名,也就是这些函数的第二个参数,是这些属性的字符串 名字。换句话说,在操作obj.attr 时,就相当于调用*attr(obj,‘attr‘....)系列函数。 hasattr()函数是Boolean 型的,它的目的就是为了决定一个对象是否有一个特定的属性,一 般用于访问某属性前先作一下检查。getattr()和setattr()函数相应地取得和赋值给对象的属性, getattr()会在你试图读取一个不存在的属性时,引发AttributeError 异常,除非给出那个可选的 默认参数。setattr()将要么加入一个新的属性,要么取代一个已存在的属性。而delattr()函数会 从一个对象中删除属性。
Here are some examples using all the *attr() BIFs:
>>> class myClass(object):
... def __init__(self):
... self.foo = 100
...
>>> myInst = myClass()
>>> hasattr(myInst, ‘foo‘)
True
>>> getattr(myInst, ‘foo‘)
100
>>> hasattr(myInst, ‘bar‘) False
>>> getattr(myInst, ‘bar‘) Traceback (most recent call last):
File "<stdin>", line 1, in ?
getattr(myInst, ‘bar‘)
AttributeError: myClass instance has no attribute ‘bar‘
>>> getattr(c, ‘bar‘, ‘oops!‘)
‘oops!‘
>>> setattr(myInst, ‘bar‘, ‘my attr‘)
>>> dir(myInst)
[‘__doc__‘, ‘__module__‘, ‘bar‘, ‘foo‘]
>>> getattr(myInst, ‘bar‘) # same as myInst.bar #等同于 myInst.bar
‘my attr‘
>>> delattr(myInst, ‘foo‘)
>>> dir(myInst)
[‘__doc__‘, ‘__module__‘, ‘bar‘]
>>> hasattr(myInst, ‘foo‘)
False
dir()(列出一个模块所有属性的信息)
?? dir()作用在实例上(经典类或新式类)时,显示实例变量,还有在实例所在的类及所有它
的基类中定义的方法和类属性。
?? dir()作用在类上(经典类或新式类)时,则显示类以及它的所有基类的__dict__中的内容。
但它不会显示定义在元类(metaclass)中的类属性。
?? dir()作用在模块上时,则显示模块的__dict__的内容。(这没改动)。
?? dir()不带参数时,则显示调用者的局部变量。(也没改动)。
super()
一般情况下,程序员可能仅仅采用非绑定方式调用祖先类方法,使用 super()可以简化搜索一个合适祖先的任务,并且在调用它时,替你传入实例或类型对象。
事实上,super()是一个工厂函数,它创造了一个super object,为一个给定的类使用__mro__ 去查找相应的父类。
super() 的主要用途, 是来查找父类的属性, 比如, super(MyClass,self).__init__(),如果你没有执行这样的查找,你可能不需要使用super()。
语法如下:
super(type[, obj])
给出type,super()“返回此type 的父类”。
如果你希望父类被绑定,你可以传入obj 参数(obj 必须是type 类型的),否则父类不会被绑定。
obj 参数也可以是一个类型,但它应当是type 的一个子类。
通常,当给出obj 时:
?? 如果 obj 是一个实例,isinstance(obj,type)就必须返回True
?? 如果 obj 是一个类或类型,issubclass(obj,type)就必须返回True
vars()
vars()内建函数与dir()相似,只是给定的对象参数都必须有一个__dict__属性。
vars()返回一个字典,它包含了对象存储于其__dict__中的属性(键)及值,如果提供的对象没有这样一个属性,则会引发一个TypeError 异常。
如果没有提供对象作为vars()的一个参数,它将显示一个包含本地名字空间的属性(键)及其值的字典,也就是,locals()。
13.13 用特殊方法定制类
特殊方法是Python 中用来扩充类的强有力的方式。
它们可以实现:
?? 模拟标准类型
?? 重载操作符
特殊方法允许类通过重载标准操作符+,*, 甚至包括分段下标及映射操作操作[] 来模拟标准类型。
如同其它很多保留标识符,这些方法都是以双下划线(__)开始及结尾的。
基本定制型
基本定制型 C.__init__(self[, arg1, ...]) 构造器(带一些可选的参数) C.__new__(self[, arg1, ...]) 构造器(带一些可选的参数);通常用在设置不变数据类型的子类。 C.__del__(self) 解构器 C.__str__(self) 可打印的字符输出;内建str()及print 语句 C.__repr__(self) 运行时的字符串输出;内建repr() 和‘‘ 操作符 C.__unicode__(self) Unicode 字符串输出;内建unicode() C.__call__(self, *args) 表示可调用的实例 C.__nonzero__(self) 为object 定义False 值;内建bool() (从2.2 版开始) C.__len__(self) “长度”(可用于类);内建len()
对象(值)比较
对象(值)比较 C.__cmp__(self, obj) 对象比较;内建cmp() C.__lt__(self, obj) and 小于/小于或等于;对应<及<=操作符 C.__gt__(self, obj) and 大于/大于或等于;对应>及>=操作符 C.__eq__(self, obj) and 等于/不等于;对应==,!=及<>操作符
属性
属性 C.__getattr__(self, attr) 获取属性;内建getattr();仅当属性没有找到时调用 C.__setattr__(self, attr, val) 设置属性 C.__delattr__(self, attr) 删除属性 C.__getattribute__(self, attr) 获取属性;内建getattr();总是被调用 C.__get__(self, attr) (描述符)获取属性 C.__set__(self, attr, val) (描述符)设置属性 C.__delete__(self, attr) (描述符)删除属性
数值类型:二进制操作符
数值类型:二进制操作符 C.__*add__(self, obj) 加;+操作符 C.__*sub__(self, obj) 减;-操作符 C.__*mul__(self, obj) 乘;*操作符 C.__*div__(self, obj) 除;/操作符 C.__*truediv__(self, obj) True 除;/操作符 C.__*floordiv__(self, obj) Floor 除;//操作符 C.__*mod__(self, obj) 取模/取余;%操作符 C.__*divmod__(self, obj) 除和取模;内建divmod() C.__*pow__(self, obj[, mod]) 乘幂;内建pow();**操作符 C.__*lshift__(self, obj) 左移位;<<操作符 C.__*rshift__(self, obj) 右移;>>操作符 C.__*and__(self, obj) 按位与;&操作符 C.__*or__(self, obj) 按位或;|操作符 C.__*xor__(self, obj) 按位与或;^操作符
数值类型:一元操作符
数值类型:一元操作符 C.__neg__(self) 一元负 C.__pos__(self) 一元正 C.__abs__(self) 绝对值;内建abs() C.__invert__(self) 按位求反;~操作符
数值类型:数值转换
数值类型:数值转换 C.__complex__(self, com) 转为complex(复数);内建complex() C.__int__(self) 转为int;内建int() C.__long__(self) 转为long;内建long() C.__float__(self) 转为float;内建float()
数值类型:基本表示法(String)
数值类型:基本表示法(String) C.__oct__(self) 八进制表示;内建oct() C.__hex__(self) 十六进制表示;内建hex()
数值类型:数值压缩
数值类型:数值压缩 C.__coerce__(self, num) 压缩成同样的数值类型;内建coerce() C.__index__(self) 在有必要时,压缩可选的数值类型为整型(比如:用于切片索引等等)
序列类型
序列类型 C.__len__(self) 序列中项的数目 C.__getitem__(self, ind) 得到单个序列元素 C.__setitem__(self, ind,val) 设置单个序列元素 C.__delitem__(self, ind) 删除单个序列元素 C.__getslice__(self, ind1,ind2) 得到序列片断 C.__setslice__(self, i1, i2,val) 设置序列片断 C.__delslice__(self, ind1,ind2) 删除序列片断 C.__contains__(self, val) 测试序列成员;内建in 关键字 C.__*add__(self,obj) 串连;+操作符 C.__*mul__(self,obj) 重复;*操作符 C.__iter__(self) 创建迭代类;内建iter()
映射类型
映射类型 C.__len__(self) mapping 中的项的数目 C.__hash__(self) 散列(hash)函数值 C.__getitem__(self,key) 得到给定键(key)的值 C.__setitem__(self,key,val) 设置给定键(key)的值 C.__delitem__(self,key) 删除给定键(key)的值 C.__missing__(self,key) 给定键如果不存在字典中,则提供一个默认值
定制类的特殊方法
定制类的特殊方法 基本的定制和对象(值)比较特殊方法在大多数类中都可以被实现,且没有同任何特定的类型 模型绑定。延后设置,也就是所谓的Rich 比较,在Python2.1 中加入。属性组帮助管理您的类的 实例属性。这同样独立于模型。还有一个,__getattribute__(),它仅用在新式类中,我们将在后 面的章节中对它进行描述。 特殊方法中数值类型部分可以用来模拟很多数值操作,包括那些标准(一元和二进制)操作符, 类型转换,基本表示法,及压缩。也还有用来模拟序列和映射类型的特殊方法。实现这些类型的特 殊方法将会重载操作符,以使它们可以处理你的类类型的实例。 另外,除操作符__*truediv__()和__*floordiv__()在Python2.2 中加入,用来支持Python 除 操作符中待定的更改---可查看5.5.3 节。基本上,如果解释器启用新的除法,不管是通过一个开关 来启动Python,还是通过"from __future__ import division",单斜线除操作(/)表示的将是ture 除法,意思是它将总是返回一个浮点值,不管操作数是否为浮点数或者整数(复数除法保持不变)。 双斜线除操作(//)将提供大家熟悉的浮点除法,从标准编译型语言像C/C++及Java 过来的工程师 一定对此非常熟悉。同样,这些方法只能处理实现了这些方法并且启用了新的除操作的类的那些符 号。 表格中,在它们的名字中,用星号通配符标注的数值二进制操作符则表示这些方法有多个版本, 在名字上有些许不同。星号可代表在字符串中没有额外的字符,或者一个简单的“r”指明是一个右 结合操作。没有“r”,操作则发生在对于self OP obj 的格式; “r”的出现表明格式obj OP self。 比如,__add__(self,obj)是针对self+obj 的调用,而__radd__(self,obj)则针对obj+self 来调用。 增量赋值,起于Python 2.0,介绍了“原位”操作符。一个“i”代替星号的位置,表示左结合 操作与赋值的结合,相当是在self=self OP obj。举例,__iadd__(self,obj)相当于self=self+obj 的调用。
定制类的特殊方法
简单定制(RoundFloat2)
1 #!/usr/bin/env python 2 3 class RoundFloatManual(object): 4 def __init__(self, val): 5 assert isinstance(val, float), 6 "Value must be a float" 7 self.value = round(val, 2) 8 9 def __str__(self): 10 return ‘%.2f‘ % self.value 11 12 __repr__ = __str__ # 让__repr__()和__str__()的输出一致 13 14 rfm = RoundFloatManual(5.5964) 15 print rfm 16 print rfm.value 17 rfm2 = RoundFloatManual(5)
数值定制(Time60)
1 #!/usr/bin/env python 2 3 class Time60(object): # ordered pair 4 ‘Time60 - track hours and minutes‘ 5 def __init__(self, hr, min): # consstructor 6 ‘Time60 constructor - takes hours and minutes‘ 7 self.hr = hr # assign hours 8 self.min = min # assign minutes 9 10 # 显示 11 def __str__(self): 12 ‘Time60 - string representation‘ 13 return ‘%d:%d‘ % (self.hr, self.min) 14 15 __repr__ = __str__ 16 17 # 加法 18 # 新的对象通过调用类来创建,在类中,你一般不直接调用类名,而是使用self 的__class__属性,即实例化self 的那个类,并调用它。 19 def __add__(self, other): 20 ‘Time60 - overloading the addition operator‘ 21 return self.__class__(self.hr + other.hr, self.min + other.min) 22 23 # 原位加法 24 def __iadd__(self, other): 25 ‘Time60 - overloading in-place addition‘ 26 self.hr += other.hr 27 self.min += other.min 28 return self 29 30 tud = Time60(10,30) 31 mon = Time60(11,15) 32 print tud 33 print mon 34 print tud+mon 35 tud+=mon 36 print tud
13.14 私有化(实现隐藏,对象封装)
默认情况下,属性在Python 中都是“public”,类所在模块和导入了类所在模块的其他模块的代码都可以访问到。
双下划线(__)
Python 为类元素(属性和方法)的私有性提供初步的形式。由双下划线开始的属性在运行时被“混淆”,所以直接访问是不允许的。实际上,会在名字前面加上下划线和类名。
尽管这样做提供了某种层次上的私有化,但算法处于公共域中并且很容易被“击败”。这更多的 是一种对导入源代码无法获得的模块或对同一模块中的其他代码的保护机制.
这种名字混淆的另一个目的,是为了保护__XXX 变量不与父类名字空间相冲突。
单下划线(_)
简单的模块级私有化只需要在属性名前使用一个单下划线字符。 这就防止模块的属性用“from mymodule import *”来加载。这是严格基于作用域的,所以这同样适合于函数。
13.15 *授权
包装
包装是对一个已存在的对象进行包装,不管它是数据类型,还是一段代码,可以是对一个已存在的对象,增加新的,删除不要的,或者修改其它已存在的功能。
图13 包装类型
实现授权
授权是包装的一个特性,可用于简化处理有关dictating 功能,采用已存在的功能以达到最大限度的代码重用。
授权的过程,即是所有 更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__()方法,在代码中包含一个对getattr()内建函数的调用。
当引用一个属性时,Python 解释器将试着在局部名称空间中查找那个名字,比如一个
自定义的方法或局部实例属性。如果没有在局部字典中找到,则搜索类名称空间,以防一个类属性
被访问。最后,如果两类搜索都失败了,搜索则对原对象开始授权请求,此时,__getattr__()会被调用。
授权 授权是包装的一个特性,可用于简化处理有关dictating 功能,采用已存在的功能以达到最大 限度的代码重用。 包装一个类型通常是对已存在的类型的一些定制。我们在前面提到过,这种做法可以新建,修 改或删除原有产品的功能。其它的则保持原样,或者保留已存功能和行为。授权的过程,即是所有 更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。 实现授权的关键点就是覆盖__getattr__()方法,在代码中包含一个对getattr()内建函数的调 用。特别地,调用getattr()以得到默认对象属性(数据属性或者方法)并返回它以便访问或调用。 特殊方法__getattr__()的工作方式是,当搜索一个属性时,任何局部对象首先被找到(定制的对象)。 如果搜索失败了,则__getattr__()会被调用,然后调用getattr()得到一个对象的默认行为。 换言之,当引用一个属性时,Python 解释器将试着在局部名称空间中查找那个名字,比如一个 自定义的方法或局部实例属性。如果没有在局部字典中找到,则搜索类名称空间,以防一个类属性 被访问。最后,如果两类搜索都失败了,搜索则对原对象开始授权请求,此时,__getattr__()会被 调用。
1 class WrapMe(object): 2 def __init__(self, obj): 3 self.__data = obj 4 5 def get(self): 6 return self.__data 7 8 def __repr__(self): 9 return `self.__data` 10 11 def __str__(self): 12 return str(self.__data) 13 14 def __getattr__(self, attr): 15 return getattr(self.__data, attr) 16 17 def test(): 18 wc = WrapMe(3.5 + 4.2j) 19 print `wc` 20 print wc.real, wc.imag 21 22 test()
1 # 包装标准类型(twrapme.py) 2 #!/usr/bin/env python 3 4 from time import time, ctime 5 6 class TimedWrapMe(object): 7 8 def __init__(self, obj): 9 self.__data = obj 10 self.__ctime = self.__mtime = 11 self.__atime = time() 12 13 def get(self): 14 self.__atime = time() 15 return self.__data 16 17 def gettimeval(self, t_type): 18 if not isinstance(t_type, str) or 19 t_type[0] not in ‘cma‘: 20 raise TypeError, 21 "argument of ‘c‘, ‘m‘, or ‘a‘ req‘d" 22 return getattr(self, ‘_%s__%stime‘ % 23 (self.__class__.__name__, t_type[0])) 24 25 def gettimestr(self, t_type): 26 return ctime(self.gettimeval(t_type)) 27 28 def set(self, obj): 29 self.__data = obj 30 self.__mtime = self.__atime = time() 31 32 def __repr__(self): # repr() 33 self.__atime = time() 34 return `self.__data` 35 36 def __str__(self): # str() 37 self.__atime = time() 38 return str(self.__data) 39 40 def __getattr__(self, attr): # delegate 41 self.__atime = time() 42 return getattr(self.__data, attr) 43 44 two = TimedWrapMe(99) 45 print two 46 print two.gettimestr(‘c‘)
13.16 新式类的高级特性 (Python 2.2+)
新式类的通用特性
由于类型和类的统一,这些特性中最重要的是能够子类化Python 数据类型。
其中一个副作用是,所有的Python 内建的 “casting” 或转换函数现在都是工厂函数,当这些函数被调用时,你实际上是对相应的类型进行实例化。
为测试一个对 象是否是一个整数,旧风格中,我们必须调用type()两次或者import 相关的模块并使用其属性;但现在只需要使用isinstance(),甚至在性能上也有所超越。
尽管isinstance()很灵活,但它没有执行“严格匹配”比较----如果obj 是一个给定类型的实例或其子类的实例,也会返回True,但如果想进行严格匹配,你仍然需要使用is 操作符。
新式类的通用特性 我们已提讨论过有关新式类的一些特性。由于类型和类的统一,这些特性中最重要的是能够子类化Python 数据类型。其中一个副作用是,所有的Python 内建的 “casting” 或转换函数现在都 是工厂函数。当这些函数被调用时,你实际上是对相应的类型进行实例化。 下面的内建函数,跟随Python 多日,都已“悄悄地”(也许没有)转化为工厂函数: ?? int(), long(), float(), complex() ?? str(), unicode() ?? list(), tuple() ?? type() 还有,加入了一些新的函数来管理这些“散兵游勇”: ?? basestring()1 ?? dict() ?? bool() ?? set(),2 frozenset()2 ?? object() ?? classmethod() ?? staticmethod() ?? super() ?? property() ?? file() 这些类名及工厂函数使用起来,很灵活。不仅能够创建这些类型的新对象,它们还可以用来作 为基类,去子类化类型,现在还可以用于isinstance()内建函数。使用isinstance()能够用于替 换用烦了的旧风格,而使用只需少量函数调用就可以得到清晰代码的新风格。比如,为测试一个对 象是否是一个整数,旧风格中,我们必须调用type()两次或者import 相关的模块并使用其属性;但 现在只需要使用isinstance(),甚至在性能上也有所超越: OLD (not as good): ?? if type(obj) == type(0)… ?? if type(obj) == types.IntType… BETTER: ?? if type(obj) is type(0)… EVEN BETTER: ?? if isinstance(obj, int)… ?? if isinstance(obj, (int, long))… ?? if type(obj) is int… 记住:尽管isinstance()很灵活,但它没有执行“严格匹配”比较----如果obj 是一个给定类 型的实例或其子类的实例,也会返回True。但如果想进行严格匹配,你仍然需要使用is 操作符。
__slots__类属性
__dict__属性
字典位于实例的“心脏”。
__dict__属性跟踪所有实例属性。
举例来说,你有一个实例inst.它有一个属性foo,那使用inst.foo 来访问它与使用inst.__dict__[‘foo‘]来访问是一致的。
字典会占据大量内存,如果你有一个属性数量很少的类,但有很多实例,那么正好是这种情况。
__slots__属性
为内存上的考虑,用户现在可以使用__slots__属性来替代__dict__。
基本上,__slots__是一个类变量,由一序列型对象组成,由所有合法标识构成的实例属性的集 合来表示。
它可以是一个列表,元组或可迭代对象。也可以是标识实例能拥有的唯一的属性的简单字符串。
任何试图创建一个其名不在__slots__中的名字的实例属性都将导致AttributeError 异常。
带__slots__属性的类定义不会存在__dict__了(除非你在__slots__中增加 ‘__dict__‘元素)。
特殊方法__getattribute__()
Python 类有一个名为__getattr__()的特殊方法,它仅当属性不能在实例的__dict__或它的类 (类的__dict__),或者祖先类(其__dict__)中找到时,才被调用。
__getattribute__()是一个适当的函数来执行每一个属性访问,不光是当属性不能 找到的情况。
如果类同时定义了__getattribute__()及__getattr__()方法,除非明确从__get-attribute__() 调用,或__getattribute__()引发了AttributeError 异常,否则后者不会被调用。
描述符
描述符是Python 新式类中的关键点之一。
描述符为对象属性提供强大的API。
描述符是 表示对象属性的一个代理。
当需要属性时,可根据你遇到的情况,通过描述符(如果有)或者采用常规方式(句点属性标识法)来访问它。
13.17 相关模块和文档
与类相关的模块
模块 说明
UserList 提供一个列表对象的封装类
UserDict 提供一个字典对象的封装类
UserString 提供一个字符串对象的封装类;它又包括一个MutableString 子类,如果有需
要,可以提供有关功能
types 定义所有Python 对象的类型在标准Python 解释器中的名字
operator 标准操作符的函数接口