Chinaunix首页 | 论坛 | 博客
  • 博客访问: 720535
  • 博文数量: 161
  • 博客积分: 2998
  • 博客等级: 少校
  • 技术积分: 1697
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-28 09:39
文章分类

全部博文(161)

文章存档

2012年(6)

2011年(120)

2010年(35)

分类: Python/Ruby

2011-10-08 23:36:44

第六章 模块

如果退出Python解释程序然后再进入,原有的定义(函数和变量)就丢失了。所以,如果需要写长一点的程序,最好用一个文本编辑程序为解释程序准备输入,然后以程序文件作为输入来运行Python解释程序,这称为准备脚本(script)。当你的程序变长时,最好把它拆分成几个文件以利于维护。你还可能想在几个程序中都使用某个很方便的函数,但又不想把函数定义赋值到每一个程序中。

为了支持这些,Python有一种办法可以把定义放在一个文件中然后就可以在一个脚本中或交互运行中调用。这样的文件叫做一个模块;模块中的定义可以导入其它模块或主模块(主模块指在解释程序顶级执行的脚本或交互执行的程序所能访问的变量集合)。

模块是包含了Python定义和语句的文件。文件名由模块名加上后缀“.py”构成。在模块内,模块的名字(作为一个字符串)可以由全局变量__name__的值获知。例如,在Python的搜索路径中用你习惯使用的文本编辑器(Python 1.5.2包含了一个用Tkinter编写的IDLE集成开发环境,MS Windows下有一个PythonWin界面也可以进行Python程序编辑)生成一个名为“fibo.py ”的文件,包含如下内容:

# Fibonacci numbers module   def fib(n): # 输出小于n的Fibonacci序列 a, b = 0, 1 while b < n: print b, a, b = b, a+b   def fib2(n): # 返回小于n的Fibonacci序列 result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result

然后进入Python解释程序(在IDLE或PythonWin中可以直接进入解释程序窗口),用如下命令可以导入模块:

>>> import fibo

这不会把模块fibo中的函数的名字直接引入当前的符号表,这只是把模块名fibo引入。可以用模块名来访问其中的函数:

>>> fibo.fib(1000) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 >>> fibo.fib2(100) [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] >>> fibo.__name__ 'fibo'

如果经常使用某个函数可以给它赋一个局部名字:

>>> fib = fibo.fib >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 6.1 模块的进一步介绍

模块除了可以包含函数定义之外也可以包含可执行语句。这些可执行语句用来初始化模块,它们只在模块第一次被导入时执行。

每个模块有自己私有的符号表,这个私有符号表对于模块中的所有函数而言却是它们的全局符号表。因此,模块作者可以在模块中使用全局变量而不需担心与模块用户的全局变量冲突。另一方面,如果你有把握的话也可以用访问模块中函数的格式,即modname.itemname的方法来修改模块中的全局变量。

模块可以导入其它模块。我们通常把所有的导入语句放在模块(或脚本)的开始位置,这不是规定要求的。导入的模块名放入模块的全局符号表中。

导入还有另一种用法,可以把模块中的名字直接导入使用者的符号表。例如:

>>> from fibo import fib, fib2 >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377

这不会把模块名导入使用者的符号表中(例如,上面例子中fibo就没有定义)。

还有一种办法可以导入一个模块中定义的所有名字:

>>> from fibo import * >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377

这可以把模块中除了以下划线结尾的所有名字导入。

6.1.1 模块搜索路径

在导入名为spam的模块时,解释程序先在当前目录中寻找名为“spam.py”的文件,然后从环境变量PYTHONPATH所定义的目录列表中寻找。PYTHONPATH的用法和可执行文件的搜索路径PATH用法相同,都是一个目录列表。当PYTHONPATH未设置的时候,或者文件仍找不到,则搜索继续在安装时设定的缺省路径搜索,在Unix中,这通常是“.:/usr/local/lib/python” 。

实际上,模块是按变量sys.path指定的路径搜索的,此变量在解释程序启动时初始化为包含输入脚本的目录(或当前路径),PYTHONPATH和安装缺省路径。这样,用户可以通过修改sys.path 来修改和替换模块搜索路径。参见后面关于标准模块的一节。

6.1.2 “编译”的Python文件

为了提高调用许多标准模块的小程序的启动时间,一个重要的措施是,如果在找到“spam.py ”的目录中存在一个名为“spam.pyc”的文件,就认为此文件包含了模块spam的一个所谓“ 字节编译”版本。用于生成“spam.pyc”的“spam.py”的修改时间被记入了“spam.pyc”中,如果记录的修改时间与现在文件的时间不相符的话就忽略编译文件。

一般不需要自己生成“spam.pyc”这样的编译文件。每当“spam.py”成功编译后解释程序就尝试写编译版本“spam.pyc”,如果不可写也不会出错;如果因为某种原因此文件没有写完则生成的“spam.pyc”被识别为不完整的而被忽略。编译文件“spam.pyc”的格式是不依赖于平台的,所以不同结构的机器可以共享Python模块目录。

下面是对专家的一些窍门:

  • 如果Python解释程序是以-O标志启动的,将生成优化的编译代码,保存在“.pyo”文件中。目前优化不是很多,现在只是去掉assert语句和SET_LINENO指令。使用了-O标志时,所有字节码都是优化的,“.pyc”文件被忽略,“.py”文件被编译为优化的字节码。

  • 给Python解释程序两个优化标志(-OO)产生的优化代码有时会导致程序运行不正常。目前双重优化只从字节码中删除了__doc__字符串,使得“.pyo”文件较小。有些程序可能是依赖于文档字符串的,所以只有在确知不会有问题时才可以使用这样的优化。

  • 从“.pyc”或“.pyo”读入的程序并不能比从“.py”读入的运行更快,它们只是调入速度更快一些。

  • 如果一个程序是用在命令行指定脚本文件名的方式运行的,脚本的字节码不会写入“.pyc ”或“.pyo”文件。所以如果把程序的主要代码都移入一个模块,脚本中只剩下导入该模块的引导程序则可以略微缩短脚本的启动时间。

  • 可以有叫做“spam.pyc”(当用了-O标志时为“spam.pyo”)的文件而没有对应的源文件“spam.py”。这可以用来分发一个比较难反编译的Python代码库。

  • 模块compileall可以把一个目录中所有模块编译为“.pyc”文件(指定了-O选项时编译为“.pyo”文件)。

6.2 标准模块

Python带有一个标准模块库,在另一个文档《Python库参考》中进行了描述。一些模块直接编入了解释程序中,这些模块不是语言的核心,为了运行效率或者为了提供对于系统调用这样的系统底层功能而编入了解释程序中。提供那些模块是编译时的选择,例如,amoeba模块只在提供amoeba底层指令的系统中才能提供。

有一个模块值得特别重视:sys模块,每一个Python解释程序中都编译入了这个模块。变量sys.ps1和sys.ps2定义了交互运行时的初始提示和续行提示。

>>> import sys >>> sys.ps1 '>>> ' >>> sys.ps2 '... ' >>> sys.ps1 = 'C> ' C> print 'Yuck!' Yuck! C>

这两个变量只在解释程序以交互方式运行时才有定义。

变量sys.path是一个字符串列表,由它确定解释程序的模块搜索路径。它被初始化为环境变量PYTHONPATH所指定的缺省路径,环境变量没有定义时初始化为安装时的缺省路径。可以用标准的列表操作修改这个搜索路径,例如:

>>> import sys >>> sys.path.append('/ufs/guido/lib/python') 6.3 dir()函数

内置函数dir()用于列出一个模块所定义的名字,它返回一个字符串列表:

>>> import fibo, sys >>> dir(fibo) ['__name__', 'fib', 'fib2'] >>> dir(sys) ['__name__', 'argv', 'builtin_module_names', 'copyright', 'exit', 'maxint', 'modules', 'path', 'ps1', 'ps2', 'setprofile', 'settrace', 'stderr', 'stdin', 'stdout', 'version']

没有自变量时,dir()列出当前定义的名字。

>>> a = [1, 2, 3, 4, 5] >>> import fibo, sys >>> fib = fibo.fib >>> dir() ['__name__', 'a', 'fib', 'fibo', 'sys']

注意dir()列出了所有各类名字:变量名、模块名、函数名,等等。dir()不会列出内置函数、变量的名字。要想列出内置名字的话需要使用标准模块__builtin__:

>>> import __builtin__ >>> dir(__builtin__) ['AccessError', 'AttributeError', 'ConflictError', 'EOFError', 'IOError', 'ImportError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'MemoryError', 'NameError', 'None', 'OverflowError', 'RuntimeError', 'SyntaxError', 'SystemError', 'SystemExit', 'TypeError', 'ValueError', 'ZeroDivisionError', '__name__', 'abs', 'apply', 'chr', 'cmp', 'coerce', 'compile', 'dir', 'divmod', 'eval', 'execfile', 'filter', 'float', 'getattr', 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'len', 'long', 'map', 'max', 'min', 'oct', 'open', 'ord', 'pow', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'round', 'setattr', 'str', 'type', 'xrange'] 6.4 包

Python中可以用“包”来组织Python的模块名字空间,名字引用时可以用“带点的模块名。例如,模块名A.B代表包“A”内名为“B”的子模块。正如使用模块可以使不同模块的作者不用顾虑彼此的全局变量名会冲突,使用带点的模块名可以使多模块包如NumPy和PIL的作者不需要担心彼此的模块名会冲突。

假设你有一系列处理声音文件和声音数据的模块(称为一个“包”)。有许多种不同的声音文件格式(通常用扩展名来识别,如“wav”,“.aiff”,“.au”),所以你可能需要制作并维护一组不断增加的模块来处理不同文件格式的转换。你还可能需要对声音数据进行许多不同的操作(如混音、回响、均衡、产生模拟立体声效果),所以你还需要不断增加模块来执行这些操作。一下是你的程序包的可能的结构(用一个分层文件系统表示):

  Sound/ 顶层包 __init__.py 初始化音响包 Formats/ 用于文件格式转换的子程序包 __init__.py wavread.py wavwrite.py aiffread.py aiffwrite.py auread.py auwrite.py ... Effects/ 用于音响效果的子程序包 __init__.py echo.py surround.py reverse.py ... Filters/ 用于滤波的子程序包 __init__.py equalizer.py vocoder.py karaoke.py ...  

包目录中的“__init__.py”文件是必须得,用来指示Python把这个目录看成包,这可以防止有相同名字如“string”的子目录掩盖住在搜索路径后面一些出现的模块定义。在最简单的情况下,“__init__.py”可以是一个空文件,它也可以包含初始化包所需的代码,和设置“__all__”变量,这些后面会加以讨论。

包的用户可以从包中导入单独的模块,如:

import Sound.Effects.echo

这可以把子模块Sound.Effects.echo导入。要引用它也必须用全名,例如:

Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)

导入子模块的另一种办法是:

from Sound.Effects import echo

这同样也导入子模块echo,但调用时不需写包前缀,所以可以用如:

echo.echofilter(input, output, delay=0.7, atten=4)

另外一种写法是直接导入所需的函数或变量:

from Sound.Effects.echo import echofilter

这一次同样是调入了子模块echo,但是使其函数echofilter直接可用:

echofilter(input, output, delay=0.7, atten=4)

注意使用“from 包 import 项”这样的格式时,导入的项可以是包的一个子模块(或子包),也可以是包内定义的其它名字如函数、类、变量。导入语句首先查找包内是否定义了所需的项,如果没有则假设它是一个模块然后调入。如果找不到,结果引起ImportError。

相反的,当使用“import item.subitem.subsubitem”这样的格式时,除最后一个外其它各项都应该是包,最后一项可以是包也可以是模块,不允许是前面一项内部定义的类、函数或变量。

6.4.1 从包中导入*

现在,如果用户写“from Sound.Effects import *”会发生什么情况?理想情况下我们希望这应该扫描文件系统,找到所有包内的子模块并把它们都导入进来。不幸的是这种操作在Mac和Windows平台上不能准确实现,这两种操作系统对文件名的大小写没有准确信息。在这些平台上,不知道名为“ECHO.PY”的文件会作为模块echo、Echo还是ECHO被导入。(例如,Windows 95在显示文件名时总是讨厌地把第一个字母大写)。DOS的8+3文件名限制更是对长模块名造成了有趣的困难。

这个问题的唯一解决办法是由模块作者显式地提供包的索引。引入*的import语句遵循如下规定:如果包的“__init__.py”文件定义了一个名为“__all__”的列表,这个列表就作为从包内导入*时要导入的所有模块的名字表。因此当包的新版本发布时需要包的作者确保这个列表是最新的。包的作者如果认为不需要导入*的话也可以不支持这种用法。例如,文件Sounds/Effects/__init__.py 可以包含如下代码:

  __all__ = ["echo", "surround", "reverse"] 这意味着from Sound.Effects import *将从Sound包中导入指定的三个子包。 

如果没有定义__all__,则from Sound.Effects import *语句不会导入Sound.Effects包中的所有子模块;此语句只能保证Sound.Effects被导入(可能是执行其初始化代码“__init__.py ”)并导入包中直接定义的名字。这包括由“__init__.py”定义的任何名字和显式导入的子模块名。这也包括模块中已经在前面用import显式地导入的子模块,例如:

  import Sound.Effects.echo import Sound.Effects.surround from Sound.Effects import *  

在这个例子中,echo和surround模块被导入当前名字空间,因为它们在执行from...import 语句时已定义(在定义了__all__的情况下这一点也是成立的)。

注意用户应尽量避免使用从模块或包中导入*的做法,因为这样经常导致可读性差的代码。尽管如此,在交互运行时可以用导入*的办法节省敲键次数,而且有些模块在设计时就考虑到了这个问题,它们只输出遵循某种约定的名字。注意,from 包 import 特定子模块的用法并没有错,实际上这还是我们推荐的用法,除非程序还需要用到来自其它包的同名的子模块。

6.4.2 包内部引用

子模块常常需要彼此引用。例如,模块surround可能要用到模块echo。事实上,这样的引用十分常见,所以import语句首先从子模块的所在包中寻找要导入的子模块才在标准模块搜索路径查找。所以,模块surround只要写import echo或from echo import echofilter。如果在包含本模块的包中没有找到要导入的模块,import语句将去寻找指定名字的顶级模块。

当包组织成子包时(比如例中的Sound包),没有一种简单的办法可以引用兄弟包中的子模块――必须使用子模块的全名。例如,如果模块Sound.Filters.vocoder要引用Sound.Effects 包中的echo模块,它可以用Sound.Effects import echo。

第七章 输入输出

有几种办法可以从程序输出;数据可以用可读的形式显示,或保存到文件中以备日后使用。本章讨论一些输入输出的办法。

7.1 输出格式控制

到现在为止我们已经看到了两种输出值的方法:表达式语句和print语句。(第三种方法是使用文件对象的write()方法,标准输出文件可以用sys.stdout引用。参见库参考手册)。

我们常常需要控制输出格式,而不仅仅是显示空格分开的值。有两种办法控制输出格式:一种办法是自己进行字符串处理,用字符串的片断和合并操作可以产生任何可以想象的格式。标准模块string包含了诸如把字符串填充到指定的列宽这样的有用操作,后面会有提及。

另一种办法是使用%运算符,此运算符以一个字符串为左运算元,它按C的sprintf()函数格式把右运算元转换为字符串,返回转换结果。

 问题是:如何把值转换为字符串?

幸运的是,Python有一种办法可以把任何值转换为字符串:使用repr()函数,或把值写在两个反向引号(``)之间。例如:

>>> x = 10 * 3.14 >>> y = 200*200 >>> s = 'The value of x is ' + `x` + ', and y is ' + `y` + '...' >>> print s The value of x is 31.4, and y is 40000... >>> # 反向引号也适用于非数值型 ... p = [x, y] >>> ps = repr(p) >>> ps '[31.4, 40000]' >>> # 转换字符串对字符串加字符串引号和反斜杠 ... hello = 'hello, world\n' >>> hellos = `hello` >>> print hellos 'hello, world\012' >>> # 反向引号内可以是一个序表 ... `x, y, ('spam', 'eggs')` "(31.4, 40000, ('spam', 'eggs'))"

下面是两种写出平方、立方表的方法:

>>> import string >>> for x in range(1, 11): ... print string.rjust(`x`, 2), string.rjust(`x*x`, 3), ... # 前一行的结尾逗号表示不换行 ... print string.rjust(`x*x*x`, 4) ... 1 1 1 2 4 8 3 9 27 4 16 64 5 25 125 6 36 216 7 49 343 8 64 512 9 81 729 10 100 1000 >>> for x in range(1,11): ... print'%2d %3d %4d' % (x, x*x, x*x*x) ... 1 1 1 2 4 8 3 9 27 4 16 64 5 25 125 6 36 216 7 49 343 8 64 512 9 81 729 10 100 1000

注意print输出的各项之间额外加了一个空格,这是print的规定。 

此例显示了函数string.rjust()的用法,此函数可以把一个字符串放进指定宽度右对齐,左边用空格填充。类似函数还有string.ljust()和string.center()。这些函数不向外输出,只是返回转换后的字符串。如果输入字符串太长也不会被截断而是被原样返回。这样的处理可能会使你的列对齐失效,但这可能比截断要好一些,截断的结果是我们看到一个错误的值。(如果你确实需要截断的话总可以再加一层片断,如string.ljust(x,n)[0:n])。

 还有一个函数string.zfill(),可以在数值左边填零。此函数可以处理带有加减号的情况:

>>> string.zfill('12', 5) '00012' >>> string.zfill('-3.14', 7) '-003.14' >>> string.zfill('3.14159265359', 5) '3.14159265359'

%操作符的用法如下例:

>>> import math >>> print 'The value of PI is approximately %5.3f.' % math.pi The value of PI is approximately 3.142.

如果有多个值可以用一个序表给出,这时格式字符串中要有多个格式,如:

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678} >>> for name, phone in table.items(): ... print'%-10s ==> %10d' % (name, phone) ... Jack ==> 4098 Dcab ==> 8637678 Sjoerd ==> 4127

大多数格式与C用法相同,要求要输出的值的类型符合格式的需要。但是,如果你没有引发例外错误的话也不会产生内核堆列。Python的%s格式要宽得多:如果相应的输出项不是字符串对象,就先用str()内置函数把它变成字符串。在格式指定中宽度指定为*号表示后面的输出项中多出一个指定宽度的整数。C格式%n和%p未被支持。

如果你有一个长格式串不想把它分开,可以在指定格式的地方指定名字,这样就不需要按次序去把格式和名字对应起来,这种格式为“%(变量名)格式”,例如:

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678} >>> print 'Jack: %(Jack)d; Sjoerd: %(Sjoerd)d; Dcab: %(Dcab)d' % table Jack: 4098; Sjoerd: 4127; Dcab: 8637678

这里输出项总是一个字典,字典的各项值是要输出的值,字典的键值是各值的名字。这种输出格式经常与内置函数var()配合使用,var()返回包含所有局部变量的字典。

7.2 读写文件

  open()打开一个文件对象,经常使用两个参数:“open(文件名,模式)”。例如:

>>> f=open('/tmp/workfile', 'w') >>> print f

第一自变量是一个包含了文件名的字符串,第二自变量是文件打开方式的字符串。模式‘r ’表示读取,‘w’表示只写(已有的同名文件被清除),‘a’表示打开文件在尾部添加, ‘r+’表示打开文件既可以读也可以写。打开方式参数可选,缺省为‘r’模式。

在Windows和Macintosh中在模式中加入‘b’表示以二进制格式打开文件,如‘rb’、‘wb ’、‘r+b’。Windows对文本文件和二进制文件有不同的处理,文本文件中的换行字符在读写时有变化。这种对文件数据的幕后的修改不影响ASCII文本文件,但是会破坏二进制数据如JPEG 或“.EXE”文件的数据。读写这样的文件一定要使用二进制格式。(Macintosh中文本模式的精确描述依赖于使用的C库)。

7.2.1 文件对象的方法

 本节后面的例子假设已经建立了一个名为f的文件对象。

为了读取文件内容,调用f.read(size),可以读入一定字节数的数据返回为一个字符串。size 是一个可选数值参数,省略size或size取负值时读入整个文件并返回为一个字符串;如果文件比你的机器内存大一倍,那是你的问题。指定了正的size的时候之多读入并返回size字节。如果读到了文件尾,f.read()返回一个空串("")。如:

  >>> f.read() 'This is the entire file.\012' >>> f.read() ''  

f.readline()从文件中读入一行,返回的字符串中将包括结尾的一个换行符(\n),如果文件的最后一行没有换行符则由该行读入的字符串也没有结尾的换行符。这样,由readline() 返回的结果不会有歧义,读入结果为空串时一定是到了文件尾,读入一个'\n'时为空行。

>>> f.readline() 'This is the first line of the file.\012' >>> f.readline() 'Second line of the file\012' >>> f.readline() ''

f.readlines()反复调用f.readline(),返回一个包含文件所有行的列表。

>>> f.readlines() ['This is the first line of the file.\012', 'Second line of the file\012'] f.write(string)把string的内容写入到文件中,返回None。   >>> f.write('This is a test\n')  

f.tell()返回文件对象的当前读写为止,按从文件开始的字节数算。为了改变读写位置,使用“f.seek(位移,从哪里)”。读写位置按一个参考点加上位移来计算,参考点用“从那里”参数指定,取0时从文件头开始算,取1时按当前位置算,取2时从文件尾算。缺省值是0 ,从文件开始算。

>>> f=open('/tmp/workfile', 'r+') >>> f.write('0123456789abcdef') >>> f.seek(5) # 从文件头前进5个字节,到达第6个字符 >>> f.read(1) '5' >>> f.seek(-3, 2) # 转到结尾前3个字符 >>> f.read(1) 'd'

用外一个文件后调用f.close()关闭文件,释放打开文件所占用的系统资源。文件关闭后再使用此文件对象就无效了。

>>> f.close() >>> f.read() Traceback (innermost last): File "", line 1, in ? ValueError: I/O operation on closed file

文件对象还有其它一些不太常用的方法,例如isatty()和truncate(),参见库参考手册。 

7.2.2 pickle模块

字符串可以很容易地从文件读入或向文件写出。读入数值要麻烦一些,因为read()方法总是返回字符串,要把读入的字符串传给象string.atoi()这样的函数,把象‘123’这样的字符串转换为对应的整数值123。但是,当你想保存更复杂的数据类型如列表、字典或类实例时,读写就要复杂得多。

Python的设计使程序员可以不必反复编写调试保存复杂数据类型的代码,它提供了一个叫做pickle的标准模块。这个令人惊异的模块可以把几乎任何Python对象转换为字符串表示,这个过程叫做腌制,从对象的字符串表示恢复对象叫做恢复。在腌制和反腌制之间,对象的字符串表示可以保存在文件或数据中,甚至于通过网络连接传送到远程计算机上。

 如果你有一个对象x,有一个可写的文件对象f,最简单的腌制对象的办法是下面一行代码:

pickle.dump(x, f)

为了恢复对象,如果刚才的文件已打开用于读取,文件对象名仍为f,则:

x = pickle.load(f)

(腌制和恢复还有其它用法,可以腌制多个对象,可以不把数据写入文件,详见库参考手册)。

pickle是保存Python对象并被其它程序或同一程序以后再运行时调用的标准办法,这种做法的专用术语叫做“持久对象”。因为pickle使用广泛,许多Python扩展模块的作者都留意使新增加的数据类型如矩阵可以正确地腌制和恢复。

第八章 错误与例外

到现在为止我们只是提到了错误信息而没有详细讨论,如果你运行了前面的例子可能已经看到了一些错误信息。至少有两种不同错误:句法错和例外错(exceptions)。

8.1 句法错

 句法错也称为语法分析错,是你在学习Python的时候最可能犯的错误。

>>> while 1 print 'Hello world' File "", line 1 while 1 print 'Hello world' ^ SyntaxError: invalid syntax

语法分析器重复出错行,并用一个小‘箭头’指向行内最早发现错误的位置。错误是由箭头前面的记号引起的(至少是在这里检测到的)。在本例中,错误在关键字print处检测到,因为它前面应该有一个冒号(“:”)。错误信息中显示了文件名和行号这样如果错误发生在一个脚本文件中你就知道到哪里去找。

8.2 例外

即使语句或表达式句法没有问题,在试图运行的时候也可能发生错误。运行时检测到的错误叫做例外,这种错误不一定必然是致命的:你很快就会学到如何在Python程序中处理例外。然而,多数例外不能被程序处理,这是会产生错误信息,如:

>>> 10 * (1/0) Traceback (innermost last): File "", line 1 ZeroDivisionError: integer division or modulo >>> 4 + spam*3 Traceback (innermost last): File "", line 1 NameError: spam >>> '2' + 2 Traceback (innermost last): File "", line 1 TypeError: illegal argument type for built-in operation

错误信息的最后一行显示发生的情况。例外有不同的类型,类型作为错误信息的一部分显示:上例中错误的类型有ZeroDivisionError、NameError和TypeError。作为例外类型显示的字符串是发生的例外的内置名。这对于所有内置例外成立,但对用户自定义例外不一定成立(用户最好能遵守这样的约定)。标准例外名是内置的标识符(不是保留关键字)。

此行的其余部分是错误的细节,其解释依赖于例外类型。错误信息前面的部分以堆栈反跟踪的形式显示了发生错误的上下文环境。一般这包含了列出源代码行的一个列出源程序行的堆栈反跟踪;然而,它不会显示从标准输入读进的行。

 库参考手册列出了内置例外和其含义。 

8.3 例外处理

 可以编程序来处理选定的例外。请看下面的例子,显示一些浮点数的倒数:

>>> numbers = [0.3333, 2.5, 0, 10] >>> for x in numbers: ... print x, ... try: ... print 1.0 / x ... except ZeroDivisionError: ... print '*** has no inverse ***' ... 0.3333 3.00030003 2.5 0.4 0 *** has no inverse *** 10 0.1

try语句是这样工作的:

  • 首先,运行try子句(在try和except之间的语句)。
  • 如果没有发生例外,跳过except子句,try语句运行完毕。
  • 如果在try子句中发生了例外错误而且例外错误匹配except后指定的例外名,则跳过try 子句剩下的部分,执行except子句,然后继续执行try语句后面的程序。
  • 如果在try子句中发生了例外错误但是例外错误不匹配except后指定的例外名,则此例外被传给外层的try语句。如果没有找到匹配的处理程序则此例外称作是未处理例外,程序停止运行,显示错误信息。

try语句可以有多个except子句,为不同的例外指定不同处理。至多只执行一个错误处理程序。错误处理程序只处理相应的try子句中发生的例外,如果同try语句中其它的错误处理程序中发生例外错误处理程序不会反应。一个except子句可以列出多个例外,写在括号里用逗号分开,例如:

... except (RuntimeError, TypeError, NameError): ... pass

最后一个except子句可以省略例外名,作为一个通配项。这种方法要谨慎使用,因为这可能会导致程序实际已出错却发现不了。

try ... except语句有一个可选的else子句,如有的话要放在所有except子句之后。else 的意思是没有发生例外,我们可以把try子句中没有发生例外时要做的事情放在这个子句里。例如:

for arg in sys.argv[1:]: try: f = open(arg, 'r') except IOError: print '不能打开', arg else: print arg, '有', len(f.readlines()), '行' f.close()

例外发生时可能伴有一个值,叫做例外的参数。参数是否存在及其类型依赖于例外的类型。对于有参数的例外,except在自居可以在例外名(或表)后指定一个变量用来接受例外的参数值,如:

>>> try: ... spam() ... except NameError, x: ... print 'name', x, 'undefined' ... name spam undefined

有参数的例外未处理时会在错误信息的最后细节部分列出其参数值。 

例外处理程序不仅处理直接产生于try子句中的例外,也可以处理try子句中调用的函数(甚至是间接调用的函数)中的例外。如:

>>> def this_fails(): ... x = 1/0 ... >>> try: ... this_fails() ... except ZeroDivisionError, detail: ... print 'Handling run-time error:', detail ... Handling run-time error: integer division or modulo 8.4 产生例外

raise语句允许程序员强行产生指定的例外。例如:

>>> raise NameError, 'HiThere' Traceback (innermost last): File "", line 1 NameError: HiThere

raise语句的第一个参数指定要产生的例外的名字。可选的第二参数指定例外的参数。 

8.5 用户自定义例外

程序中可以定义自己的例外,只要把一个字符串赋给一个变量即可。例如:

>>> my_exc = 'my_exc' >>> try: ... raise my_exc, 2*2 ... except my_exc, val: ... print 'My exception occurred, value:', val ... My exception occurred, value: 4 >>> raise my_exc, 1 Traceback (innermost last): File "", line 1 my_exc: 1

许多标准模块用这种方法报告自己定义的函数中发生的错误。 

8.6 定义清理动作

  try语句还有另一个finally可选子句,可以用来规定不论出错与否都要执行的动作。例如:

>>> try: ... raise KeyboardInterrupt ... finally: ... print 'Goodbye, world!' ... Goodbye, world! Traceback (innermost last): File "", line 2 KeyboardInterrupt

finally子句不论try子句中是否发生例外都会执行。例外发生时,先执行finally子句然后重新提出该例外。当try语句用break或return语句退出时也将执行finally子句。

要注意的是,try语句有了except子句就不能有finally子句,有了finally子句就不能有except 子句,不能同时使用except子句和finally子句。需要的话可以嵌套。

第九章 类

Python是一个真正面向对象的语言,它只增加了很少的新语法就实现了类。它的类机制是C++ 和Modula-3的类机制的混合。Python的类并不严格限制用户对定义的修改,它依赖于用户自觉不去修改定义。然而Python对类最重要的功能都保持了完全的威力。类继承机制允许多个基类的继承,导出类可以重载基类的任何方法,方法可以调用基类的同名方法。对象可以包含任意多的私有数据。

用C++术语说,所有类成员(包括数据成员)是公用的,所有成员函数是虚拟(virtual)的。没有特别的构建函数或销毁函数(destructor)。如同在Modula-3中一样,从对象的方法中要引用对象成员没有简捷的办法:方法函数的必须以对象作为第一个参数,而在调用时则自动提供。象在Smalltalk中一样,类本身也是对象,实际上这里对象的含义比较宽:在Python 中所有的数据类型都是对象。象在C++或Modula-3中一样,内置类型不能作为基类由用户进行扩展。并且,象C++但不象Modula-3,多数有特殊语法的内置函数(如算术算符、下标等)可以作为类成员重定义。

9.1 关于术语

Python的对象概念比较广泛,对象不一定非得是类的实例,因为如同C++和Modula-3而不同于Smalltalk,Python的数据类型不都是类,比如基本内置类型整数、列表等不是类,甚至较古怪的类型如文件也不是类。然而,Python所有的数据类型都或多或少地带有一些类似对象的语法。

对象是有单独身份的,同一对象可以有多个名字与其联系,这在其他语言中叫做别名。这样做的好处乍一看并不明显,而且对于非可变类型(数字、字符串、序表(tuple))等没有什么差别。但是别名句法对于包含可变对象如列表、字典及涉及程序外部物件如文件、窗口的程序有影响,这可以有利于程序编制,因为别名有些类似指针:比如,传递一个对象变得容易,因为这只是传递了一个指针;如果一个函数修改了作为参数传递来的对象,修改结果可以传递回调用处。这样就不必象Pascal那样使用两种参数传递机制。

9.2 Python作用域与名字空间

在引入类之前,我们必须讲一讲Python的作用域规则。类定义很好地利用了名字空间,需要了解Python如何处理作用域和名字空间才能充分理解类的使用。另外,作用域规则也是一个高级Python程序员必须掌握的知识。

 先给出一些定义。

名字空间是从名字到对象的映射。多数名字空间目前是用Python字典类型实现的,不过这一点一般是注意不到的,而且将来可能会改变。下面是名字空间的一些实例:Python中内置的名字(如abs()等函数,以及内置的例外名);模块中的全局名;函数调用中的局部变量名。在某种意义上一个对象的所有属性也构成了一个名字空间。关于名字空间最重要的事要知道不同名字空间的名字没有任何联系;例如,两个不同模块可能都定义了一个叫“maximize ”的函数而不会引起混乱,因为模块的用户必须在函数名之前加上模块名作为修饰。

另外,在Python中可以把任何一个在句点之后的名字称为属性,例如,在表达式z.real中,real是一个对象z的属性。严格地说,对模块中的名字的引用是属性引用:在表达式modname.funcname 中,modname是一个模块对象,funcname是它的一个属性。在这种情况下在模块属性与模块定义的全局名字之间存在一个直接的映射:它们使用相同的名字空间!

属性可以是只读的也可以是可写的。在属性可写的时候,可以对属性赋值。模块属性是可写的:你可以写“modname.the_answer = 42”。可写属性也可以用del语句闪出,如“del modname.the_answer”。

名字空间与不同时刻创建,有不同的生存周期。包含Python内置名字的名字空间当Python 解释程序开始时被创建,而且不会被删除。模块的全局名字空间当模块定义被读入时创建,一般情况下模块名字空间也一直存在到解释程序退出。由解释程序的最顶层调用执行的语句,不论是从一个脚本文件读入的还是交互输入的,都属于一个叫做__main__的模块,所以也存在于自己的全局名字空间之中。(内置名字实际上也存在于一个模块中,这个模块叫做__builtin__ )。

函数的局部名字空间当函数被调用时创建,当函数返回或者产生了一个不能在函数内部处理的例外时被删除。(实际上,说是忘记了这个名字空间更符合实际发生的情况。)当然,递归调用在每次递归中有自己的局部名字空间。

一个作用域是Python程序中的一个文本区域,其中某个名字空间可以直接访问。“直接访问” 这里指的是使用不加修饰的名字就直接找到名字空间中的对象。

虽然作用域是静态定义的,在使用时作用域是动态的。在任何运行时刻,总是恰好有三个作用域在使用中(即恰好有三个名字空间是直接可访问的):最内层的作用域,最先被搜索,包含局部名字;中层的作用域,其次被搜索,包含当前模块的全局名字;最外层的作用域最后被搜索,包含内置名字。

一般情况下,局部作用域引用当前函数的局部名字,其中局部是源程序文本意义上来看的。在函数外部,局部作用域与全局作用域使用相同的名字空间:模块的名字空间。类定义在局部作用域中又增加了另一个名字空间。

一定要注意作用域是按照源程序中的文本位置确定的:模块中定义的函数的全局作用域是模块的名字空间,不管这个函数是从哪里调用或者以什么名字调用的。另一方面,对名字的搜索却是在程序运行中动态进行的,不过,Python语言的定义也在演变,将来可能发展到静态名字解析,在“编译”时,所以不要依赖于动态名字解析!(实际上,局部名字已经是静态确定的了)。

Python的一个特别之处是赋值总是进入最内层作用域。关于删除也是这样:“del x”从局部作用域对应的名字空间中删除x的名字绑定(注意在Python中可以多个名字对应一个对象,所以删除一个名字只是删除了这个名字与其对象间的联系而不一定删除这个对象。实际上,所有引入新名字的操作都使用局部作用域:特别的,import语句和函数定义把模块名或函数名绑定入局部作用域。(可以使用global语句指明某些变量是属于全局名字空间的)。

9.3 初识类

 类引入了一些新语法,三种新对象类型,以及一些新的语义。

9.3.1 类定义语法

 类定义的最简单形式如下:

class 类名: <语句-1> . . . <语句-N>

如同函数定义(def语句)一样,类定义必须先执行才能生效。(甚至可以把类定义放在if 语句的一个分支中或函数中)。在实际使用时,类定义中的语句通常是函数定义,其它语句也是允许的,有时是有用的――我们后面会再提到这一点。类内的函数定义通常具有一种特别形式的自变量表,专用于方法的调用约定――这一点也会在后面详细讨论。

进入类定义后,产生了一个新的名字空间,被用作局部作用域――于是,所有对局部变量的赋值进入这个新名字空间。特别地,函数定义把函数名与新函数绑定在这个名字空间。

当函数定义正常结束(从结尾退出)时,就生成了一个类对象。这基本上是将类定义生成的名字空间包裹而成的一个对象;我们在下一节会学到类对象的更多知识。原始的局部作用域(在进入类定义之前起作用的那个)被恢复,类对象在这里被绑定到了类对象定义头部所指定的名字。

9.3.2 类对象

类对象支持两种操作:属性引用和实例化。属性引用的格式和Python中其它的属性引用格式相同,即obj.name。有效的属性名包括生成类对象时的类名字空间中所有的名字。所以,如果象下面这样定义类:

class MyClass: "A simple example class" i = 12345 def f(x): return 'hello world'

则MyClass.i和MyClass.f都是有效的属性引用,分别返回一个整数和一个函数对象。也可以对类属性赋值,所以你可以对MyClass.i赋值而改变该属性的值。

__doc__也是一个有效的属性,它是只读的,返回类的文档字符串:“A simple example class”。

类实例化使用函数记号。只要把这个类对象看成是一个没有自变量的函数,返回一个类实例。例如(假设使用上面的类):

x = MyClass()

可以生成该类的一个新实例并把实例对象赋给局部变量x。 

9.3.3 实例对象

 我们如何使用实例对象呢?类实例只懂得属性引用这一种操作。有两类有效的属性。 

第一类属性叫做数据属性。数据属性相当于Smalltalk中的“实例变量”,和C++中的“数据成员”。数据成员不需要声明,也不需要在类定义中已经存在,象局部变量一样,只要一赋值它就产生了。例如,如果x是上面的MyClass类的一个实例,则下面的例子将显示值16而不会留下任何痕迹:

x.counter = 1 while x.counter < 10: x.counter = x.counter * 2 print x.counter del x.counter

类实例能理解的第二类属性引用是方法。方法是“属于”一个对象的函数。(在Python中,方法并不是只用于类实例的:其它对象类型也可以有方法,例如,列表对象也有append、insert 、remove、sort等方法。不过,在这里除非特别说明我们用方法来特指类实例对象的方法)。

类对象的有效方法名依赖于它的类。按照定义,类的所有类型为函数对象属性定义了其实例的对应方法。所以在我们的例子y,x.f是一个有效的方法引用,因为MyClass是一个函数;x.i 不是方法引用,因为MyClass.i不是。但是x.f和MyClass.f不是同一个东西――x.f是一个方法对象而不是一个函数对象。

9.3.4 方法对象

 方法一般是直接调用的,例如:

x.f()

在我们的例子中,这将返回字符串‘hello world’。然而,也可以不直接调用方法:x.f 是一个方法对象,可以把它保存起来再调用。例如:

xf = x.f while 1: print xf()

会不停地显示“hello world”。 

调用方法时到底发生了什么呢?你可能已经注意到x.f()调用没有自变量,而函数f在调用时有一个自变量。那个自变量是怎么回事?Python如果调用一个需要自变量的函数时忽略自变量肯定会产生例外错误――即使那个自变量不需要用到……

实际上,你可以猜出答案:方法与函数的区别在于对象作为方法的第一个自变量自动传递给方法。在我们的例子中,调用x.f()等价于调用MyClass.f(x)。一般地,用n个自变量的去调用方法等价于把方法所属对象插入到第一个自变量前面以后调用对应函数。

如果你还不理解方法是如何工作的,看一看方法的实现可能会有所帮助。在引用非数据属性的实例属性时,将搜索它的类。如果该属性名是一个有效的函数对象,就生成一个方法对象,把实例对象(的指针)和函数对象包装到一起:这就是方法对象。当方法对象用一个自变量表调用时,它再被打开包装,由实例对象和原自变量表组合起来形成新自变量表,用这个新自变量表调用函数。

9.4 一些说明

在名字相同时数据属性会覆盖方法属性;为了避免偶然的名字冲突,这在大型程序中会造成难以查找的错误,最好按某种命名惯例来区分方法名和数据名,例如,所有方法名用大写字母开头,所有数据属性名前用一个唯一的字符串开头(或者只是一个下划线),或方法名用动词而数据名用名词。

数据属性可以被方法引用也可以被普通用户(“客户”)引用。换句话说,类不能用来构造抽象数据类型。实际上,Python中没有任何办法可以强制进行数据隐藏——这些都是基于惯例。(另一方面,Python的实现是用C写的,它可以完全隐藏实现细节,必要时可以控制对象存取;用C写的Python扩展模块也有同样特性)。

客户要自己小心使用数据属性——客户可能会因为随意更改类对象的数据属性而破坏由类方法维护的类数据的一致性。注意客户只要注意避免名字冲突可以任意为实例对象增加新数据属性而不需影响到方法的有效性——这里,有效的命名惯例可以省去许多麻烦。

从方法内要访问本对象的数据属性(或其它方法)没有一个简写的办法。我认为这事实上增加了程序的可读性:在方法定义中不会混淆局部变量和实例变量。

习惯上,方法的第一自变量叫做self。这只不过是一个习惯用法:名字self在Python中没有任何特殊意义。但是,因为用户都使用此惯例,所以违背此惯例可能使其它Python程序员不容易读你的程序,可以想象某些类浏览程序会依赖于此惯例)。

作为类属性的任何函数对象都为该类的实例定义一个方法。函数的定义不一定必须在类定义内部:只要在类内把一个函数对象赋给一个局部变量就可以了。例如:

# Function defined outside the class def f1(self, x, y): return min(x, x+y)   class C: f = f1 def g(self): return 'hello world' h = g

现在f、g和h都是类C的属性且指向函数对象,所以它们都是C的实例的方法——其中h与g 完全等价。注意我们应该避免这种用法以免误导读者。

 方法可以用代表所属对象的self自变量来引用本类其它的方法,如:

class Bag: def empty(self): self.data = [] def add(self, x): self.data.append(x) def addtwice(self, x): self.add(x) self.add(x)

实例化操作(“调用”一个类对象)生成一个空对象。许多类要求生成具有已知初识状态的类。为此,类可以定义一个特殊的叫做__init__()的方法,如:

def __init__(self): self.empty()

一个类定义了__init__()方法以后,类实例化时就会自动为新生成的类实例调用调用__init__() 方法。所以在Bag例子中,可以用如下程序生成新的初始化的实例:

x = Bag()

当然,__init__()方法可以有自变量,这样可以实现更大的灵活性。在这样的情况下,类实例化时指定的自变量被传递给__init__()方法。例如:

>>> class Complex: ... def __init__(self, realpart, imagpart): ... self.r = realpart ... self.i = imagpart ... >>> x = Complex(3.0,-4.5) >>> x.r, x.i (3.0, -4.5)

方法可以和普通函数一样地引用全局名字。方法的全局作用域是包含类定义的模块。(注意类本身并不被用作全局作用域!)虽然我们很少需要在方法中使用全局数据,全局作用域还是有许多合法的用途:例如,导入全局作用域的函数和模块可以被方法使用,在同一模块中定义的函数和方法也可以被方法使用。包含此方法的类一般也在此全局作用域中定义,下一节我们会看到一个方法为什么需要引用自己的类!

9.5 继承

当然,一个语言如果不支持继承就谈不到“类”。导出类的定义方法如下:

class 导出类名(基类名): <语句-1> . . . <语句-N>

其中“基类名”必须在包含导出类定义的作用域中有定义。除了给出基类名外,还可以给出一个表达式,在基类定义于其它模块中时这是有用的,如:

class 导出类名 (模块名.基类名):

导出类定义的运行和基类运行的方法是一样的。生成类对象是,基类被记忆。这用于解决属性引用:如果类中未找到要求的属性就到基类中去查找。如果基类还有基类的话这个规则递归地应用到更高的类。

导出类在实例化时没有任何特殊规则。“导出类名()”产生该类的一个新实例。方法引用这样解决:搜索相应类属性,如果必要的话逐级向基类查找,如果找到了一个函数对象就是有效的方法引用。

导出类可以重写基类的方法。因为方法在调用同一对象的其它方法时并无任何特殊权限,如果基类中某一方法调用同一基类的另一方法,在导出类中该方法调用的就可能是已经被导出类重写后的方法了。(对C++程序员而言:Python中所有方法都是“虚拟函数”)。

导出类中重写的方法可能是需要扩充基类的同名方法而不是完全代替原来的方法。导出类调用基类同名方法很简单:“基类名.方法名(self, 自变量表)”。对类用户这种做法偶尔也是有用的。(注意只有基类在同一全局作用域定义或导入时才能这样用)。

8.5.1 多重继承

Python也支持有限的多重继承。有多个基类的类定义格式如下:

class 导出类名 (基类1, 基类2, 基类3): <语句-1> . . . <语句-N>

关于多重继承只需要解释如何解决类属性引用。类属性引用是深度优先,从左向右进行的。所以,如果在导出类定义中未找到某个属性,就先在基类1中查找,然后(递归地)在基类1 的基类中查找,如果都没有找到,就在基类2中查找,如此进行下去。

(对某些人来说宽度优先——先在基类2和基类3中查找再到基类1的基类中查找——看起来更自然。然而,这需要你在确定基类1与基类2的属性冲突时明确知道这个属性是在基类1本身定义还是在其基类中定义。深度优先规则不区分基类1的一个属性到底是直接定义的还是继承来的)。

很显然,如果不加约束地使用多重继承会造成程序维护的恶梦,因为Python避免名字冲突只靠习惯约定。多重继承的一个众所周知的问题是当导出类有两个基类恰好从同一个基类导出的。尽管很容易想到这种情况的后果(实例只有一份“实例变量”或数据属性被共同的基类使用),但是这种做法有什么用处却是不清楚的。

9.6 私有变量

Python对私有类成员有部分支持。任何象__spam这样形式的标识符(至少有两个前导下划线,至多有一个结尾下划线)目前被替换成_classname__spam,其中classname是所属类名去掉前导下划线的结果。这种搅乱不管标识符的语法位置,所以可以用来定义类私有的实例、变量、方法,以及全局变量,甚至于保存对于此类是私有的其它类的实例。如果搅乱的名字超过255个字符可能会发生截断。在类外面或类名只有下划线时不进行搅乱。

名字搅乱的目的是给类一种定义“私有”实例变量和方法的简单方法,不需担心它的其它类会定义同名变量,也不怕类外的代码弄乱实例的变量。注意搅乱规则主要是为了避免偶然的错误,如果你一定想做的话仍然可以访问或修改私有变量。这甚至是有用的,比如调试程序要用到私有变量,这也是为什么这个漏洞没有堵上的一个原因。(小错误:导出类和基类取相同的名字就可以使用基类的私有变量)。

注意传递给exec,eval()或evalfile()的代码不会认为调用它们的类的类名是当前类,这与global语句的情况类似,global的作用局限于一起字节编译的代码。同样的限制也适用于getattr() ,setattr()和delattr(),以及直接访问__dict__的时候。

下面例子中的类实现了自己的__getattr__和__setattr__方法,把所有属性保存在一个私有变量中,这在Python的新旧版本中都是可行的:

class VirtualAttributes: __vdict = None __vdict_name = locals().keys()[0] def __init__(self): self.__dict__[self.__vdict_name] = {} def __getattr__(self, name): return self.__vdict[name] def __setattr__(self, name, value): self.__vdict[name] = value 9.7 补充

有时我们希望有一种类似Pascal的“record”或C的“struct”的类型,可以把几个有名的数据项组合在一起。一个空类可以很好地满足这个需要,如:

class Employee: pass   john = Employee() # 生成一个空职员记录   # 填充记录的各个域 john.name = 'John Doe' john.dept = 'computer lab' john.salary = 1000

一段需要以某种抽象数据类型作为输入的Python程序经常可以接受一个类作为输入,该类只是模仿了应输入的数据类型的方法。例如,如果你有一个函数是用来格式化一个文件对象中的数据,就可一个定义一个具有方法read()和readline()的类,该类可以不从文件输入而是从一个字符串缓冲区输入,把这个类作为自变量。

 实例方法对象也有属性:m.im_self是方法所属的实例,m.im_func是方法对应的函数对象。 

9.7.1 例外可以是类

用户自定义的例外除了可以是字符串对象以外还可以是类。这样可以定义可扩充的分层的类例外结构。

raise语句有两种新的有效格式:

raise 类, 实例   raise 实例

在第一种形式中,“实例”必须是“类”的实例或“类”的导出类的实例。第二种形式是

raise instance.__class__, instance

的简写。except语句除了可以列出字符串对象外也可以列出类。execpt子句中列出的类如果是发生的例外类或基类则是匹配的(反过来不对——except中如果是导出类而发生的例外属于基类时是不匹配的)。例如,下面的程序会显示B、C、D:

class B: pass class C(B): pass class D(C): pass   for c in [B, C, D]: try: raise c() except D: print "D" except C: print "C" except B: print "B"

注意如果把except子句的次序颠倒过来的话(“except B”放在最前),程序将显示B,B ,B——因为第一个匹配的except子句被引发。

当没有处理的例外是类的时候,类名显示在错误信息中,后面跟着一个冒号和一个空格,最后是实例用内置函数str()转换成字符串的结果。


阅读(3589) | 评论(0) | 转发(1) |
0

上一篇:python 1

下一篇:robocopy 简介

给主人留下些什么吧!~~