Linux 版 (精华区)
发信人: emacs (被淹死的鱼), 信区: Linux
标 题: 第三章 面向对象框架 -- 2
发信站: 哈工大紫丁香 (Sat Jun 15 09:52:31 2002) , 转信
3.6. 高级专用类方法
比起刚才的 __getitem__ 和 __setitem__ 还有更多的专用函数。某些可以让你模拟出你
甚至可能不知道的功能。
例 3.13. 在 UserDict 中更多的专用方法
def __repr__(self): return repr(self.data)
def __cmp__(self, dict):
if isinstance(dict, UserDict):
return cmp(self.data, dict.data)
else:
return cmp(self.data, dict)
def __len__(self): return len(self.data)
def __delitem__(self, key): del self.data[key] __repr__ 是一个专用的方法
,在当调用 repr(instance) 时被调用。repr 函数是一个内置函数,它返回一个对象的字
符串表示。它可以用在任何对象上,不仅仅是类的实例。你已经对 repr 相当熟悉了,尽
管你不知道它。在交互式窗口中,当你只敲入一个变量名,接着按回车,Python使用 rep
r 来显示变量的值。自已用一些数据来创建一个字典 d ,然后用 print repr(d) 来看一
看吧。
__cmp__ 在比较类实例时被调用。通常,你可以通过使用 == 比较任意两个Python对象
,不只是类实例。有一些规则,定义了何时内置数据类型被认为是相等的,例如,字典在
有着全部相同的关键字和值时是相等的。对于类实例,你可以定义 __cmp__ 方法,自已编
写比较逻辑,然后你可以使用 == 来比较你的类,Python将会替你调用你的 __cmp__ 专用
方法。
__len__ 在当调用 len(instance) 时被调用。len 是一个内置函数,可以返回一个对象
的长度。它可以用于任何被认为理应有长度的对象。字符串的 len 是它的字符个数;字典
的 len 是它的关键字的个数;列表或序列的 len 是元素的个数。对于类实例,定义 __l
en__ 方法,接着自已编写长度的计算,然后调用 len(instance),Python将替你调用你的
__len__ 专用方法。
__delitem__ 在调用 del instance[key] (你可能记得它作为从字典中删除单个元素的
方法)时调用,Python替你调用 __delitem__ 专用方法。
在这个地方,你可能会想,“所有这些工作只是为了在类中做一些我可以对一个内置数据
类型所做的操作”。不错,如果你能够从象字典一样的内置数据类型进行继承的话,事情
就容易多了(那样整个 UserDict 类将完全不需要了)。但是也许你可以,专用方法仍然是
有用的,因为它们可以用于任何的类,而不只是象 UserDict 的封装类。
专用方法意味着任何类可以象字典一样保存键-值对,只要定义 __setitem__ 方法。任何
类可以表现得象一个序列,只要通过定义 __getitem__ 方法。任何定义了 __cmp__ 方法
的类可以用 == 进行比较。并且如果你的类表现拥有类似长度的东西,不要定义 GetLeng
th 方法,而定义 __len__ 方法,使用 len(instance)。
但是其它的面向对象语言仅让你定义一个对象的物理模型(“这个对象有 GetLength 方法
”),专用类方法象 __len__ 允许你定义一个对象的逻辑模型(“这个对象有一个长度”)
。
存在许多其它的专用方法。有一整套的专用方法,可以让类表现得象数值一样,允许你在
类实例上进行加,减,和执行其它数学操作。(关于这一点典型的例子就是表示复数的类,
数值带有实数和虚数部分。) __call__ 方法让一个类表现得象一个函数,允许你直接调用
一个类实例。并且存在其它的专用函数,允许类拥有只读或只写数据属性,在后面的章节
中我们会更多地谈到这些。
3.7. 类属性
你已经知道了数据属性,它们是被一个特定的类定例所拥有的变量。Python也支持类属性
,它们是由类本身所拥有的。
例 3.14. 类属性介绍
class MP3FileInfo(FileInfo):
"store ID3v1.0 MP3 tags"
tagDataMap = {"title" : ( 3, 33, stripnulls),
"artist" : ( 33, 63, stripnulls),
"album" : ( 63, 93, stripnulls),
"year" : ( 93, 97, stripnulls),
"comment" : ( 97, 126, stripnulls),
"genre" : (127, 128, ord)}>>> import fileinfo
>>> fileinfo.MP3FileInfo
<class fileinfo.MP3FileInfo at 01257FDC>
>>> fileinfo.MP3FileInfo.tagDataMap
{'title': (3, 33, <function stripnulls at 0260C8D4>),
'genre': (127, 128, <built-in function ord>),
'artist': (33, 63, <function stripnulls at 0260C8D4>),
'year': (93, 97, <function stripnulls at 0260C8D4>),
'comment': (97, 126, <function stripnulls at 0260C8D4>),
'album': (63, 93, <function stripnulls at 0260C8D4>)}
>>> m = fileinfo.MP3FileInfo()
>>> m.tagDataMap
{'title': (3, 33, <function stripnulls at 0260C8D4>),
'genre': (127, 128, <built-in function ord>),
'artist': (33, 63, <function stripnulls at 0260C8D4>),
'year': (93, 97, <function stripnulls at 0260C8D4>),
'comment': (97, 126, <function stripnulls at 0260C8D4>),
'album': (63, 93, <function stripnulls at 0260C8D4>)} MP3FileInfo 是类本身,不
是任何类的特别实例。
tagDataMap 是一个类属性:字面的意思,一个类的属性。它是在创建任何类实例之前就
有效了。
类属性既可以通过直接对类的引用,也可以通过对类的任意实例的引用来使用。
类属性可以作为类级别的常量来使用(这就是为什么我们在 MP3FileInfo 中使用它们),但
是它们不是真正的常量。[3] 你也可以修改它们。
例 3.15. 修改类属性
>>> class counter:
... count = 0
... def __init__(self)
... self.__class__.count += 1
...
>>> counter
<class __main__.counter at 010EAECC>
>>> counter.count
0
>>> c = counter()
>>> c.count
1
>>> counter.count
1
>>> d = counter()
>>> d.count
2
>>> c.count
2
>>> counter.count
2 count 是 counter 类的一个类属性。
__class__ 是每个类实例的一个内置属性(也是每个类的)。它是一个类的引用,而 sel
f 是一个类(在本例中, 是 counter 类)的实例。
因为 count 是一个类属性,它可以在我们创建任何类实例之前,通过直接对类引用而得
到。
因为创建一个类实例会调用 __init__ 方法,它会给类属性 count 加 1。这样会影响到
类自身,不只是新创建的实例。
创建第二个实例将再次增加类属性 count。注意类属性是如何被类和所有类实例所共享
的。某些程序语言(明显的是Powerbuilder)因为这个原因将类属性叫做“共享变量”。
3.8. 私有函数
象大多数语言,Python也有私有函数的概念,私有函数不可以从它们的模块外面被调用;
私有类方法,不能够从它们的类外面被调用;并且私有属性,不能够从它们的类外面被使
用。不象大多数的语言,一个Python函数,方法,或属性是私有还是公有,完全取决于它
的名字。
在 MP3FileInfo 中,有两个方法:__parse 和 __setitem__。正如我们已经讨论过的,_
_setitem__ 是一个专有方法;通常,你不直接调用它,而是通过在一个类上使用字典语法
来调用,但它是公有的,并且如果有一个真正好的理由,你可以直接调用它(甚至从 file
info 模块的外面)。然而,__parse 是私有的,因为在它的名字前面有两个下划线。
如果一个Python函数的名字,类方法,或属性以两个下划线开始(但不是结束),它是私有
的;其它所有的都是公有的。
在Python中,所有的专用方法(象__setitem__)和内置属性(象__doc__)遵守一个标准的命
名习惯:开始和结束都有两个下划下。不要对你自已的方法和属性用这种方法命名;它后
面只会搞乱你(或其它人)。
Python没有类方法保护的概念(只能用于它们自已的类和子父中)。类方法要不私有(只能在
它们自已的类中使用)要不公有(任何地方都可使用)。
例 3.16. 尝试调用一个私有方法
>>> import fileinfo
>>> m = fileinfo.MP3FileInfo()
>>> m.__parse("/music/_singles/kairo.mp3")
Traceback (innermost last):
File "<interactive input>", line 1, in ?
AttributeError: 'MP3FileInfo' instance has no attribute '__parse' 如果你试图调
用一个私有方法,Python将引发一个有些误导的异常,宣称那个方法不存在。当然它确实
存在,但是它是私有的,所以在类外是不可使用的。[4]
进一步阅读
------------------------------------------------------------------------------
--
Python Tutorial 讨论了私有变量的内部工作方式。
脚注
------------------------------------------------------------------------------
--
[4] 严格地说,私有方法在它们的类外是有效的,只是不容易处理。在Python中没有什么
是真正私有的;在内部,私有方法和属性的名字被忽然破坏和修补(译注:原文为“inter
nally, the names of private methods and attributes are mangled and unmangled o
n the fly to make them seem inaccessible by their given names.”,但不知道“ma
ngled and unmangled on the fly”应如何翻译为好),以致于使得它们看上去用它们给定
的名字是无法使用的。你可以通过 _MP3FileInfo__parse 名字来使用 MP3FileInfo 类的
__parse 方法。知道了这个方法很有趣,然后要保证决不在真正的代码中使用它。私有方
法由于某种原因而私有,但是象其它很多在Python中的东西一样,它们的私有化基本上是
习惯问题,而不是强迫的。
3.9. 处理异常
象许多面向对象语言一样,Python具有异常处理,通过使用 try...except 块。C++,Jav
a,和Delphi也有这个机制;VB根据它的声明将在版本7中实现它,Powerbuilder将在版本
8中实现。在Python中的语法同C++,Java和Delphi相似。
如果你已经了解了异常的全部知识,你可以跳过这节。如果你一直在一种松散语言下编程
,或者你使用着真正的语言但没有用过异常,本节非常重要。
异常在Python中无处不在;实际上在标准Python库中的每个模块都使用了它们,并且Pyth
on自已会在许多不同的情况下引发它们。在整本书中你已经再三看到它们了。
使用不存在的字典关键字将引发 KeyError 异常。
搜索列表中不存在的值将引发 ValueError 异常。
调用不存在的方法将引发 AttributeError 异常。
引用不存在的变量将引发 NameError 异常。
未强制转换就混和数据类型将引发 TypeError 异常。
在这些情况下,我们都在简单使用Python IDE:一个错误发生了,异常被打印出来(根据你
的IDE,有意地以一种刺眼的红色形式表示),并且就这些。这叫做未处理异常;当异常被
引发时,没有代码来显式地关注它和处理它,所以异常被传给置在Python中的缺省的处理
,它会输出一些调试信息并且终止运行。在IDE中,这不是什么大事,但是如果发生在你真
正的Python程序运行的时候,整个程序将会终止。[5]
然而,一个异常不一定会引起程序的完全崩溃。当异常引发时,可以被处理掉。有时候一
个异常实际是因为代码中的bug(比如使用一个不存在的变量),但是许多时候,一个异常是
可以预计的。如果知道一行代码可能会引发异常(象打开一个可能不存在的文件,或连到一
个可能不能处理的数据库),你应该使用一个 try...except 块来处理异常。
例 3.17. 打开一个不存在的文件
>>> fsock = open("/notthere", "r")
Traceback (innermost last):
File "<interactive input>", line 1, in ?
IOError: [Errno 2] No such file or directory: '/notthere'
>>> try:
... fsock = open("/notthere")
... except IOError:
... print "The file does not exist, exiting gracefully"
... print "This line will always print"
The file does not exist, exiting gracefully
This line will always print 使用内置 open 函数,我们可以试着打开一个文件来读取
(在下一节有更多的关于 open)。但是那个文件不存在,所以这样就引发 IOError 异常。
因为我们没有提供任何显式的对 IOError 异常的检查,Python仅仅打印出某个关于发生了
什么的调试信息,然后终止。
我们试图打开同样不存在的文件,但是这次我们在一个 try...except 内来执行它。
当 open 方法引发 IOError 异常时,我们已经准备好处理它了。 except IOError: 行
捕捉异常接着执行我们自已的代码块,这个代码块在本例中只是打印出更令人愉快的错误
信息。
一旦异常被处理了,处理通常在 try...except 块之后的第一行继续进行。注意这一行
将总是打印出来,无论异常是否发生。如果在你的根目录下确实有一个叫 notthere 的文
件,对 open 的调用将成功,except 子句将忽略,并且最后一行仍将执行。
异常可能看不去不友好(毕竟,如果你不捕捉异常,整个程序将崩溃),但是考虑一下别的
方法。你宁愿找回对于不存在文件的不可用的文件对象吗?不管怎么样你都得检查它的有
效性,而且如果你忘记了,你的程序将会在下面某个地方给出奇怪的错误,这样你将不得
不追溯到源程序。我确信你做过这种事;这可并不有趣。使用异常,一发生错误,你就可
以在问题的源头通过标准的方法来处理它们。
除了处理实际的错误条件之外,对于异常还有许多其它的用处。在标准Python库中一个普
通的用法就是试着导入一个模块,然后检查是否它能使用。导入一个并不存在的模块将引
发一个 ImportError 异常。你可以使用这种方法来定义多级别的功能,依靠在运行时哪个
模块是有效的,或支持多种平台(即平台特定代码被分离到不同的模块中)。
例 3.18. 支持特定平台功能
这个代码来源于 getpass 模块,一个从用户得到口令的封装模块。得到口令在UNIX,Win
dows,和MacOS平台上的实现是不同的,但是这个代码封装了所有不同。
# Bind the name getpass to the appropriate function
try:
import termios, TERMIOS
except ImportError:
try:
import msvcrt
except ImportError:
try:
from EasyDialogs import AskPassword
except ImportError:
getpass = default_getpass
else:
getpass = AskPassword
else:
getpass = win_getpass
else:
getpass = unix_getpass termios 是一个UNIX特定模块,它提供了对于输入终端的
底层控制。如果这个模块无效(因为它不在你的系统上,或你的系统不支持它),则导入失
败,Python引发我们捕捉的 ImportError 异常。
哦,我们没有 termios,所以让我们试试 msvcrt,它是一个Windows特定模块,可以提
供在Microsoft Visual C++运行服务中的许多有用的函数的一个API。如果导入失败,Pyt
hon会引发我们捕捉的 ImportError 异常。
如果前两个不能工作,我们试着从 EasyDialogs 导入一个函数,它是一个MacOS特定模
块,提供了各种各样类型的弹出对话框。再一次,如果导入失败,Python会引发一个我们
捕捉的 ImportError 异常。
这些平台特定的模块没有一个有效(有可能,因为Python已经移植到了许多不同的平台上
了),所以我们需要回头使用一个缺省口令输入函数(这个函数定义在 getpass 模块中的别
的地方)。注意,我们在这里做的:我们将函数 default_getpass 赋给变量 getpass。如
果你读了官方 getpass 文档,它会告诉你 getpass 模块定义了一个 getpass 函数。它是
这样做的:通过绑定 getpass 到正确的函数来适应你的平台。然后当你调用 getpass 函
数时,你实际上调用了平台特定的函数,是这段代码已经为你设置好的。你不需要知道或
关心你的代码正运行在何种平台上;只要调用 getpass,则它总能正确处理。
一个 try...except 块可以有一条 else 子句,就象 if 语句。如果在 try 块中没有异
常引发,然后 else 子句被执行。在本例中,那就意味着如果 from EasyDialogs import
AskPassword 导入可工作,所以我们应该绑定 getpass 到 AskPassword 函数。其它每个
try...except 块有着相似的 else 子句,当我们找到一个 import 可用时,来绑定 get
pass 到适合的函数 。
--
※ 来源:.哈工大紫丁香 http://bbs.hit.edu.cn [FROM: 211.93.34.115]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:208.398毫秒