python学习
1 前言
Python是一个容易学习又功能强大的程序语言。它含有高效率的高阶数据结构,也是一个简单但是有效果的对象导向程序语言(object-oriented programming)。Python优雅的语法及动态型态识别(dynamic typing),加上直译式(intepretion)的本质,使得它成为一个在多种功能多种平台上撰写脚本(scripts)及快速开发的理想语言。
各种主要平台的Python直译器(interpreter)及延伸的标准链接库(library)都可以在 Python的网站( http://www.python.org )上免费下载及自由流传(包含原始码及二元执行档(binary form))。 在该网站上也有许多的档案及连结,包括了免费、第三者开发的Python模块、程序及工具, 以及许多的附带文件。
Python的直译器也可以很容易的延伸,可以加入新的由C或是C++(或其它可以由C呼叫的程序语言)所写的函数或是数据型态。Python也很适合用来当作其它应用程序的延伸语言(译者言:比如说用Python来延伸CAD, DBMaker等的功能)。
本教学文件将非正式的介绍给读者Python语言及系统的基本观念及特性。如果读者手边有一个Python的直译器将有助于获得实际的知识,但是本文件的解释都很充足,所以如果单纯离线阅读也是可以的。
若对于标准的对象及模块有兴趣的话,请参阅 Python Library Reference。 若是要知道正式Python语言的标准定义的话,可参考 Python Reference Manual 。 若有兴趣用C或C++写延伸的功能的话,请参考 Extending and Embedding the Python Interpreter 以及 Python/C API Reference。市面上也有许多更深入探讨Python的书籍。
本教学文件并不试图完整的介绍每一个Python的特性,甚至也不试图介绍每一个常用的功能。 相反的,本文件介绍许多Python值得认识的功能,并且让读者对这个语言的大致风貌有一个了解。 在读完此书之后,读者应该可以开始读及写Python的模块及程序,并且应该可以开始学习各种在 Python Library Reference所介绍的模块了。
2 开胃菜
如果你曾经写过大型的shell script,你大概能了解那种感觉:你想要新增加一个功能,但是这个script已经实在有够大够慢够复杂的了,或者说,你想要加入的新功能需要呼叫系统功能或是其它函数,但是这些功能/函数只有C才能呼叫。你要解决的问题好像并没有严重到要重新用C来写整个程序,或者有些问题因为要用到可变长度的字符串或是特别的数据结构(像是用排序过的文件名称组成序列(list)),用C来写实在比shell麻烦的太多,又或者是你根本不是对C很熟。
另外一个情境是这样的:也许你要使用好几个C的链接库,但是标准开发C程序的过程(写/编译/测试/重新编译)实在太花时间,你需要能快速的开发好软件。又或者你已经些好一个应用程序,这个程序可以使用一个延伸的语言来控制。你不想创造一种语言,然后还得写好这个语言的编译器,还得把这个编译器跟你的程序放在一起。
在这些情况之下,Python也许正是你所需要的语言。Python虽然简单,却是不折不扣的程序语言。对大型的程序来说,它比起shell能提供更多的结构性及支持。另外一方面,它也提供了比C语言更多的错误检查。由于Python是一个非常高阶的语言,所以它有许多内建的数据型态像是有弹性的数组及字典(dictionary)等等,如果用C来做的话得花上你大半天的时间。正是因为Python有较为一般性的数据型态,Python可以应用的范围比起awk甚或是Perl要广的很多,最起码,Python跟这些语言一样容易开发。
Python的另外一个特点就是可以将程序切成小模块,然后这些模块还可以应用在其它的程序之中。Python本身也有一个相当大的标准模块库可以让你来使用,或者当作学习Python程序设计的范例。在Python中也有内建的模块可以提供许多功能,诸如:档案I/O、系统呼叫、sockets,甚至是与Tk之类的GUI工具互动的接口。
Python是一个直译式的语言,可以省掉你在开发程序时不少编译及连结程序的时间。这个Python的直译器甚至可以交互式的使用,让你在写一些小程序来试验Python语言的特性,或是测试程序时可以写节省不少时间。你还可以用Python直译器来当作计算器呢。
Python让你可以写出非常精练及可读性高的程序。用Python写出的程序通常比用C或C++写的程序要短得多,其理由如下:
w 因为其高阶的数据型态,使得你可以用很简单的叙述(statement)就能够表达复杂的运作过程
w Python使用缩排来代替C/C++中常见的前后括号{}
w Python不需要变量或是参数的宣告
Python 是延伸性高的语言。如果你知道如何写C语言的程序的话,你很容易就可以在Python的直译器中加入新的内建函数(function)或是模块,这样做的好处是你可以让程序中关键的部分速度调到最快,或者是连结Python到binary的链接库(例如是厂商做好的图形链接库)去。一但你真的需要,你也可以把Python直译器加入到你用C写的应用程序里面去,然后Python就变成你的应用程序的延伸或是商业化的语言了。
另外一提的是,这个程序的命名由来是源自于BBC著名的节目``Monty Python's Flying Circus'',跟其它恶心的爬虫类没有任何关系。如果你的文件中要提到Monty Python的话,不但照准,而且还相当鼓励。
2.1然后呢
现在你应该对Python感到有一些兴趣了吧,接下来你将看到比较多的的细节讨论。学习语言的最好途径是使用之,赶快动手吧。
在下一章我们将讨论到如何使用Python的直译器,虽然相当的普通,但是如果你要尝试一下之后的范例的话,这是重要的基础。
本教学文件的其余部分将用许多的例子介绍Python语言的各种不同的特性,先从简单的表示式(expressions)开始,将会谈到叙述(statements)及数据型态,然后是函式(functions)及模块(module),最后会谈到较高深的观念像是例外情形(exceptions)及使用者自订的类别(user-defined classes)等等。
3 使用Python的直译器
3.1如何启动直译器
在Unix之类的操作系统上,如果有安装的话,Python直译器通常安装在 /usr/local/bin/python 这个目录中。你可能需要先在Shell中设定寻找 /usr/local/bin 目录,这样你才可以在Shell中打入以下的指令来启动Python直译器
python
你的Python直译器安装的位置是可以在安装时设定的,因此也有可能安装在其它的地方。你也许需要问你周遭的Python大师或是系统管理员才能知道正确的安装位置( /usr/local/python 是另外一个普遍的可能安装所在)。
要离开Python直译器的话,打入EOF的字符( 在Unix上是 Control-D ,在DOS及Windows上是 Control-Z 就会使得直译器离开(zero exit status)。如果行不通的话,你可以打入以下指令离开直译器: "import sys; sys.exit()".
Python直译器使用每行编辑,应该不难使用。在Unix上,也许安装Python直译器的人有安装使用GNU readline链接库的功能,这样的话你会有交互式编辑以及编辑过去数据的功能。最简单的检查你有没有这项功能的方法就是在Python的提示之下打入Control-P ,如果有哔声的话,就表示你有这项功能,你可以翻到 附录 A 去看特殊键的用法。如果你没有听到哔声,或是只出现 P 的话,就表示你没有这项功能,你得使用退格键(backspace)来清除目前所在行的字符了。
Python直译器的操作方法根Unix shell很像:当被呼叫时所连结的标准输入是tty device(终端机)的话,直译器会互动的读及执行所输入的指令。当被呼叫时加入文件名称参数或所连结的标准输入是连到档案的话,直译器就会读入并执行该档所含有的 script 。
第三种启动直译器的方法是打入以下的指令 "python -c command [arg] ..." ,这个指令会执行 command 所代表的叙述(这跟shell的 -c option很像),因为Python叙述(statement)常有空白及特殊字符,所以用此法时可以把 command 所代表的叙述用””括起来,以免跟shell的其它特殊字符或是参数有所混淆。
要注意的是 "python file" 指令跟 "python <file"指令是有所区分的。对后者而言,不单单只有执行这个script,而且程序中有关输入的需求(例如呼叫 input() 或是 raw_input() ) 也都会由这个 file 来满足。由于此file已经在程序执行之初已经被从头到尾读过一次,所以一执行这个程序将会马上就碰到了EOF。相反的对于前一个写法来说,程序的输入需求是由任何连结到Python直译器的标准输入(standard input)的装置或档案来满足的,而这个也许才是你所想要的结果。
当script档案在使用的时候,也许你会想要执行这个script然后还可以之后继续进入互动的模式。这时你可以加入 -i 这个选项。但是如同前一段所说的,如果此script是由标准输入来读进去的话就没有办法这样做了。
3.1.1 参数的传递
如果interpreter认识sys的话(译:可用“import sys”指令),script的文件名及附加传入的参数都会被纪录在 sys.argv 这个变量并并传给script来使用。sys.argv 是一列的字符串,长度至少为1,如果你什么都档案或参数都没传的话, sys.argv[0] 就是一个空字符串。如果script的名字是 '-' (就是标准输入的意思)的话, sys.argv[0] 就会被设为 '-' 。当使用 -c command 的话, sys.argv[0] 就被设定为 '-c' 所有的在 -c command 之后的option(例如 –i )都会被当成 sys.argv 而被command所处理,所以就不是当作option一样的来看待了。
3.1.2 互动模式
当指令是由tty终端机来传入时,我们称之为互动模式( interactive mode)。在此模式之下会出现主要的命令提示符号( primary prompt)来提示输入下一个指令,这个primary prompt通常是 "> >> " 。如果是指令是延续上一行的话就会出现secondary prompt符号,这个 secondary prompt 就通常是 "... " 。一进入python的互动模式的话直译器会出现一个欢迎信息,以及版本编号辑版权说明,接下来就是第一个prompt。如下所示:
python
Python 1.5.2b2 (#1, Feb 28 1999, 00:02:06) [GCC 2.8.1] on sunos5
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>>
当你输入需要多行的结构时,直译器就会自动会出现延续上一行的prompt符号,下面的例子是 if 叙述的情况:
>>> the_world_is_flat = 1
>>> if the_world_is_flat:
... print "Be careful not to fall off!"
...
Be careful not to fall off!
3.2直译器及其周边环境
3.2.1 程序错误处理
当有错误产生时,直译器就会在屏幕印出错误的信息以及stack trace的所有数据。在互动模式之下,印完数据之后会再印出prompt来。如果输入是来自于档案的话,在出现错误的情况下直译器在印出stack trace之后程序会以nonzero exit 的状态结束。(此处讨论不包含已经由 try 叙述及 except 子句处理外的状况(Exceptions))。有些的程序错误是没有办法挽救并且会造成nonzero exit的结束情况,这常常是内部的不一致或是某种running out of memory所造成。所有的错误信息都会被写入至标准error stream之中,正常的程序执行的输出则会写入到标准(standard output)输出之中。
如果在primary或是secondary prompte之下打入中断字符(通常是 Control-C 或是 DEL),这会造成输入的中断并且会回到prompt之下。 (有一个GNU Readline package的问题可能会使这个功能失效。) 在指令执行之中打入中断字符则会引起 KeyboardInterrupt 的exception,而这是可以在 try 叙述中处理的。
3.2.2 执行Python脚本
在BSD之类的Unix 系统上,我们可以在script的最前面加入以下的叙述(类似shell script),并改变档案属性为可执行:
#! /usr/bin/env python
如此script就会变成可执行档,可以直接执行 (假设Python的直译器是在user的 $PATH) 变量之中) 。 "#!" 这两个字必须在script档案的最前面。值得一提的是 "#" 在Python之中也当作注解(comment)部分开始的符号。
3.2.3 交互式启动档
当你使用互动模式的时候,如果可以在每次直译器要启动时先执行一些命令的话将是很有用的。要达成如此功能,你可以设定一个文件名称给环境变量 $PYTHONSTARTUP ,这个档案可以包含你想要在启动时执行的命令,类似 .profile 在Unix shell中的用法。
这个启动档(startup file)只有对在互动模式下有效,如果你用Python读入script时就没有用,在当 /dev/tty 是命令的输入来源也没有用(其它的情况与互动模式相类似)。这个startup file所执行命令的命名空间是与其它互动模式下输入的指令相同,所以在startup file内定义或是import的对象,在之后的互动模式指令中都是可以直接使用的。你也可以在这个startup file中改变 sys.ps1 及 sys.ps2 ,如此就可以改变你的primary prompt及secondary prompt。
如果你在你的startup file中想要使用另外的在目前目录的startup file,你只需要在主要startup file (global start-up file)写入 "if os.path.isfile('.pythonrc.py'): execfile('.pythonrc.py')" 。如果你想要在你的script之中使用startup file的话,你必须在你的script中写入:
import os
filename = os.environ.get('PYTHONSTARTUP')
if filename and os.path.isfile(filename):
execfile(filename)
4 非正式的Python介绍
在底下的例子里,你可以很容易区别凡是需要输入的地方都会出现prompts (">> > " 或 "... "),凡是输出的结果则没有。如果你想要跟着这个教学文件一起做的话,你就得打入所有在prompts之后的指令,凡是没有prompts出现的行就是直译器输出的结果。值得注意的是,secondary promt 之后如果什么东西都没有,表示这是一个空行(直接按ENTER的结果),也表示这是一个多行指令的结束。
在本文件中的大部分例子,都有加上注释,甚至是那些互动模式下的例子。注释(comment)在Python中是以 " #" 之后的东西都是注释(译:跟Perl一样)。注释可以自成一行,也可以跟在空格符或是程序代码的后面。但是,如果 " #" 是在字符串常数(string literal)之中的话,就不代表注释的意义,而只是一个普通字符罢了。
底下是一些例子:
# this is the first comment
SPAM = 1 # and this is the second comment
# ... and now a third!
STRING = "# This is not a comment."
4.1把Python当作计算器来用
现在我们来试一试一些简单的Python指令吧。请先启动Python的直译器并且等待primary prompt( " >>> " )的出现。(应该不会很久的)
4.1.1 数字
直译器就好像一个计算器一样:你可以打入一个表示式(expression),然后直译器会把这个expression的执行结果秀出来。Expression的语法都很简单直接,一般的运算符号 +, -, * 以及 / 的用法就跟其它的程序语言(像是Pascal或C)一样。你也可以用括号 "( )" 来表示运算执行的先后次序。例子如下:
>>> 2+2
4
>>> # This is a comment
... 2+24
>>> 2+2 # and a comment on the same line as code
4
>>> (50-5*6)/4
5
>>> # Integer division returns the floor:
... 7/3
2
>>> 7/-3
-3
跟C语言一样,等于符号 ("=") 其实是表示设定某个值给一个变量的意思。虽然设定 ("=") 运算本身是有结果值的,但是直译器并不会输出其结果来。
>>> width = 20
>>> height = 5*9
>>> width * height
900
一个值是可以同时设给许多变量的:
>>> x = y = z = 0 # Zero x, y and z
>>> x
0
>>> y
0
>>> z
0
浮点数的运算在Python里面也是支持的,如果整数与浮点数(带小数点或e的数)进行运算的话,整数部分会先转换(convert)成浮点数再进行运算。
>>> 4 * 2.5 / 3.3
3.0303030303
>>> 7.0 / 2
3.5
甚至连复数的运算也支持喔,只需要把虚数部分加上 "j" 或是 " J"在其后就可以了。如果实部不为零的话,复数的写法就写成 "(real+ imagj)" 。或者,我们也可以用函数的方式来表示复数为 "complex(real , imag)" 的形式。
>>> 1j * 1J
(-1+0j)
>>> 1j * complex(0,1)
(-1+0j)
>>> 3+1j*3
(3+3j)
>>> (3+1j)*3
(9+3j)
>>> (1+2j)/(1+1j)
(1.5+0.5j)
复数的虚数部分及实数部分的值都是以浮点数(float point numbers)来表示的,如果 z 代表一个复数的话,你可以很轻易的用 z.real 以及 z.imag 得到一个复数的实数部分及虚数部分。
>>> a=1.5+0.5j
>>> a.real
1.5
>>> a.imag
0.5
复数没有办法直接用 (float(), int() 或是 long()) 转换成浮点数或是整数。事实上,复数没有直接对应的实数,你必须用 abs(z) 来得到 z 的magnitude(以浮点数表示),或是如上所述用z.real 直接得到其实数部分。
>>> a=1.5+0.5j
>>> float(a)
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: can't convert complex to float; use e.g. abs(z)
>>> a.real
1.5
>>> abs(a)
1.58113883008
在互动模式之下,最后一个印出来的expression的值会储存在一个特殊变量 "_ " 之中。这表示,当你用Python的直译器来当作计算器用的时候,想要连续做运算其实是方便许多的。如下例:
>>> tax = 17.5 / 100
>>> price = 3.50
>>> price * tax
0.61249999999999993
>>> price + _
4.1124999999999998
>>> round(_, 2)
4.1100000000000003
对于使用者来说, "_" 这个变数是一个只读的变数。你没有办法设定一个值给它,当你这样做的时候,事实上你是重新创造一个同名的变量,但是跟之前系统内建的 "_" 这个变量是一点关系也没有的了。
4.1.2 字符串
除了数字之外, Python也有能力处理字符串(string)。字符串在Python中有很多种表达方式,它可以放在双括号””之中,也可以放在单括号’’里面:
>>> 'spam eggs'
'spam eggs'
>>> 'doesn\'t'
"doesn't"
>>> "doesn't"
"doesn't"
>>> '"Yes," he said.''
"Yes," he said.'
>>> "\"Yes,\" he said."
'"Yes," he said.'
>>> '"Isn\'t," she said.'
'"Isn\'t," she said.'
字符串常数(string literals)是可以跨越多行的,其表示方法有很多。如果要换行的话可以用”
}”符号来表示之。如下例:
hello = "This is a rather long string containing\n\
several lines of text just as you would do in C.\n\
Note that whitespace at the beginning of the line is\
significant.\n"
print hello
这个例子会印出以下的结果:
This is a rather long string containing
several lines of text just as you would do in C.
Note that whitespace at the beginning of the line is significant.
你也可以用成对的三个单引号( """ ) 或双引号 ( ''' ) 来表示字符串。在此情况下你所打入的ENTER就会直接被解读为换行符号而不需要再用\n了。
print """
Usage: thingy [OPTIONS]
-h Display this usage message
-H hostname Hostname to connect to
"""
这个例子会印出以下的结果:
Usage: thingy [OPTIONS]
-h Display this usage message
-H hostname Hostname to connect to
如果你打入的expression是字符串的运算,运算的结果的同样的会由直译器显示出来,而且显示的方式跟你直接打入字符串常数(string literals)是一样的:会在引号之中,所有有escape character “\”表示的字符都会依样的显示出来。如果字符串本身包含有单引号,整个字符串就会用双引号括起来,要不然就会只用单引号来把整个字符串括起来。(如果你使用 print 这个叙述(statement)来印出字符串的话,屏幕的输出就不会有引号出现,而且字符串中的escape character (\”表示的特殊字符) 都会显示出其所代表的意义来。)
字符串可以用 + 这个操作数来相加 (连接起来),或是用 * 这个操作数来重复之。请看例子:
>>> word = 'Help' + 'A'
>>> word
'HelpA'
>>> '<' + word*5 + '>'
'<HelpAHelpAHelpAHelpAHelpA>'
如果你把两个字符串常数放在一起,它们自动就会相加起来。所以,上面的例子的第一行也可以写作 "word = 'Help' 'A'" 。不过这个方法只适用于两个字符串常数的相加,其它情况就不适合了。请看例子:
>>> import string
>>> 'str' 'ing' # <- This is ok'string'
>>> string.strip('str') + 'ing' # <- This is ok'string'
>>> string.strip('str') 'ing' # <- This is invalid
File "<stdin>", line 1
string.strip('str') 'ing'
^
SyntaxError: invalid syntax
如同在C语言一样,字符串是有标记(subscript(index))的,第一个字符的标记(subscript(index))就是0。在Python中没有另外一个字符character数据型态,一个字符就是一个长度为 1的字符串。就像是在Icon语言一样,字符串是可以用其subscript(index)来切出( slice notation )其中的一部份的,其语法为 ""。
>>> word[4]
'A'
>>> word[0:2]
'He'
>>> word[2:4]
'lp'
与C不同的是,Python的字符串是不可改变的(immutable),如果你想要改变其中的一个字符或是一个部份(slice),你会得到一个错误的信息:
>>> word[0] = 'x'
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment
>>> word[:-1] = 'Splat'
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: object doesn't support slice assignment
但是你可以任意使用一个字符串的一个字符或是一个部份(slice)来创造出另一个字符串,这是完全可行的:
>>> 'x' + word[1:]
'xelpA'
>>> 'Splat' + word[-1:]
'SplatA'
当你用字符串切片(string slice)的语法时,可以使用其预定(default)的subscript(index)值,这是很方便的。第一个subscript(index)的默认值是0,第二个subscript(index)的默认值则是这个字符串的整体长度。
>>> word[:2] # The first two characters
'He'
>>> word[2:] # All but the first two characters
'lpA'
所以, s[:i] + s[i:] 会恰好等于 s 。你可以想一想为什么:
>>> word[:2] + word[2:]
'HelpA'
>>> word[:3] + word[3:]
'HelpA'
如果你用一些奇怪的index来切割字符串,Python直译器也都处理的很好:如果第二个index太大的话就自动代换为字符串的长度,如果第二个index比第一个index还要小的话就自动传回一个空字符串。
>>> word[1:100]
'elpA'
>>> word[10:]
''
>>> word[2:1]
''
字符串的index甚至可以是负数,若是负数的话,就必须从字符串的尾巴开始算起。如下例:>>> word[-1] # The last character
'A'
>>> word[-2] # The last-but-one character
'p'
>>> word[-2:] # The last two characters
'pA'
>>> word[:-2] # All but the last two characters
'Hel'
但是 -0 事实上是等于 0 ,所以不会从尾巴开始算起。
>>> word[-0] # (since -0 equals 0)
'H'
如果负数index超过字符串的范围的话,就自动只会到最大可能的范围,但是如果不是切割一部份的话就会造成错误的情形:
>>> word[-100:]
'HelpA'
>>> word[-10] # error
Traceback (innermost last):
File "<stdin>", line 1
IndexError: string index out of range
最好避免错误的方法是把index看成是指向字符及字符间位置的指针,字符串的最开头是0,字符串的结尾处就是字符串的长度。如下图所示:
+---+---+---+---+---+
| H | e | l | p | A |
+---+---+---+---+---+
0 1 2 3 4 5
-5 -4 -3 -2 -1
上图的数字部分第一行代表的是正数的index,由0到字符串的长度,第二行代表的是负数的index。字符串的切片(slice)很容易就可以看出来,就是两个index之间的所有字符组合成的字符串啰。
对于正数的index来说,如果两个index都在范围之内,字符串的切片(slice)的长度就正好是其两个index相减的结果。举例来说 word[1:3] 的长度就正好是 2。
Python内建的 len() 函式可以帮助我们得到字符串的长度值。
>>> s = 'supercalifragilisticexpialidocious'
>>> len(s)
34
4.1.3 Unicode字符串
从Python 2.0 开始Python支持一种新的储存文字数据的数据型态:Unicode物件(object)。使用这个对象你可以储存并控制Unicode的资料(详见 http://www.unicode.org ) ,并且这个对象跟已经存在的字符串(string)对象是完全可以互相整合,并且在需要时可以互相转换的。
使用Unicode的好处是可以处理各种不同国家语言的字符。在Unicode之前,在一个code page里只有256个字符可以使用在script中。这个限制的结果常常造成软件国际化(internationalizetion,通常写作 "i18n" -- "i" + 18个字符 + "n")时候的困扰。Unicode的出现定义一个所有script都可以用的code page,如此就解决了这个问题。
在Python中要创造一个Unicode字符串就跟创造一个普通字符串一样容易:
>>> u'Hello World !'
u'Hello World !'
在引号之前小写的 "u" 代表这个字符串是一个Unicode字符串。如果你要用到特殊字符,你可能要使用Python的Unicode特殊字符编码( Unicode-Escape encoding)。底下的范例示范如何使用之:
>>> u'Hello\\u0020World !'
u'Hello World !'
上面的u0020 表示在这个位置要插入一个由十六位0x0020所代表的Unicode字符 (就是空格符啦)。
其它的字符也是一样的会被解读为其对应的Unicode字符。由于Unicode对应中的前256 个Unicode字符正好就是大部分欧美国家使用的Latin-1 编码字符,所以其转换是更加的容易。
对于专家们来说,有一个字符串的原始模式(raw mode)可以使用。你必须再加上一个小写 'r' 来使Python 使用这一个原始的Unicode特殊字符编码( Raw-Unicode-Escape encoding)。只有当uXXXX 之中的小写 'r' 有奇数的'\'时才会用到这一个编码的。
>>> ur'Hello\u0020World !'
u'Hello World !'>>> ur'Hello\\u0020World !'
u'Hello\\\\u0020World !'
这个原始模式(raw mode)通常用在当你的字符串里面有一大堆的反斜线 '\' 时 ,例如regular expressions(正规表示)时就常用到。
除了这些标准的编码之外, Python还提供了一整套的方法让你可以从以知的编码中创造出Unicode字符串来。
Python内建的 unicode() p() 函式可以让你使用所有的已注册的Unicode译码/编码系统(codecs (COders and DECoders))。 这个 codes 可以与大部分的系统互相转换,包括 Latin-1, ASCII , UTF-8 以及 UTF-16 等等。上面所提到的最后两种系统是可变长度的编码系统,可以来储存8位及16位的Unicode字符。Python预设使用UTF-8为预设编码系统。当你印出Unicode或是将Unicode写入档案时都会使用到。
>>> u"??ü"
u'\344\366\374'
>>> str(u"??ü")
'\303\244\303\266\303\274'
如果你要使用一个特别的编码系统,但是要印出对应的Unicode码时,你可以使用 unicode() 函式,加上这个编码系统的名称当作第二个参数。
>>> unicode('\303\244\303\266\303\274','UTF-8')
u'\344\366\374'
如果要把Unicode字符串转换为一般的字符串编码时,可以使用Unicode对象的 encode() 方法(method)。
>>> u"??ü".encode('UTF-8')
'\303\244\303\266\303\274'
4.1.4 列(List)
(译:硬要翻译list实在太不方便,我直接用原文啰)
Python能够了解一些较为 复杂 的数据型态,这些数据型态大多是用来处理一群的其它数据值。最方便使用的要算是 list 了,一个list可以写成一串由逗号分开的值(东西),然后用角括号括起来便成。放在list里的东西不需要是同一个数据型态
>>> a = ['spam', 'eggs', 100, 1234]
>>> a
['spam', 'eggs', 100, 1234]
跟字符串的index用法相同,list的index也由0开始,同样你可以用index来切割lists、组合两个list等等:
>>> a[0]
'spam'
>>> a[3]
1234
>>> a[-2]
100
>>> a[1:-1]
['eggs', 100]
>>> a[:2] + ['bacon', 2*2]
['spam', 'eggs', 'bacon', 4]
>>> 3*a[:3] + ['Boe!']
['spam', 'eggs', 100, 'spam', 'eggs', 100, 'spam', 'eggs', 100, 'Boe!']
与字符串不相同的是,字符串的个别字符是不可变动的( immutable ),但是list的个别成员是可以自由改变的。
>>> a
['spam', 'eggs', 100, 1234]
>>> a[2] = a[2] + 23
>>> a
['spam', 'eggs', 123, 1234]
你也可以设定一个值或是一个list给一个list的切割部分(slice),但是这样的结果会改变整个list的长度:
>>> # Replace some items:
... a[0:2] = [1, 12]
>>> a
[1, 12, 123, 1234]
>>> # Remove some:
... a[0:2] = []
>>> a
[123, 1234]
>>> # Insert some:
... a[1:1] = ['bletch', 'xyzzy']
>>> a
[123, 'bletch', 'xyzzy', 1234]
>>> a[:0] = a # Insert (a copy of) itself at the beginning
>>> a
[123, 'bletch', 'xyzzy', 1234, 123, 'bletch', 'xyzzy', 1234]
内建的 len() 函式仍然可用在list上面:
>>> len(a)
8
一个list也可以是另一个list的成员(这叫作巢状list, nested list),参考下例:
>>> q = [2, 3]
>>> p = [1, q, 4]
>>> len(p)
3
>>> p[1]
[2, 3]
>>> p[1][0]
2
>>> p[1].append('xtra') # See section 5.1
>>> p
[1, [2, 3, 'xtra'], 4]
>>> q
[2, 3, 'xtra']
注意前一个例子, p[1] 以及 q 事实上指得是同一个对象。我们在之后还会再讨论对象的语法( object semantics )。
4.2迈向程序设计的第一步
当然Python能做比二加二更有用更复杂的事,例如说,我们可以写一个程序来印出费氏数列( the Fibonacci series )来:
>>> # Fibonacci series:
... # the sum of two elements defines the next
... a, b = 0, 1
>>> while b < 10:
... print b
... a, b = b, a+b
...
1
1
2
3
5
8
这个范例告诉了我们很多新的事情:
w 程序的第一行是一个多重设定( multiple assignment ):两个变量 a 以及 b 同时都设定了新的值0 与 1。 程序的最后一行再次使用这个技巧,这次在设定符号(等号)的右边我们使用了expression,所有在右边的expression会先求得其值(evaluate)然后才进行设定(assign)的动作。对于在右边的expression来说,其evaluate的次序则是由左至右的。
w 在 while 循环中,只要条件符合(在这里是 b < 10 ), 这个while 循环就会一直执行。与C相同的是,对Python而言只要是非零的整数都代表在决定true/false的情况下都代表true,0则代表false。我们也可以在循环的条件的地方放入字符串或是一个list,只要这个字符串或list的长度不是零就代表true,若是空字符串或空的list就代表false。在这个例子里,我们比较两个值的大小。比较的操作数与C是完全相同的: < (小于), > (大于), == (等于), <= (小于或等于), >= (大于或等于) 以及 != (不等于)。
w 在循环中的执行部分是 缩排 的:缩排在Python中是表示一群叙述的方法(way of grouping statements)。Python没有(还没有)提供够聪明的行排版机制,所以每个要缩排的行你都得打入空格键或是tab键来缩排。实际的工作环境中,你也许会有自己的文字编辑器,大部分的编辑器会自动帮你做缩排的工作。当在互动模式下输入一个复合的statement时(一个由许多statements组合成的statement),最后你需要再打入一个空白行(译:按ENTER键)来告诉直译器这个statement已经完成了(直译器没办法猜你什么时候完成这个statement)。值得注意的是,如果你的statement是属于同一群(block)的话,你缩排的距离就要是一样的。
w print 这个叙述会印出一个expression的结果值,这点与我们之前所做的仅仅打入expression是不同的。不同之处在于对字符串及多个的expression来说,用 print 不会印出字符串的引号,也会在多个expression之间印出空白来,这样会让结果好看一点。如下所示:
>>> i = 256*256
>>> print 'The value of i is', i
The value of i is 65536
如果不想每次的输出都换行的话可以在 print 叙述之后加上逗号,如下所示:
>>> a, b = 0, 1
>>> while b < 1000:
... print b,
... a, b = b, a+b
...
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
值得注意的是如果最后一行还没有完成的话,直译器会在印出prompt之前印出新的一行。
5 更多流程控制的工具
除了我们刚刚介绍的 while 叙述之外,Python也能够使用大部分其它程序语言使用的流程控制形式 ─ 除了有一些不同之外。
5.1if 叙述
大概最为人所知的 statement 就是 if 叙述了,举例如下:
>>> x = int(raw_input("Please enter a number: "))
>>> if x < 0:
... x = 0
... print 'Negative changed to zero'
... elif x == 0:
... print 'Zero'
... elif x == 1:
... print 'Single'
... else:
... print 'More'...
elif 的部份可以没有也可以有很多个, else 部分可以有一个也可以没有。 `elif' 这个关键词是`else if'的简化,而且有减少过分缩排的效果。 用 if ... elif ... elif ... 这样的写法可以来取代在其它一些程序语言中常见的 switch 或是 case 的写法。
5.2 for 叙述
在Python里的 for 叙述的用法与在C或是Pascal里的用法有所不同。不像是在Pascal中一定要执行某个数目的循环,也不像是在C中让使用者决定执行的进度(step)及结束执行的条件,Python的 for 叙述会将一个系列(sequence,像是list或是string)里所有的成员走遍一次,执行的顺序是依照成员在squence里的顺序。以下是一个例子:
>>> # Measure some strings:
... a = ['cat', 'window', 'defenestrate']
>>> for x in a:
... print x, len(x)
...
cat 3
window 6
defenestrate 12
在循环的执行之中改变sequence的内容是危险的一件事(当然,只有可变的sequence像list才能作更动),如果你真的需要在循环的执行中改变list的成员值,最好先复制一份这个list的拷贝,然后针对这个拷贝来做循环。list的切割(slice)提供了一个简便的制作拷贝的方法:
>>> for x in a[:]: # make a slice copy of the entire list
... if len(x) > 6: a.insert(0, x)...
>>> a
['defenestrate', 'cat', 'window', 'defenestrate']
5.3 range() 函式
如果你真的需要一个循环执行一定数目的次数的话,你可以使用内建的 range() 函式。这个函式会产生一个含有逐步增加数字的list。如下:
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
在这个函式中的所传入的参数是代表端点,而且这个端点不在产生的list之中。 range(10) 正好产生10个数值,正好是这个list的index是由0到10。我们也可以让这个产生的list从某个数值开始,或者规定其每次增加的数值为多少 (增加值也可以是负数,这个增加值也叫做 `step')。
>>> range(5, 10)
[5, 6, 7, 8, 9]
>>> range(0, 10, 3)
[0, 3, 6, 9]
>>> range(-10, -100, -30)
[-10, -40, -70]
所以如果我们要循环一次一个sequence的index的话,我们可以用 range() 配合上 len() 一起使用:
>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
... print i, a[i]...
0 Mary
1 had
2 a
3 little
4 lamb
5.4 break 及 continue 叙述,以及在循环中的 else 子句
如同在C语言里一样, break 叙述中断最靠近的一个 for 或 while 循环。
同样的,从C语言借过来的 continue 叙述会中断目前执行的循环,并且执行下一个循环。
特别的是,Python的循环有一个 else 子句,这个子句之后的程序代码会在整个循环正常结束的时候执行,(对 for) 循环而言指的是list已经到底,对 while 循环而言指的是条件式变成false)。但是,若是在非正常结束(因为 break 叙述)的情况下 else 子句的程序代码就不会执行。底下的例子是一个循环,用来找出所有的质数:
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print n, 'equals', x, '*', n/x
... break
... else:
... print n, 'is a prime number'
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
5.5pass 叙述
pass 叙述什么也不做,通常是用在当你的程序的语法上需要有一个叙述,但是却不需要做任何事的时候。例子如下:
>>> while 1:
... pass # Busy-wait for keyboard interrupt
...
5.6定义函式
我们可以定义一个函式,在底下这个函式定义的例子,当我们给定想要印出的范围,这个函式会印出一个费氏数列来:
>>> def fib(n): # write Fibonacci series up to n
... "Print a Fibonacci series up to n"
... a, b = 0, 1
... while b < n:
... print b,
... a, b = b, a+b
...
>>> # Now call the function we just defined:
... fib(2000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
在上例中这个 def 关键词代表了一个函式的定义(function definition),在 def 之后必须接着函式的名称以及一个用括号括起来的一连串的参数。接下来一行之后的程序代码就是函式的主体部分,而且必须是缩排的。函式的程序代码部分的第一个statement可以是一个字符串常数(string literal),这个字符串常数会被当作是函式的批注部分而叫做批注字符串(documentation string或是 docstring )。
有工具可以使用这个批注字符串来自动的制作出线上的或是印出来的文件,或者是让使用者可以交互式的浏览程序代码。写批注是一个好习惯,所以最好养成这个好习惯,把所有的程序代码都写上批注字符串。
执行函式的时候会产生一个目前(local)的符号表(system table),这个表是用来记录函式中的所有local的变量的。更精确的来说,所有在函式中变量的设定值都会纪录在这个system table中,所以当你要使用(reference)一个变量时,会先检查local的system table,然后是整个程序(global)的system talbe,然后是内建的变量名称。虽然 global 变量可以在函式使用(reference),但是不能在函式之内直接的设定其值(除非是在一个global的statement中建立的)。
当函式被呼叫时,实际传入的函式参数是会被纪录在被呼叫函式的local system table里的。因此,参数被传入时是 以其值传入的(call by value) 。在此的值指的是对象的参考( reference ),而非对象本身的 值。 4.1 当一个函式呼叫另一个函式时,就会因此呼叫而建立一个新的local system table。
当定义函式的时候,也就在目前所在的system table里定义了这个函式的名称。对直译器来说,这个函式名称的数据型态是一个使用者自订的函式。这个函式的值名称可以被设定给另一个名称,然后这个新的名称就可以被当作是一个函式名称来使用。这个过程就是一个一般的重新命名的机制。
>>> fib
<function object at 10042ed0
>>>> f = fib
>>> f(100)
1 1 2 3 5 8 13 21 34 55 89
你也许认为 fib 不是一个函式(function)而是一个程序(procedure)。如同在C中一样,在Python的procedure指的是没有传回值的函式(function)。事实上,就技术上而言,procedure也是有传回值的,只是所传回的是一个Python系统内键的值,叫做 None 。通常来说,如果只传回 None 的话,直译器不会印出这一个传回值。但是,如果你真想看一看它的话,你可以这样做:
>>> print fib(0)
None
如果想让你的函式传回一个包含费氏数列的list,而不是只印出来的话,其实是很简单的:
>>> def fib2(n): # return Fibonacci series up to n
... "Return a list containing the Fibonacci series up to n"
... result = []
... a, b = 0, 1
... while b < n:
... result.append(b) # see below
... a, b = b, a+b
... return result
...
>>> f100 = fib2(100) # call it
>>> f100 # write the result
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
如同往例,这个例子事实上示范了一些新的Python的特点:
w return 叙述使得函式传回了一个值。如果单单 return 没有其它的expression来表示传回的值时,就表示这是一个从procedure传回来的写法(procedure到结束都没有传回值也是表示从procedure传回来)。这种写法表示传回值是 None 。
w result.append(b) 这个叙述表示呼叫了 result 这个list对象的一个方法( method ) 。Method是一个特别”属于”某个对象的函式,而且其名称的形式是 obj.methodname 。在这里 obj 指的是某一个对象(我们也可以用expression来代替),而 methodname 指得是由这个对象的数据型态所定义的这个方法的名称。不同的数据型态会定义不同的方法,不同的数据型态也许所定义的方法名称会相同,但是并不会造成冲突(你可以定义你自己的数据型态及其方法,我们称之为类别( classes ),后面会再谈到的)。在这个例子里的 append() 方法是在list这个数据型态中定义的。这个方法会在list的最后面加入一个新的成员,在这个例子里也可以写作 " result = result + [b]" ,效果一样,但是用方法来写有效率多了。
5.7定义函式(续)
在定义函式的时候我们可以加入不定数目的参数,加入参数的写法有三种,是可以混和使用的。
5.7.1 预设内定参数值
最好用的一种写法是,对其中的一个或多个参数给它一个特定的默认值。这样子的话,当你在呼叫函式时,就可以不用传入参数,或是传入较少的参数了。请看下例:
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
while 1:
ok = raw_input(prompt)
if ok in ('y', 'ye', 'yes'): return 1
if ok in ('n', 'no', 'nop', 'nope'): return 0
retries = retries - 1
if retries < 0: raise IOError, 'refusenik user'
print complaint
当你呼叫这个函式的时候你可以用 ask_ok('Do you really want to quit?') ,或者是 ask_ok('OK to overwrite the file?', 2) 。
设定的默认值可以是一个变量,但是这个变量在函式定义的时候就以定义时的情况( defining scope )决定(evaluate)了其值,所以以下的例子:
i = 5
def f(arg = i): print arg
i = 6
f()
印出的结果会是 5 。
重要的警告: 这个参数默认值只有被evaluate一次,这在当默认值是可变的对象像是list或是dictionary时会造成重要的差别。举例来说,底下的函式会记录曾经被呼叫过每次所传入的参数。
def f(a, l = []):
l.append(a)
return l
print f(1)
print f(2)
print f(3)
印出来的结果会是:
[1]
[1, 2]
[1, 2, 3]
所以如果你的默认值是一个可变的对象,但是你又不想让每次呼叫都共享的时候,你就必须如此写你的函式:
def f(a, l = None):
if l is None:
l = []
l.append(a)
return l
5.7.2 关键词参数
呼叫函式时也可以使用关键词参数,其形式是 " keyword = value" ,底下的这个函式:
def parrot(voltage,state='a stiff',action='voom',type='Norwegian Blue'):
print "-- This parrot wouldn't", action,
print "if you put", voltage, "Volts through it."
print "-- Lovely plumage, the", type
print "-- It's", state, "!"
用这些方式呼叫都是正确的:
parrot(1000)
parrot(action = 'VOOOOOM', voltage = 1000000)
parrot('a thousand', state = 'pushing up the daisies')
parrot('a million', 'bereft of life', 'jump')
但是用这些方式都是不正确的:
parrot() # required argument missing
parrot(voltage=5.0,'dead')#non-keyword argument following keyword
parrot(110, voltage=220) # duplicate value for argument
parrot(actor='John Cleese') # unknown keyword
一般来说,一连串的参数的次序是先有非关键词参数(也可以没有)然后才是关键词参数,关键词必须是函式定义时所用的参数名称。这个定义时用的参数名称有没有默认值并不重要,但是一个传入的参数只能有一个值(默认值不算),如果你已经先用非关键词参数给了某个参数一个值,接下来你就不能再用关键词参数给它另外的值。底下的例子就违反了这个规则:
>>> def function(a):
... pass
...
>>> function(0, a=0)
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: keyword parameter redefined
当一个函式定义时参数名称是以 ** name 这种形式定义时,表示这个参数要接受的是一个 dictionary(译:字典,包含许多关键词以及值的对应),这个 dictionary 包含许多的关键词参数,但是这些关键词不能跟其它的参数名称相同。另外参数也可以用 *name 这种形式定义(下一小节会解释),这种方式定义的参数要接受的是一个 tuple(译:不可更动的list),这个 tuple 可以接受不限数目的非关键词参数( *name 必须要出现在 **name 之前)。下面的例子就是一个函式定义的范例:
def cheeseshop(kind, *arguments, **keywords):
print "-- Do you have any", kind, '?'
print "-- I'm sorry, we're all out of", kind
for arg in arguments: print arg
print '-'*40
for kw in keywords.keys(): print kw, ':', keywords[kw]
要呼叫这个函式,你可以这样呼叫:
cheeseshop('Limburger', "It's very runny, sir.",
"It's really very, VERY runny, sir.",
client='John Cleese',
shopkeeper='Michael Palin',
sketch='Cheese Shop Sketch')
函式执行的结果如下:
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch
5.7.3 随意的参数串
最后,我们要介绍最不常见的形式,也就是定义一个函式可以接受任意数目的参数,这些传入的参数会被放进一个tuple里面去。在这一个任意数目的参数之前,可以定义没有或是一个或是多个普通的参数:
def fprintf(file, format, *args):
file.write(format % args)
5.7.4 Lambda 形式
由于众多的需求,Python里面也加入了这一个在其它功能性程序语言及Lisp里面常见的特性。你可以使用 lambda 这个关键词来定义一些小的没有名字的函式。底下是一个传回两个参数值相加结果的例子: "lambda a, b: a+b" 。Lambda形式可以使用在任何需要函式对象(function objects)的地方。语法上限制lambda形式只能有一个expression,其功能只是方便的取代一个正常的函式定义。就像是函式里面包含函式定义一样,lambda形式不能使用(reference)外面一层函式的的变量,但是你可以使用传入默认值参数的方式来克服这个问题,像是下面的例子:
def make_incrementor(n):
return lambda x, incr=n: x+incr
5.7.5 批注字符串
批注字符串的内容及形式是有一个新的约定俗成的规范的。
第一行应该是一个有关这个对象的目的的短的、简洁的摘要。因为简洁的缘故,这一行不应该包括对象的名称及型态(除非对象的的名称正好是解释对象目的的一个动词),因为对象名称及型态是可以从其它地方得知的。这一行第一个字的第一个字母应该大写,最后应该有一个句点。
如果批注字符串还包含其它行的话,第二行应该是空白的,这样可以让摘要及细部的解释有所区分。底下的各行应该是一个或多个段落,其目的应该是诸如解释对象的呼叫方法及其副效果(side effects)的解释说明。
一般时候,Python的分析器(parser)并不会把多行字符串的缩排拿掉,但是在批注字符串中,批注字符串的处理工具需要特别拿掉那些缩排。底下的一般通用准则可以用来帮助决定批注字符串如何缩排:在第一行之后所遇到的第一个非空白行决定了整个批注字符串的缩排大小,(我们不能用第一行,因为第一行一定要跟着引号在一起,所以其缩排是不明显的)。在这之后的与这个缩排相等的空白,都会被整个拿掉。如果某行的前面有空白但缩排的空白不足(这是不应该发生的),这些缩排也会被整个拿掉。空白的解释是把tab展开后(一般为八个空白)的方式来解释的。
这里示范了如何使用多行的批注字符串:
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print my_function.__doc__
Do nothing, but document it.
No, really, it doesn't do anything.
脚注
... 对象本身的值。4.1
事实上是,较洽当的说法是以其对象参考传入的( call by object reference ),因为如果一个不可改变的对象传入之后,呼叫这个函式的地方仍然可以看到这个函式对这个对象的改变(例如在list之中插入一个对象)。
6 数据结构
这一章讨论的内容有些你在之前已经看过,但我们会更深入的讨论。另外,我们也会介绍一些新的东西。
6.1列(Lists)(续)
列(list)这个数据型态有一些方法可以使用,底下我们就列出来一些常用的方法:
append(x)
在list的尾端加入一个成员,也可以用这个方法来写 a[len(a):] = [x] 。
extend(L)
接受一个新的list的参数,然后把它加入到目前这个list的尾端,也可以写作 a[len(a):] = L 。
insert(i, x)
在某个特定的位置加入一个成员。第一个参数是要加入的位置的index,所以 a.insert(0, x) 会加入在list的最前端,而 a.insert(len(a), x) 会在最后端加入,相等于 a.append(x) 。
remove(x)
拿掉第一个其值相等于 x. 的成员。如果整个list都没有这个成员,那就会得到一个错误(error)。
pop([i])
从一个list中拿掉某个位置的成员,并且传回这个被拿掉的成员。如果没有传入位置的index的话, a.pop() 会传回这个list的最一个成员,同样的这个成为会被从这个list之中拿掉。
index(x)
传回第一个其值相等于 x 的成员之位置(index),如果整个list都没有这个成员,那就会得到一个错误(error)。
count(x)
传回在整个list里面, x 出现了多少次。
sort()
针对list里面的成员做排序。
reverse()
反转整个list里面成员的位置。
底下的这个例子使用了大部分的lsit的方法(method):
>>> a = [66.6, 333, 333, 1, 1234.5]
>>> print a.count(333), a.count(66.6), a.count('x')
2 1 0
>>> a.insert(2, -1)
>>> a.append(333)
>>> a
[66.6, 333, -1, 333, 1, 1234.5, 333]
>>> a.index(333)
1
>>> a.remove(333)
>>> a
[66.6, -1, 333, 1, 1234.5, 333]
>>> a.reverse()
>>> a
[333, 1234.5, 1, 333, -1, 66.6]
>>> a.sort()
>>> a
[-1, 1, 66.6, 333, 333, 1234.5]
6.1.1 把列(Lists)当作堆积(Stacks)使用
由于有这些好用的方法,把列(list)当作堆积(stack)来使用是一件容易的事。Stacks的最后一个加入的成员是第一个被取出来的成员(后进先出``last-in, first-out''法则)。要在stack的最顶端加入一个成员可以使用 append() ,要从stack的最顶端取出一个成员可以用 pop() (不须加入参数)。例子如下:
>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4]
6.1.2 把列(Lists)当作队列(Queues)使用
你也可以很方便的拿list来当作队列(queues)来使用。Queues的特点是第一个加入的成员会是第一个被取出的成员(先进先出``first-in, first-out''法则)。要在queue的后端加入一个成员可以使用 append() ,要从queue的最前端取出一个成员可以使用 use pop() ,记得参数是 0 。例子如下:
>>> queue = ["Eric", "John", "Michael"]
>>> queue.append("Terry") # Terry arrives
>>> queue.append("Graham") # Graham arrives
>>> queue.pop(0)
'Eric'
>>> queue.pop(0)
'John'
>>> queue
['Michael', 'Terry', 'Graham']
6.1.3 功能式程序设计工具
有三个与list合用非常有用的内建工具函式: filter(), map(), 以及 reduce() 。
"filter( function, sequence)" 这个函式会传回 一个sequence (如果可能的话其成员为同一数据型态),这个sequence里面的成员都是将 sequence 里面的的成员,一一传入到 function( item) 所代表的函式后,传回值为true的成员所组合而成。这个函式对于传入的 sequence 有过滤的效果,如下例所示:
>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]
"map( function, sequence)" 会针对 sequence 里的各个成员呼叫 function(item) ,然后传回个别成员呼叫之后传回的结果。举例来说,要计算一连串的立方值,我们可以如此做:
>>> def cube(x): return x*x*x
...
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
我们也可以传入不只一个sequence。如果传入多个sequence的话,第一个函式名称的参数要是能够处理多个参数的函式,然后系统会把各个sequence相对应的成员拿出来放入函式之中(如果两个sequence长度不相等的话,不足的会用 None 来补足)。如果第一个函式名称参数为 None 的话,所呼叫的函式就仅仅是传回其所传入的参数。综合以上的两个特性,我们可以使用 " map(None, list1, list2)" 这一个工具函式来方便的转换两个sequence成为一个成对的成员组合的sequence。请看例子:
>>> seq = range(8)
>>> def square(x): return x*x
...
>>> map(None, seq, map(square, seq))
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25), (6, 36), (7, 49)]
"reduce( func, sequence)" 会利用 sequence 的前两个成员当参数呼叫 func ,然后所得的传回值再与下一个成员当参数传入 func ,一直到整个 sequence 结束。下面的例子计算1到10的总和:
>>> def add(x,y): return x+y
...
>>> reduce(add, range(1, 11))
55
如果在 sequence 里面只有一个成员的话,这个成员的值就会直接传回。如果在 sequence 里面没有任何成员的话,会造成一个例外状况(exception)。
我们也可以加入第三个参数来当作开始的值,如此当传入的 sequence 是空的话,就可以使用这个开始值。如果是正常的sequencde的话,开始值会先跟第一个成员被传入当作呼叫 func 的参数,其传回值再跟第二个成员传入 func ,依此类推。请看下例:
>>> def sum(seq):
... def add(x,y): return x+y
... return reduce(add, seq, 0)
...
>>> sum(range(1, 11))
55
>>> sum([])
0
6.1.4 传回整个列 (List Comprehensions)
List comprehensions提供了一个制造list简洁的方法,而不用使用 map(), filter() 以及/或者 lambda 形式。其结果也比使用以上的方法来做出list要来的清楚易懂。list comprehension通常是一个expression跟着是一个 for 的语句,然后是零个或多个 for 或是 if 语句。其传回的list是一个由在 for 及 if 语句条件下执行expression的结果。如果expression的结果是一个tuple,就必须用括号"( )"括起来。
>>> freshfruit = [' banana', ' loganberry ', 'passion fruit ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
>>> vec = [2, 4, 6]
>>> [3*x for x in vec]
[6, 12, 18]
>>> [3*x for x in vec if x > 3]
[12, 18]
>>> [3*x for x in vec if x < 2]
[]
>>> [{x: x**2} for x in vec]
[{2: 4}, {4: 16}, {6: 36}]
>>> [[x,x**2] for x in vec]
[[2, 4], [4, 16], [6, 36]]
>>> [x, x**2 for x in vec] # error - parens required for tuples File "<stdin>", line 1
[x, x**2 for x in vec]
^
SyntaxError: invalid syntax
>>> [(x, x**2) for x in vec]
[(2, 4), (4, 16), (6, 36)]
>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [x*y for x in vec1 for y in vec2]
[8, 6, -18, 16, 12, -36, 24, 18, -54]
>>> [x+y for x in vec1 for y in vec2]
[6, 5, -7, 8, 7, -5, 10, 9, -3]
6.2del 叙述
del 叙述可以让你轻松的去掉在list当中某一个位置(index)的成员。这个叙述也可以用切割(slice)的方法来去掉某一段的成员(在之前我们必须借着设定某个slice为空list来达成同样的效果)。请看下例:
>>> a
[-1, 1, 66.6, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.6, 333, 333, 1234.5]
>>> del a[2:4]
>>> a
[1, 66.6, 1234.5]
del 也可以用来去掉整个变量:
>>> del a
如果你在去掉之后还继续使用这个变量名称的话,就会得到一个错误 (除非你之后再设定另外一个值给它)。我们稍后会继续看到使用 del 的例子。
6.3Tuples(固定有序列)及Sequences(有序列)
我们之前所讨论的lists以及字符串(strings)有很多的共通点,例如可以用index来定位置,可以切出其中的某一段(slicing)等等。事实上,list及字符串都是 sequence 这个数据型态的特例。由于Python是一个可以不断进步的语言,其它的sequence数据型态有可能会陆续的加入。我们就来看另外一种标准的sequence数据型态:固定有序列( tuple )。
一个tuple是由特定数目的值所组成,其成员与成员之间以逗号分开。举例如下:
>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
如同在前面例子所见到的,tuples输出的结果都会包含在括号之中。所以,巢状tuple(tuple之中有tuple)可以被清楚的区分出来。Tuple在输入的时候可以有括号也可以没有,通常我们都会加上括号(特别是用在在复杂的expression之中)。
Tuples有很多种用途,例如(x, y)坐标,从数据库取出的员工的数据库记录等等。Tuples跟字符串一样都是不可改变(immutable)的:我们不能单独的设定一个tuple里面的个别成员(虽然我们可以用切割及连结(concaatenation)来达到同样的效果)。我们也可以做出可以改变成员的tuple来,例如list。
有一个特殊的情况就是只包含0个或1个成员的tuple:要创造这样的一个tuple,我们必须在语法上有一些的变化。空的tuple的表示方法是一对空的括号,只有一个成员的tuple表示方法是在成员后面加上逗点(不能只是用括号把一个成员括起来)。虽然有点奇怪,但是蛮有用的。请看例子:
>>> empty = ()
>>> singleton = 'hello', # <-- note trailing comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)
t = 12345, 54321, 'hello!' 这个叙述是一个tuple包装( tuple packing )的例子: 12345 , 54321 以及 'hello!' 这三个值都被包装放在一个tuple里面了。我们也可以使用相反的操作方式,例如:
>>> x, y, z = t
这个动作叫做打开sequence( sequence unpacking )。Sequence unpacking的动作需要在设定符号左边有一串的变量,其数目应与右边sequence的成员数目相同。值得注意的是,多重设定(a, b = 1, 2)其实只是tuple packing以及sequence unpacking的结合罢了!
有一个不太对称的地方:packing的动作永远结果是一个tuple,但是unpacking可以针对各种不同的sequence来做。
6.4Dictionaries(字典)
另外一个在Python当中很好用的内建数据型态是字典( dictionary )。Dictionary有的时候在别的程序语言里面也叫做连结记忆( ``associative memories'' )或者是连结数组( ``associative arrays'' )。不同于sequence是由一连串的数字来做index,dictionary用一个特殊的不可改变的(immutable)钥( keys 来当作其 index。字符串及数字都不能被改变,所以都可以来当作dictionary的key。Tuple如果只含有字符串,数目字,以及其它tuple的话也可以当作key。如果tuple里面有包含任何可改变的(mutable)的对象的话(包括直接或间接),就不能当作key来使用。List不能当作key,因为list的成员可以被改变(你可以用 append() 以及 extend() 之类的方法,或是切割(slicing) 或 index 来设定list的个别成员)。
我们最好把dictionary想象成一个没有顺序的 key: value 成对的组合。唯一的条件是,在dictionary里面key的值必须是唯一不重复的。最简单的dictionary就是一对空的中括号: {} 。在中括号里面放入由逗号分开的key:value对,就成了dictionary里面的成员。这也是当dictionary被印到输出时的标准格式。
我们可以对dictionary做一些事,包括加入一个带有key的值、或者是用key来找一个特殊的值。我们也可以用 del 来删去一对key:value的成员。如果你试图存一对key:value但是这个key已经被使用了的话,原来的那一个value的值就会被盖过。如果你想用一个不存在的key来找出某一个成员的话,你会得到一个error。
使用 keys() 这一个dictionary的方法我们可以得到一个由所有的key值组成的list,其顺序是随机没有次序的(如果你想要排序的话,只要针对这一个得到的list来呼叫其 sort() 方法就可以了)。要检查某个key是不是存在的话,你可以使用 has_key() 这一个method来做检查。
底下是一个有关dictionary的小例子:
>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}
>>> tel.keys()
['guido', 'irv', 'jack']
>>> tel.has_key('guido')
1
6.5条件(续)
在之前所谈到的 while 及 if 里面的条件叙述,除了一般的比较之外也可以包含其它的运算。
我们可以用 in 以及 not in 来检查某个值是否出现(或没出现)在某个sequence里面。我们也可以使用 is 以及 is not 来检查两个对象是否事实上指的是相同的一个对象(这只有跟像是list一样可变的对象有关)。所有的比较运算的运算优先次序都是一样的,都比所有的数字运算要来的低。
比较运算是可以连起来的:像是 a < b == c 就是试验是否 a 比 b 小,以及 b 和 c 是否相等。
比较运算也可以用 and 以及 or 等boolean运算来连结起来,其比较的结果(或其它boolean运算的结果)也可以用 not 来得到相反(negated)的结果。在这些运算里, not 有最高的优先次序, or 的优先次序最低,但是它们所有的优先次序都比比较运算来的低。所以, A and not B or C 其实相等于 (A and (not B)) or C 。当然,最好适时的使用括号来帮助你表达你真正想要的组合。
and 以及 or 这两个boolean操作数也可以称做有快捷方式的操作数( shortcut operators):它们的evaluated的次序都是由左而右,而且一但已经可以决定其运算的结果,就不会再继续的做下去。也就是说如果 A 以及 C 都是 true 而 B 是false的话, A and B and C 并不会evaluate C 这个expression。一般来说这些shortcut operator 的传回值如果不是当作boolean而是当作一般的值来用的话,其传回值会是最后一个被evaluate的expression的值。
我们也可以把一个比较运算,或是 boolean运算的结果设定给一个变量,其例子如下:
>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_null
'Trondheim'
值得注意的是不像是C,在Python里面设定(assignment)不能够放在expression里面。C的程序设计师也许会针对此点抱怨,但是这样的好处是可以避免一些常见的把设定( = )及等于( == )弄混淆的情形
6.6Sequences(有序列)及其它数据型态的比较
Sequence 对象可以和其它的相同数据型态的sequence 对象相比较,其比较方法是依照所谓的 lexicographical 顺序(lexicographical ordering)。首先是两个sequence的第一个成员互相比较,如果比较有大小之别的话就此决定其相对大小,若是相等的话就再比较下一个成员的大小,余此类推直到sequence的结束。如果两个要相比较的成员本身也是一个sequence的话,同样的条件可以继续递归的使用在这两个sequence之上。如果这两个sequence的所有成员都相等的话,我们就说这两个成员是相等的。如果某一个sequence是另一个sequence的一部份的话,较短的那一个sequence就是较小的。字符串的Lexicographical顺序用的是个别字符的ASCII码的顺序。底下是一些同一数据型态的sequence的比较例子:
(1, 2, 3) < (1, 2, 4)
[1, 2, 3] < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4) < (1, 2, 4)
(1, 2) < (1, 2, -1)
(1, 2, 3) == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4)
值得注意的是,我们也可以 比较两个不同数据型态的对象,而其结果是依其数据型态的名称来决定的。所以所有的list都比字符串还要来的小(因为list小于string),所有的string也都比tuple还要小。至于数值的数据型态则是由其数值大小来决定其大小,所以0等于0.0的,其余按此类推。
5.1 Footnotes
... 其余按此类推。5.1
你不应该完全倚赖这些比较不同数据型态的规则,因为这些规则是尚未确定的,以后的Python版本也有可能会再做更动。
7 模块
如果你离开Python直译器然后又再打开Python直译器的话,你会发现你刚才定义的一些东西(函式或变量)都不再存在了。所以说,如果你真的想写一些比较大型的程序的话,你可能需要有一个文字编辑器来编辑一个档案,然后再让Python直译器来将这个档案当作输入(input)来处理。这个过程就是写脚本( script )的过程。如果你的程序继续的越来越长的话,你也许会想要把你的程序分成几个小的档案,这样比较方便来维护你的程序。你也许也会希望有一些方便的函式可以让你自由的用在好几个程序之中,你又不想要copy这些函式的定义在每个程序之中。
要达到以上的这些目的,Python有一个将定义放在档案中的方法,你可以之后再在你的script或是互动模式的程序下使用这些存好的定义。这样的档案就叫做模块( module )。存在于module之中的定义可以用 imported 放入在其它的module或是主要的 main module之中。(main module是一组你可以在script的最高一级 (top level)部分使用,或是在互动模式中使用的变量)。
一个module就是一个包含有Python的定义及叙述的档案,档案的名称就是module的名称加上延伸档名 .py 在后面。在一个module里面,module的名字(是一个字符串)会存在 __name__ 这个变量里面并当作全域变量(global variable)使用。举例来说,你可以用你喜欢的文字编辑器打入以下的内容,并将这个档案存在目前的目录,并取名为 fibo.py :
# Fibonacci numbers module
def fib(n): # write Fibonacci series up to n
a, b = 0, 1
while b < n:
print b,
a, b = b, a+b
def fib2(n): # return Fibonacci series up to n
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a+b
return result
现在你可以进入Python的直译器里面并且import 你刚刚建立的module,其方法如下:
>>> import fibo
这个命令并不会使得所有的 fibo 里面的函式名称都写入目前的符号表(symbol table)里面,但是会把 fibo 这个module的名字写在symbol table里面。 所以,我们现在就可以使用module的名字来呼叫这些我们之前所定义的函式了:
>>> 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'
如果你真的想要只用函式名称的话,你可以把这些函式名称设定到另一个local变量去(可以就是函式的名称):
>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
7.1模块(续)
一个module里面除了放函式的定义之外也可以放可执行的叙述(statement)。这些statement的功用在于初始化(initialize)这个module。这些statement也只有在module 第一次 被import的时候才会被执行。 6.1
每一个模块都有其自己的符号表(symbol table),这个symbol table也就成为在module里面所定义的函式的全域变量(global variables)。所以说,写module的人就可以自由的使用这些global variable而不需要担心会跟module的使用者的global variable有所冲突。从另一方面来说,如果你知道自己在做什么的话,你也可以跟使用函式一样的使用module里面的global variable。其语法为 modname.itemname.
Module可以被import到其它的module里面。习惯上(并非一定),我们会把所有的 import 的叙述都放在module(或者是script)的最开头。这样的话这个被import的module的名称就会被放在目前这个module的global symbol table里面了。
有一个变形的方式可以直接import module里面的变量或函式的名称进入symbol table里面。举例如下:
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
这样做的话并不会使得module的名字被放在目前的symbol table里面。(所以在上面的例子里 fibo 是没有被定义的)。
我们甚至可以一次将所有的在module里面所定义的名称都import进来:
>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
这一个写法会import所有的定义的名称,除了以底线 ( _ ) 开头的之外。
7.1.1 寻找模块的路径
当你import一个叫做 spam 的module时,直译器会先在目前的目录寻找一个叫做 spam.py 的档案,如果没找到,会再依据定义在 $PYTHONPATH (一个环境变量)里面的所有路径来找。 $PYTHONPATH 的语法与设定方法与 $PATH 是一样的,也就是一连串的目录路径的名称。如果你没有设定 $PYTHONPATH ,或是在这些目录当中也找不到的话,直译器会继续在一个安装时预设的目录来找,在Unix的机器上,通常这个目录是 .:/usr/local/lib/python 。
事实上,module的搜寻路径是依照存在 sys.path 这一个变量中的一个许多路径名称组成的list。这个变量当在Python直译器启动时,会从输入的script(或目前的目录)、 $PYTHONPATH 、以及安装时设定的预设目录来读取所有的目录。如果你知道自己在做什么的话,你可以修改这个变量来改变直译器寻找module的路径。请参阅之后的标准模块(standard module)一段。
7.1.2 “编译过的”( ``Compiled'') Python档案
对于一些小的程序来说,如果使用很多标准的module,而又想加速启动的过程,你就可以用编译过的Python档案。比如你要找 spam.py ,如果在你找到这个档案的目录里ey4又有一个叫做 spam.pyc 的档案的话,这就表示 spam 这个module有一个已经二元编译过的(``byte-compiled'')的版本可以使用。在 spam.pyc 里面也会记录用来创造它的spam.py上一次被修改的时间,如果 .pyc 里面所储存的时间与最新版本的 .py 的修改时间不符合的话, .pyc 档案就不会被使用。
一般来说,你不需要做任何事来创造一个 spam.pyc 档案。当你成功的编译一个 spam.py 档时,自动的 spam.pyc 文件就会写入在同一个目录里。如果这个过程里有问题的话,系统不会当这是个错误情况(error)。相反的,如果写入的档案没有完全成功的写入的话,这个档案只会被认为是不正确的而忽视其存在。 spam.pyc 档案的内容是独立于操作系统平台的,所以一个 Python module 的目录是可以被在各种不同架构下的多台机器所共享的。
这里有一些给专家们的秘诀:
w 当使用 -O 这个选项启动Python直译器时,直译器产生会最佳化程序代码(optimized code),并存在 .pyo 档案里。这个最佳化程序代码目前并没有太多功能,它只是简单的拿掉所有的 assert 叙述以及 SET_LINENO 指令。当你使用 -O 这个选项时, 所有的 二元码(bytecode)都会被最佳化,所有的 .pyc 档都会被忽略,所有的 .py 档案都会被编译成最佳化的二元码。
w 如果你传入两个 -O 选项给Python直译器的话 ( -OO) ,在有些很少见的情况下会使得编译器的最佳化过程使得程序无法正常执行。目前这个选项会使得 __doc__ 字符串从二元码中被拿掉,进而使得 .pyo 档案可以更精简。但是有些程序会使用到这些字符串,所以你应该只有在你很确定时才使用这个选项。
w 读 .pyc 以及 .pyo 档案并不会比读 .py file; the only thing that's faster about .pyc or .pyo 档还要快,唯一的差距是在当被导入(load)时的速度有差别。
w 当你在命令列(command line)使用script的名称来执行它的话,并不会造成二元码被写到 .pyc 或是 .pyo 所以,你可以把这个script写成一个module,然后再用一个小的启动的script来import这个module。这样可以减少启动的时间。事实上,你也可以直接从命令列启动 .pyc 或是 .pyo 档案。
w 你也可以把 spam.pyc (或是 spam.pyo ,如果你用了 -O 的话) 放在没有 spam.py 的目录里。这样子,当你给别人你的链接库时,你可以给他们比较难用逆向工程(reverse engineer)破解的程序。
w 你可以用 compileall 这个module来将某个目录里面的所有module都便成 .pyc 档案(或者是 .pyo 档案,如果你用了 -O )。
7.2标准模块
Python包含有一个 标准模块的链接库,这个链接库在另一个文件 Python Library Reference (Python链接库参考手册)中有更多的描述。有些标准模块已经内建在直译器里面,这些模块让我们可以使用那些不在Python语言本身的一些功能,不管是为了效率或是要使用操作系统的资源(例如system call)。有些module是在设定时的选项,比如说, amoeba 这个module就只有在你的系统里面有Amoeba相关的资源时才会出现。有一个module特别值得我们好好注意: sys 。这个module在每一个Python直译器里面都有,其中有两个变量 sys.ps1 以及 sys.ps2 是用来设定primary prompt 以及secondary prompt的:
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print 'Yuck!'
Yuck!
C>
这两个变量只有在当你在互动模式下启动直译器时才有定义。
sys.path 这个变量是一个许多目录路径组成的list,里面的目录路径就是直译器寻找module的路径。这个变量里面的路径都是从环境变量 $PYTHONPATH 里面复制的,或者当 $PYTHONPATH 没有设定时,就会使用默认值。你也可以用一般使用list的方法来修改之。例如:
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
7.3dir() 函式
内建的 dir() 函式主要是用来找出某个module里面所定义的所有名称。其传回值是一串经过排序了的字符串list:
>>> 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']
注意这里的名称是指所有类型的名称:包括变量,函式,以及module等等。
dir() 并没有列出所有内建的函式及变量的名称。如果你真想要列出来的话,它们都定义在 __builtin__ 这个标准module里面:
>>> 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']
7.4Packages(包装)
Package是一种用点号表示模块名称(``dotted module names'')的组织Python 模块(module)命名空间的方法。举例来说:如果module的名称是 A.B 表示是在 " B" 这个package里面的一个名称为 "A" 的module。就如同使用module使得其它写module的不用担心别人的global variable命名的问题,使用这种带点号的module名称也使得写多个module的package的人不用担心所用的module名称会和别人有所重复。
现在假设你要设计一组的module(就是设计一个package),这个package是用来标准化的处理声音档案以及声音的资料的。由于声音档的格式有很多(通常是由其延伸档名来辨别,例如 .wav, .aiff, .au) 等格式),你也许需要一个随时会增加新module的package来处理新的声音档格式。由于你可能想对声音数据做各种不同的处理(例如混音、加回声、加入平衡方程式,加入人工音响效果等等),所以你还需要写一些module来专门做这些处理。底下这个架构可能是你的package所需要的(用档案阶层系统来表示):
Sound/ Top-level package
__init__.py Initialize the sound package
Formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
Effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
Filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
为使Python能把这个目录架构当作是一个package,上面的 __init__.py 这个档是必须要的。这是为了要避免有些档案目录的名字是很普通的名字(例如 " string" ),这会让直译器误认正确的module名称而找不到在搜寻路径中的module。在最简单的例子里, __init__.py 可以是一个空的档案。但是你也可以让这个档来做一些package初始化的动作,或者设定 __all__ 这个变量(稍后会再提)。
使用package的人可以从package里使用(import)某一个module,例如:
import Sound.Effects.echo
上面的程序代码会导入(load) Sound.Effects.echo 这个module。如果你要使用这个module,你必须使用完整的名称,例如:
Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)
另外一个导入在package中的某个module的方法是:
from Sound.Effects import echo
同样的,这会导入 echo 这个moduel。不同的是,当你使用这个module的时候你就不用在写前面package的名称了。请看以下使用这个module的例子:
echo.echofilter(input, output, delay=0.7, atten=4)
你也可以直接的import某一个在module里面的函式或变量,如下例:
from Sound.Effects.echo import echofilter
同样的,这会导入 echo 这个module,不同的是你现在可以直接的使用 echofilter() 这个函式了:
echofilter(input, output, delay=0.7, atten=4)
值得注意的是当你使用 from package import item 这样的叙述时,你所import的东西可以是一个package中的module(或者是subpackage),或者是在module里面所定义的名称,例如变量、类别或是函式等等。 import 叙述会先测试是否这个东西真的存在于这个package,如果没有的话,会假设这是一个module然后试着导入(load)之。如果还失败的话,就会引发一个 ImportError 的例外状况(exception)。
相反的是,当你使用 import item.subitem.subsubitem 这样的叙述时,除了最后一个东西(item)以外,其余的都必须是package。最后一个可以是 module 或是 package ,但是不能是一个定义在module里面的类别、成员或函式。
7.4.1 从一个Package中Import *
那如果使用者写了 from Sound.Effects import * ,会造成什么结果呢?理想状况下,我们可能会期望会搜寻整个package目录,然后找出所有的module并且一一的import这些module。不幸的是,在Mac 以及 Windows 平台下,档案的名称大小写并不统一。所以在这些平台之上,我们并无法保证 ECHO.PY 这个档案应该被import成 echo, Echo 或 ECHO (例如,Windows 95 有一个恼人的特点,就是会自动把所有的文件名称第一个字符大写)。DOS的 8+3 档名限制对长的module名称来说,也是另一个有趣的问题。
所以唯一的解决方法就是package的作者要提供一个明显的index给用package的人。如果遵守这个习惯的话,当用package的人在import的时候使用 from Sound.Effects import * 的话,就会去找这个package的 __init__.py 档案里面的 __all__ 这个list变量,这个list里面就会包含所有应该被import进来的module名称了。身为Package的作者有责任要保持 from package import * 这个档案的更新,但是如果package的作者确信没有人会用 from Sound.Effects import * 这种写法的话,也可以不使用这个档案。举例来说 Sounds/Effects/__init__.py 这个档案就可以有下面这样的程序代码:
__all__ = ["echo", "surround", "reverse"]
这就表示 from Sound.Effects import * 会从 Sound 这个package 里面import 这三个module。
如果没有定义 __all__ 的话, from Sound.Effects import * 这个叙述就 不会 从 Sound.Effects 这个package里面import所有的module进入目前的命名空间(namespace)。唯一能保证的是 Sound.Effects 这个package有被imported 进来(可能会执行 __init__.py 里面的初始化程序代码),并且这个package里面所定义的名称会被import进来。Package里所定义的名称包含了在 __init__.py 里面所定义的名称(以及所import的module)。当然也包含了在之前用import引进来的module名称,例如:
import Sound.Effects.echo
import Sound.Effects.surround
from Sound.Effects import *
在这个例子里,echo以及 surround 这两个modules 都会被 import进来目前的命名空间(namespace)里。这是因为当 from...import 这个叙述执行的时候,这两个module都已经在这个package中有定义了(你也可以用 __all__ 来定义)。
值得注意的是使用import * 这样的写法常常是不被鼓励的,因为这通常会使得你的程序的可读性降低。无论如何,在互动模式下这样做的确会使你减少打太多字的机会,而且有些的module在设计的时候就故意只让某些特别的名称可以被使用。
记住,使用 from Package import specific_submodule 没有任何不对的地方。事实上,除非你的module的名字会和其它的名称冲突,否则这是常被推荐使用的形式。
7.4.2 Package内的References(参考)
在package之中的module常常需要彼此互相使用。例如说, surround 这个module就有可能会使用到 echo 这个module里的东西。事实上,由于这是最常见的,所以import的时候总是会先找自己这的package里面的module,然后再依照搜寻的路径来寻找。因此 surround 这个module可以使用 import echo 或是 from echo import echofilter 就可以了。如果在自己所处的这个package里面找不到这个要import的module的话, import 指令就会在第一级(top-level)的module里找所指定的名称。
当一个subpackage是在另一个package里的话(例如前面的 Sound ),没有其它快捷方式可以让你使用其它在同一个外围的package里面的subpackage里的module,你必须使用完整的名称来指称你所要用的package。例如说,如果在 Sound.Filters.vocoder 这个module里面你想要使用在 Sound.Effects 这个package里的 echo 这个module的话,你就要使用 from Sound.Effects import echo 这个叙述。
脚注
... 才会被执行。 6.1
事实上,函式的定义也是”被执行”的叙述,这个执行的结果是把函式的名称写入module的global symbol table里面。
8 输入与输出
有很多的方式可以来表现一个程序的输出结果,可以印出来在一个可读的表格里面,也可以写入到档案里面供作未来使用。这一章里面将谈到一些可能的方法。
8.1花俏的输出格式化
到现在为止我们谈到了两种写入值的方式:用expression的叙述( expression statements ),或是用 print 这个叙述。 (第三种方法是使用file对象的 write() 方法(method),这一个标准输出所指向的档案(standard output file),可以用 sys.stdout 来存取之。请参阅链接库参考手册上面对此的详细说明。)
通常你会希望你对于输出的结果能够在格式上面稍有控制力,而不只是预设的用空白连结起来而已。有两种方法可以来控制输出的格式,第一种是自己动手来做字符串的调整。你可以使用字符串的切割(slicing)以及连结,做成任何你想要的效果。标准的 string module里面有一些好用的东西,也可以帮助你填入适当的空白,使字符串的宽度成为你想要的宽度,我们待会再来讨论如何做。另外一个控制输出格式的方法是使用 % 这个操作数,配合上用字符串成为左边的参数。这个操作数会翻译左边的这个字符串参数,其功能类似于C里面的 sprintf() 的字符串参数,然后把右边要控制的字符串适当的填入,之后再传回这个格式化的结果。
还有一个问题,如何把其它的值转换成洽当的字符串呢?幸好Python里面的 repr() 函式可以转换任何的值成为一个字符串,你以可以把这个值写在反撇号( ` ` )的中间也有同样的效果。请看一些例子:
>>> x = 10 * 3.14
>>> y = 200*200
>>> s = 'The value of x is ' + `x` + ', and y is ' + `y` + '...'
>>> print sThe value of x is 31.4, and y is 40000...
>>> # Reverse quotes work on other types besides numbers:
... p = [x, y]
>>> ps = repr(p)
>>> ps
'[31.4, 40000]'
>>> # Converting a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = `hello`
>>> print hellos
'hello, world\012'
>>> # The argument of reverse quotes may be a tuple:
... `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),
... # Note trailing comma on previous line
... 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() 这个函式会使的数目字的字符串加入前头的0。该加入正负号的时候它也会自动加入:
>>> import string>
>> 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.
如果在你的格式化字符串(format string)中有超过一个以上的格式存在,你要在 % 的右边传入一个tuple。例如这个例子:
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
... print '%-10s ==> %10d' % (name, phone)
...
Jack ==> 4098
Dcab ==> 7678
Sjoerd ==> 4127
大部分的格式(format)其效果都与你在C里面所用的一样,你必须要在右边传入适当型态的数据。如果你没有正确的如此做时,你会得到一个例外的状况(exception),而不是得到一个系统核心倾倒出来的内存数据(dump)。其中 %s 这个格式最为自由,你可以使用字符串或非字符串,如果你使用非字符串的数据时,数据会自动用内建的 str() 函式转换成字符串数据。你也可以使用 * 来传入一个独立的(整数)参数来决定宽度或是精确度(precision)的大小。但是,C里面的 %n 以及 %p 在Python里面却没有支持。
如果你有一个很长的格式化字符串,而你又不想分开他们的话,你可以使用名称而非位置来使用这些变量。其方法是使用C格式的延伸形式: %(name)format ,举例如下:
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print 'Jack:%(Jack)d; Sjoerd:%(Sjoerd)d; Dcab:%(Dcab)d'%table
Jack: 4098; Sjoerd: 4127; Dcab: 8637678
这个功能当与新的内建函式 vars() 一起使用时特别有用,这个内建函式会传回一个含有所有local变量名称及值的dictionary。
8.2读写档案
open() 这个函式会传回一个file物件。通常其用法是传入两个参数如: "open(filename, mode)".
>>> f=open('/tmp/workfile', 'w')
>>> print f
<open file '/tmp/workfile', mode 'w' at 80a0960>
第一个参数是一个包含文件名称的字符串,第二个参数是另外一个字符串,其内容是一些用来描述你要怎么使用这个档案的字符。 mode 可以是 'r' ,如果你想要这个档为只读的话,也可以使用 'w' 如果你只想要写入的话(如果该档本来就存在的话,你会杀掉原来的档案),你也可以用 'a' 表示你要在档案的尾端加入东西, 'r+' 则会让这个档可以读也可以写。你也可以不传入第二个参数,如果没有传入 mode 参数的话,会使用预设的 'r' 模式。
在Windows以及Macintosh系统上,你可以在mode里面加入 'b' 表示要以二元模式(binary mode)开启这个档案,所以你也可以使用 'rb', 'wb', 以及 'r+b' 。在Windows里面文字文件及二元文件是有区别的,在文字文件里面行终止字符(end-of-line)在档案的读写时是自动会被稍稍修改的。这个自动修改的动作对于一般的ASCII文字文件没有什么影响,但是会使得像是 JPEGs 或是 .EXE 之类的二元档被损害。所以当你在处理这些档案时特别注意要使用二元的模式。(值得注意的是,在Macintosh里面文字模式的精确的语意是会随着其背后所用的C链接库而有不同的。)
8.2.1 File对象的Methods(方法)
底下的例子都假设你已经建立了一个叫做 f 的file对象。
如果你想读一个档案的内容你需要呼叫 f.read(size) 这个方法(method)。这个method会读入某个数量的数据,然后将数据以字符串的形式传回。你也可以不传入 size 这个数值参数,如果你没有传入或是传入负值的话,就会将整个档案都传回。如果你的档案比你的内存的两倍还大的话,这是你自己要处理的问题。其它的情况下,都会读入并传回最多是 size 数量的字节(byte)的数据。如果已经到了档案的最尾端你还呼叫 f.read() 的话,回传值就会是一个空字符串 ("") 。
>>> f.read()
'This is the entire file.\012'
>>> f.read()
''
f.readline() 会一次只读入一行,换行符号 (\n ) 仍然会被留在字符串的最尾端,并且当档案不是以换行符号结束时,最后一行的换行符号就会被忽略。这会使得传回的结果不至于有混淆,当传回值是空字符串时,我们可以很有信心这已经是档案的最尾端,因为空白的行还是会有 '\n' 单独存在的。
>>> f.readline()
'This is the first line of the file.\012'
>>> f.readline()
'Second line of the file\012'
>>> f.readline()
''
f.readlines() 会传回一个 list ,其内容是所有在档案内的各个行的数据。如果你传入第二个可有可无的 sizehint 参数时,会从档案内读入这个参数所代表的byte数目,并且把最后所在的那一整行也一并读完。这一个方法通常用在一行一行的读很大档案时,如此可以增进读的效率,并避免在内存中放置大量的数据。只有完整的行才会被传回来。
>>> 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() 会传回一个整数,代表目前这个file对象在这个档案内的所在位置,其单元是从档案开始处有多少个byte。你可以用 "f.seek(offset, from_what)" 来改变file对象的所在位置, from_what 参数代表从哪里算起,0代表档案的最开头,1代表目前位置,2代表档案的结尾处。呼叫这个函式file对象会跳到从 from_what 参数代表的位置算起 offset 个byte的距离的地方。如果 from_what 没有传入的话,会使用预设的 0,代表从档案的最开头算起。
>>> f=open('/tmp/workfile', 'r+')
>>> f.write('0123456789abcdef')
>>> f.seek(5) # Go to the 5th byte in the file
>>> f.read(1)
'5'
>>> f.seek(-3, 2) # Go to the 3rd byte before the end
>>> f.read(1)
'd'
当你已经使用完毕这个file对象时,要记得呼叫 f.close() 把所有因为开文件所使用的系统资源都释放掉。一但你呼叫了 f.close() 之后,任何的对file对象的动作都会自动的失败。
>>> f.close()
>>> f.read()
Traceback (innermost last):
File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file
File 对象有一些其它的method可以用,例如 isatty() 以及 truncate() ,这些比较少用的method可以参考在链接库参考手册里面有关file对象的说明。
8.2.2 pickle Module(模块)
从档案写入及读出字符串数据都没有太大问题,但是数值数据则会比较麻烦。因为 read() 这个method 只传回字符串,你还得要将这个字符串传给类似 string.atoi() 这样的函式来将代表数值的字符串 '123' 转成数值123。如果你要在档案内储存较复杂的数据型态例如lists、dictionaries、或是某个类别的对象时,那就更加复杂了。
为使使用者不需要自己写程序来处理储存这些复杂的数据型态,Python提供了一个标准的module叫做 pickle 。这个令人惊讶的module可以处理几乎所有的Python对象(甚至是某些形式的Python程序代码!),并将之转换成一个字符串的表现方式。这个过程也叫做 pickling. R。从这个字符串重新组合成我们所要的对象的过程则叫做 unpickling 。在这两个过程之间,我们可以将这个代表对象的字符串储存成档案或数据,或是在网络上传给另一台机器。
如果你有一个 x 对象及一个可以写入的file对象 f ,要pickle一个对象最简单的方式只要一行程序就可以了:
pickle.dump(x, f)
如果file对象 f 是可读的话,要unpickle这个对象只要这样做:
x = pickle.load(f)
(这个module还有其它的用法可以pickling多个对象,或是你不想将这个pickled的数据写入档案。请参考在链接库参考手册内有关 pickle 完整的说明。)
pickle 也是一个标准的方法,可以将Python的对象储存起来给其它程序语言使用,或是等待下一次启动Python再用。技术上来说这叫做 persistent 的对象。因为 pickle 的运用如此广泛,许多的程序设计师专门写一些Python的延伸功能来处理诸如matrices这些新数据型态的pickle 以及 unpickle的过程。
9 程序错误与例外(Exceptions)情形
至此为止,我们都只有稍稍的提到错误讯息。但是如果你有试着执行上面的范例的话,你可能注意到,基本上错误的情况可以分成两类:语法错误 ( syntax errors ) 以及例外情况 ( exceptions )。
9.1语法错误
语法错误也叫做分析时的错误(parsing errors),大概是一般在学Python时最常见到的直译器所发出来的抱怨:
>>> while 1 print 'Hello world'
File "<stdin>", line 1
while 1 print 'Hello world'
^
SyntaxError: invalid syntax
Python分析器(parser)会在印出错误的行,并且用一个向上的箭号指出最早发现错误的地方,而这个错误是发生(至少是被发现)在这个箭号所指的单元(token) 之前。在我们的例子里面:错误发生在 print 这个关键词,因为前面应该有一个 ( " :" ) 。错误信息里面也包含文件名称以及行数,所以你可以很快知道要到哪里去找错。
9.2例外(Exceptions)情形
有的时候,甚至当你的语法完全正确时,当你执行程序时仍然会出错。这种在程序执行阶段发生的错误叫做例外情形 ( exceptions ) ,并且会造成程序致命的终止(无法执行下去)。你待会就会知道在Python里面要怎样处理这样的状况,但是我们先来看这样的状况下会造成什么错误信息:
>>> 10 * (1/0)
Traceback (innermost last):
File "<stdin>", line 1
ZeroDivisionError: integer division or modulo
>>> 4 + spam*3
Traceback (innermost last):
File "<stdin>", line 1
NameError: spam
>>> '2' + 2
Traceback (innermost last):
File "<stdin>", line 1
TypeError: illegal argument type for built-in operation
在这些错误信息的最后一行都是解释到底发生了什么事。例外情况(Exception)有很多种类型,类型的名称也在错误信息之中,在上面的例子里面,exception的类型分别是: ZeroDivisionError, NameError 以及 TypeError. 。对于内建的exception来说,这些印出来的字符串都是这些内建的exception类型的真正类型名称,但是对于使用者自己自定的exception类型就不一定了(虽然这是一个有用的约定俗成的习惯)。这些标准的exception名称也正是他们内建的指称(identifiers) (不是正式的关键词)。
这一行其它部分则是有关这个exception类型的详细解释,其意义则是依照exception的类型而有不同。
在错误信息最后一行之前的部分则是显示了这个exception发生时的状况,也就是内存堆积(stack)的内容追朔(backtrace)。一般来说这个这个部分包含了stack backtrace的来源行数,但是这并不代表是在从标准输入读入时候的行数。
在Python链接库参考手册中( Python Library Reference )详细列出了所有的内建exception及其说明。
9.3例外(Exceptions)情形的处理
我们可以写一个程序来处理某些的exception。请看下面程序范例,我们要求使用者输入一个有效的数字,直到所输入的数字真正有效为止。但是使用者也可以中止这个程序(用 Control-C 或者是任何操作系统支持的方式)。值得注意的是,使用者主动中止程序事实上是使用者引发一个 KeyboardInterrupt 的exception。
>>> while 1:
... try:
... x = int(raw_input("Please enter a number: "))
... break
... except ValueError:
... print "Oops! That was no valid number. Try again..."
...
这个 try 叙述的作用如下:
w 首先,try之后的叙述( try clause ,在 try 及 except 这两个字之中所有的叙述)都会被执行。
w 如果没有发生任何exception, except之后的叙述( except clause )会自动被忽略,整个 try 叙述就算是执行完毕。
w 如果当执行try之后的叙述时发生了exception,错误地方之后的叙述就不会被执行。然后如果这个exception的类型有某一个适合的 except 关键词之后的类型的话,就会执行这一个except之后的叙述,然后程序从整个 try 叙述之后的地方开始执行。
w 如果所发生的exception在except关键词之后找不到相对应的类型时,系统会将这个类型传给外面一层的 try 叙述。如果外层的exception处理机制不存在的话,这就是一个没有被处理的exception( unhandled exception ),然后整个程序会中断,并出现上面出现的错误程序。
一个 try 叙述可以包含许多的except 部分来处理各种不同的exception,但是最多只有一个handler(译:exception之后的叙述)会真正被执行。Handlers 只处理在所对应的 try 部分发生的exception,其它的 try 部分发生的exception则不在处理范围。一个except子句可以处理一个以上的exception,只要用list括号把它们括起来。例如:
... except (RuntimeError, TypeError, NameError):
... pass
最后的一个 except 可以不写出exception 类型的名称,这就当作是一个外卡(wildcard,译:处理所有的exception)来使用。当使用时要特别的小心,因为如果你很有可能就把一个应该被注意的程序错误给隐藏起来了。你也可以在这个except子句里面印出一个错误信息,然后把这个exception再丢(raise)出去(让呼叫你程序的人来处理这个exception)。
import string, sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(string.strip(s))
except IOError, (errno, strerror):
print "I/O error(%s): %s" % (errno, strerror)
except ValueError:
print "Could not convert data to an integer."
except:
print "Unexpected error:", sys.exc_info()[0]
raise
这个 try ... except 的叙述有一个可有可无的else子句( else clause )可以使用,当这个子句存在时,必须是放在所有的except clauses的后面。这个子句里的叙述是当try子句没有发生任何exception时,一定要执行的叙述。请看例子:
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except IOError:
print 'cannot open', arg
else:
print arg, 'has', len(f.readlines()), 'lines'
f.close()
使用 else 要比在 try 子句里面加入多余的程序代码来的好,因为这样减少意外的处理到那些不是由 try ... except 叙述中保护的程序代码所引发的exception。
当一个exception 发生时,exception本身有一个 连带的值,也叫做这个exception的参数( argument )。至于这个参数是否存在以及其型态,则是由exception的类型所决定。对于有这个参数存在的exception类型来说,你可以在except clause的后面加入一个名称(或是list)来接收这个参数的值。请看下例:
>>> try:
... spam()
... except NameError, x:
... print 'name', x, 'undefined'
...
name spam undefined
如果一个exception 有一个参数的话,当它在没有被处理,当作错误信息印出来的时候,就会成为最后(详细解释(`detail'))的一部份。
Exception的处理者(handlers,exception clause)并不只处理在try clause当中所发生的exception,也会处理所有在try clause当中所(直接或间接)呼叫的函式所引发的exception。请看下例:
>>> 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
9.4如何引发例外(Exceptions)
使用 raise 叙述可以引发一个特定的 exception,例如:
>>> raise NameError, 'HiThere'
Traceback (innermost last):
File "<stdin>", line 1
NameError: HiThere
raise 的第一个参数是想要引发的exception的类型,第二个参数(可有可无)则是指定这个exception的参数值。
9.5使用者自订的例外(Exceptions)
程序设计师可以自己命名自己想要的excetion,其方法是指定一个字符串给一个变量,或者是自己创造一个新的exception类别来。举例说明:
>>> class MyError:
... def __init__(self, value):
... self.value = value
... def __str__(self):
... return `self.value`
...
>>> try:
... raise MyError(2*2)
... except MyError, e:
... print 'My exception occurred, value:', e.value
...
My exception occurred, value: 4
>>> raise MyError, 1
Traceback (innermost last):
File "<stdin>", line 1
__main__.MyError: 1
许多标准的module都自己自订其exception来报回(report)在他们自己所定义的函式里面所发生的错误。
有关于classes 更多的讨论请看第九章 ``类别''。
9.6定义善后的动作
在 try 叙述的机制里面有一个可有可无的子句(optional clause),其功用是在定义不管什么情况发生下,你都得要做的清除善后的工作。 举例来说:
>>> try:
... raise KeyboardInterrupt
... finally:
... print 'Goodbye, world!'
...
Goodbye, world!
Traceback (innermost last):
File "<stdin>", line 2
KeyboardInterrupt
这个 finally clause 不管你的程序在try里面是否有任何的exception发生都会被执行。当exception发生时,程序会执行finally clause之后再引发这个exception。当程序的 try try部分因为 break 或 return 而离开时,finally clause也一样会在离开的时候(``on the way out'')被执行。
一个 try 叙述机制应该要有一个或多个except clauses,或者是有一个 finally clause,但是不能两个都有。
10 Class(类别)
Python的类别机制在加入最少新的语法及语意的情况下加入了类别的支持。Python的类别机制是C++ 以及Modula-3的综合体。正如同在modules里面的情况一样,Python的 class也没有在其定义及使用者之间加入绝对的障碍,而是仰赖使用者有礼貌的不要去闯入其定义之中(not to ``break into the definition'')。对于class来说最重要的一些特性在Python里面都完全的保留:类别的继承可以继承自个基础类别(base classes),一个子类别(derived class)可以override其所有基础类别(base class)的任何方法(method),一个method也可以呼叫一个基础类别的同名方法,对象可以自由决定是否要让某些数据是 private的。
以C++ 的术语来说,Python所有的类别成员(包含其数据成员)都是 public 的,而且所有的函式成员(member functions)都是 virtual 的。也并没有所谓的建构元(constructors)或是解构元(destructors)的存在。如同在 Modula-3里面一样,从对象的方法(method)里面要使用对象的成员并没有快捷方式可以使用:成员函式的宣告必须在第一个参数中明白的在表示所存在其中的对象,而此参数在呼叫时是不用传的。如同在Smalltalk里面一样,类别本身也是一个对象,事实上在Python里面,所有的数据型态(data type)都是对象。这提供了在import以及重新命名时候的语意(sematics)。但是如同在C++ 或是Modula-3里面,内建的基本型态是不能被使用者拿来当作基础类别使用的。与C++类似但不同于Modula-3的是,大部分有特别语法的内建操作数(operators),例如数值运算及subscripting,都可以被拿来在类别中重新定义的。
10.1 术语的使用说明
由于缺乏普遍性的术语可以讨论类别,我只好偶而从Smalltalk或是C++的术语中借来用。(我其实更想用Modula-3的术语,因为它的术语在语意上比C++还要接近Python,但是我想大部分的读者都没有听过它)。
我也要警告你的是,对象这个字在Python里面不必然指的是类别的一个特例(instance),这是一个在对象导向读者中常见的陷阱。与C++及Modula-3相同但与Smalltalk不同的是,并非所有在Python里面的数据型态都是类别,像是整数及list这类的基本的内建型态就不是类别,甚至一些特别的数据型态像是file都不是类别。无论如何, 所有的 Python的数据型态都或多或少都有一些基本相同的语意特性,我们可以把这个相同点叫做对象。
对象有其个体性(individuality,独特性),而且你可以用不同的名字连结到同一个对象去,这在其它的程序语言中也叫做别名(aliasing)。通常你第一次看到Python不会觉得这有什么特别,而且你在处理不可变动的(immutable)基本型态(例如数目字,字符串及tuple)时,你根本可以不去管它。但是对于一些都可变动的(mutable)对象,像是list,dictioanry以及其它用来表现在程序之外的实体(像是档案及窗口)的数据型别,对它们来说aliasing就和与它们有关之Python程序代码语意的解释,有(故意的)一些影响。这样的影响通常是对程序有正面的效益,因为别名(alias)运作的方式就像是一个有礼貌的指标(pointer)。举例来说,当你传一个对象当参数时,因为所传的其实只是一个指标,所以所费的资源就不多。而且,当在函式之内对这个传入的对象进行修改时,在外面呼叫这个函式的人(caller)会看得见函式所做的修改,这大大的简化了在Pascal里面需要两种不同参数传递机制才能作到的事。
10.2 Python的可用范围(Scopes)及命名空间(Naming Spaces)
在介绍类别(class)之前,我首先必须介绍Python有关可用范围(scope)的一些准则。类别的定义也对命名空间(namespace)做了一些小技巧,所以你需要对scope及namespace的运作有一些了解才能够完全的掌握到底发生了什么事。对于进阶的Python程序设计师来说,有关这个主题的了解是很有帮助的。
现在让我们先来定义一些东西:
一个 namespace 指的是名称与对象的对应关系的组合。目前来说,namespace都是以Python的dictionary来实作出来的,但是这应该没有多大意义(除非对程序的效率),而且在未来可能也有所改变。Namespace的例子有:一组的内建名称(像是 abs() 的函式,还有内建的exception名称),在module里的全域变量(global variables),以及在函式里的local变量。某种意义来说,一个对象里的特性(attributes,译:成员)也组成一个namespace。在这里要知道的重点是,不同的namespace里面所定义的名称是彼此没有任何关系的。举例来说,两个不同的module都可以定义一个叫做``maximize''的函式。这一点都不冲突,因为使用者必须要在这个函式的名称前加上module的名称。
喔,我在这里所用的 attribute 一字指的事所有在点号后面的东西,举例来说在 z.real 这个expression里面 real 就是一个属于 z 这个对象的attribute。严格说来,使用module里面的名称也是一个attribute的指称(references),在 modname.funcname 这个expression里面, modname 就是一个module对象,而 funcname 就是其attribute。在这个例子里面,刚好module的attributes就对应了在module里面定义的全域变量,所以我们就说它们就是在一个namespace里面。 9.1
Attributes可以是只读的或是可改写的。对可改写的attribute,你可以设定值给它。Module的 attributes是可以改写的:所以你可以写 "modname.the_answer = 42" 来改变其值。可改写的attributes也可以被删除掉,你可以用 del 叙述像是 "del modname.the_answer " 来做。
命名空间(Name spaces)是在不同的时候被创造出来的,而且其存在的时间也都不一定。内建名称的namespace是在当Python直译器启动时就被创造出来,而且不会被删除掉。Module里全域(global)的namespace是当module的定义被读入的时候就被创造出来,通常在直译器离开之前也不会被删除。那些在top-level启动直译器里面被执行的指令,不管是在互动模式或是从script里面而来的,都隶属于一个叫做 __main__ 的module,所以它们也算有自己的一个global namespace。 (事实上,内建的名称也都在一个module里面,这个module叫做 __builtin__ )
函式所有的namespace叫做local namespace,是在函式被呼叫时才创造的,而且当函式传回一个值或是引发一个本身无法处理的exception时,这个namespace就被删除(事实上,也许说遗忘是比较贴切的形容词)。当然,递归的函式呼叫会使每个呼叫都有自己的local namespace。
一个可用范围( scope )是一个在Python程序里面文字上的范围,在这个范围里面你可以直接使用某个namespace。直接使用(``Directly accessible'')的意思是指对一个名称而言不合格的参考(unqualified reference)试图想要在namespace里面找某一个名称。
虽然scope是静态的(statically)被决定的,但是我们使用namescope的时候是很动态(dynamically)的来使用之。在任何一个程序执行的地方,都正好有三层的scope正在被使用(也就是有三个可以直接使用的namespace):首先寻找的是最内圈的scope,包含有local的名称;其次搜寻的是中间一层,包含有目前所在的module的全域名称(global names);最后搜寻的是最外面的一层,也就是包含有内建名称的namespace。
通常,local scope指的是在文字上面目前的函式所拥有的local名称。在函式之外的话,local scope就指的是global scope所指的namespace。类别的定义在local scope里面则又放入了另外的一个namespace。
要注意的是scope的决定是依文字的安排来决定的。一个定义在module里面的函式,其global scope就是module的namespace,不管这个函式是从哪里或是用哪一个别名被呼叫的。在另一方面来说,真正的名称搜寻路线是动态的决定的(在程序执行的时候)。但是Python语言本身的定义好像慢慢的往静态决定变化(也就是在编译的时候),所以,不要过分依赖动态的名称解释。(事实上,local的变量都已经是静态就已经决定了的)。
Python有一个很特别的变化就是当设定(assignment)的时候都一定是进入到了最内层的scope。设定并不是复制数据,相反的,它只是把对象及名称连结起来而已。对于删除也是一样的, "del x" 事实上只是把 x 的连结从local scope所代表的namespace中除去。事实上,所有会引进新名称的动作都是使用local scope:特别是, import叙述以及函式的定义就是把module以及函式的名称都连结到local scope里面来了。( global 这个叙述可以用来特别指定某个特殊的变量是要放在global scope里的)
10.3 Class(类别)初探
类别(Classes)的观念引进了许多新的语法,三种新的对象以及一些新的语言上的意义:
10.3.1 9.3.1 定义Class(类别)的语法
最简单的类别定义的形式看起来像是这样的:
class ClassName:
<statement-1>
.
.
.
<statement-N>
类别的定义与函式的定义(都是用 def 叙述)相同,都必须在要在它们有任何作用之前就定义好。(你也可以在 if 叙述或是一个函式里面放入类别的定义)。
在实务上,存在于类别定义内的叙述通常都是函式的定义,但是我们也可以放入其它的叙述。这样的做法有时也很好用,我们之后会再会来看这个用法。类别定义内的函式定义通常都有一个特别的参数形式,这是为了method的特别呼叫习俗的。我们还是留到后面再来讨论之。
当一个类别的定义进来时,就会创造出一个新的namespace,而且会当作是一个local scope来用。所以所有对local变量的设定都会进入到这个新的namespace里面。具体来说,函式的定义也会把新的函式的名称连结到这里来。
当一个类别的定义正常的离开时( 藉由定义的尾端),一个类别对象( class object )就被创造出来了。这个类别对象基本上来说是只是一个包装起来的东西,其内容是由这个类别定义所创造出来的namespace里面的内容。我们在下一节就会有更多有关类别对象(class objects)的讨论。另外在类别的定义离开时,原来的local scope (在进入类别的定义之前的那一个local space)就会被重新使用,并且这个创造出来的类别对象就会被放在这个local scope里面,并且被连结到你所定义的类别名称(上面的例子里是 ClassName )上面。
10.3.2 类别对象(Class Objects)
类别对象可以做两件事情,一是attribute的指涉(references),另一个是创造出一个特例来(instantiation)。
Attribute references 所使用的是在Python里面标准的attribute reference的语法: obj.name 。有效的attribute的名称指的是当类别对象被创造时,所有在类别的namespace里面的名称。所以,如果你的类别定义如同下面例子的话:
class MyClass:
"A simple example class"
i = 12345
def f(x):
return 'hello world'
你就可以使用 MyClass.i 以及 MyClass.f 这两个有效的attribute references语法,它们分别会传回一个整数以及一个method对象来。你也可以设定值给这些类别的attributes,如此你就可以改变 MyClass.i 的值了。 __doc__ 也是类别对象的一个有效的attribute,其传回值是这个类别的注释字符串(docstring),也就是: "A simple example class" 。
类别的特例化( Class instantiation )是使用函式的表示方法。看起来好像这个类别对象是一个没有参数的函式,然后传回来的就是这个类别的的一个特例(instance)。我们再以前面的类别为例子:
x = MyClass()
就会创造出一个新的类别的 instance ,然后我们再把这个对象设定给 x 这个local的变量。
类别的特例化(Class instantiation )这个动作(也就是``呼叫''一个类别对象)所创造出来的是一个空的对象。有许多的类别希望创造出来的对象有一个特定的初始状态,所以你可以在类别里面定义一个特别的method叫做 __init__() ,如同下例:
def __init__(self):
self.data = []
当你的类别有定义一个 __init__() method时,当你在特例化(instantiation)你的类别时,就会自动的引发 __init__() 执行,并且创造出一个类别的特例(instance)。所以,一个新的对象就可以截由底下的呼叫来创造出来:
x = MyClass()
当然, __init__() 这个method 可以有参数传入,这样可以增加使用时的弹性。在这样做的时候,使用特例化(instantiate)类别的语法时,所传入的参数就会被传到 __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)
10.3.3 特例物件(instance objects)
现在对于这个被创造出来的特例对象(instance objects),我们又该怎么用呢?对于这样的特例对象,唯一它们懂得的就是attribute references。有两种的attribute names我们可以使用:
第一种我叫他是数据特性( data attributes ),这类似于在Smalltalk中所说的特例变量(``instance variables'')以及在C++中的数据成员(``data members'')。如同local变量一样,Data attributes不需要再宣告,你第一次设定值给它们的时候它们就自动存在了。举例来说,如果 x 是 MyClass 这个对象的一个instance,底下这个程序代码就会印出 16 这个结果来:
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print x.counter
del x.counter
第二种instance objecet可以使用的attribute references叫做方法( methods ) 。一个method就是一个隶属于某个对象的函式。(在Python中,method一词并不只特定用于类别的instances,其它的对象数据型态也可以有自己的 method,例如list对象就有很多methods像是append,insert,remove,sort等等。但是我们底下用到method这个词的时候,除非特别说明,要不然我们倒是单独指着instance objects 的method说的。)
一个instance objects 可以用的有效method名称是由其类别所决定的。定义上来说,所有类别里面(使用者定义)为函式对象的attribute,都会成为其instance的相对应method。所以在我们的例子里, x.f 就是一个有效的method的reference,其原因是因为 MyClass.f 是一个函式;但是 x.i 就不是一个method的reference,因为 MyClass.i 不是一个函式。但是,要注意的是, x.f 和 MyClass.f 是两回事,它是一个method物件( method object ),而非一个函式物件。
10.3.4 Method Objects(方法对象)
通常,一个method可以马上被呼叫,例如:
x.f()
在我们的例子里,这一个呼叫会传回来 'hello world' 这个字符串。但是,因为 x.f 是一个method对象,所以我们没有必要马上就呼叫它,我们可以把它储存起来,然后再稍后再呼叫它。举例如下:
xf = x.f
while 1:
print xf()
这个例子同样的会一直不断的印出 "hello world" 来。
到底,你的method被呼叫时,什么事情发生了呢?你也许注意到了 当我们呼叫 x.f() 的时候并没有传入任何参数,但是我们在类别定义的时候确实有定义 f 所传入的参数。到底是怎么回事呢?当然,依照Python的定义,当一个函是需要参数而你呼叫时没有传入参数的话,是会引发一个例外状况(exception)的,甚至这个传入的参数没有被用到也是一样…
事实上,你也许已经猜到答案了。对于method来说有一个较特殊的事是,method所处的对象会被当作函式传入的第一个参数。所以在我们的例子里面,当我们呼叫 x.f() 的时候,事实上我们是呼叫 MyClass.f(x) 。一般来说,如果你呼叫method的时候传了 n 个参数,其实你是呼叫背后所代表之类别的函式,而且该method所在的对象会插入在传入的参数中当作第一个参数。
如果你还不了解到底method如何运作的话,你也许可以看看它的实作来更了解它。当一个instance的attribute被reference,而这个attribute又不是一个data attribute的时候,该instance的类别会被寻找。如果这个class attribute的名字在类别里面代表的是一个函式对象的话,就会有一个method对象被创造出来。这个method对象是一个由这个instance对象(的指针),以及刚刚找到的这个函式对象所包装起来的一个抽象的对象。当这个method对象被带着一串参数呼叫的时候,这个method对象会先打开原来的包装,然后会用instance对象(的指针)以及那一串传进来的参数组成新的参数串,然后我们再用这个新的参数串来呼叫在method对象里面的函式对象。
10.4 9.4 一些随意的想法
[这些东西其实应该更多花点心思加以处理的…]
如果data attributes和method attributes 有相同名称的话,data attributes 会盖过method attributes 。要避免这个命名的冲突(这常常是许多bug的由来),你可能需要一些命名的规则。比如说,让method的名称都是大写的,在data attribute的前面加上一些小字符串(或者是底线),或者对于method都用动词,对data attribute都用名词。
除了一般object的使用者(client)之外,Data attributes也可以在method里面被使用到。也就是说,类别(class)是不能用来实作出纯粹的抽象数据型态(abstract data types)的。事实上,再Python里面没有东西可以保证数据的隐藏(data hiding),我们只能仰赖彼此的约定及尊重了。(另一方面来说,用C写成的Python是可能完全隐藏其实作的细节并且在需要的时候可以控制对对象的存取权限的;这是用来给C所写成的Python延伸机制(extension to Python)所使用的。)
使用data attributes的人要特别小心,你有可能把由method所管理的data attributes弄得一蹋胡涂。值得注意的是,类别的使用者可以自行在instance对象里面加入data attributes,只要小心处理命名的问题,这不会对method的正确性有所影响。再次提醒,你可以用命名的规则来避免此事发生。
从method里面要使用data attributes (或者是其它的methods)并没有快捷方式。我发现这样的好处是程序的可读性会增加很多,因为当你在读method的程序代码的时候,local变量跟instance变量混淆的机会就会少很多。
习惯上,我们把一个method的第一个参数叫做 self 。这只是一个习惯而已, self 这个名字对Python来说完全没有什么特殊的意义。(但是你要注意,如果你不用这一个习惯的话,对于某些读你程序的Python程序设计师来说,也许你程序的可读性就低了一点。而且可能有一些类似像 class browser 之类的程序是靠这个约定来分辨class的特性,所以你不遵守的话,你的类别它们可能就读不懂)
所有的在类别里面的函式对象,在定义上都是该类别之instance的一个method。在类别里面的意思不限定于一定要在文字上是在类别的定义里面,你也可以把一个函式对象设定给一个在类别里面的local变量,这样也算数的。比如说:
# 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 attributes,而且都指涉(reference)到某个函式物件去。而且,如此做的话,当 C 有instance的时候, f , g 以及 h 都会变成instance的method(事实上 h 所指的函式是跟 g 同一个的)。值得注意的是,如果你真这样做的话,你只是让读你程序的人头昏眼花罢了。
你也可以在method里面呼叫其它的method,你所需要的只是用 self 这个参数的method attribute就可以了。例如:
class Bag:
def __init__(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)
method跟一般的函式对象一样可以使用全域名称(global name)。Method的global scope所指的是类别的定义所存在的module,(注意:类别本身绝不会是一个global scope!)。 你大概很少有机会在method里面会需要用到global scope,但是你还是可以使用global scope的,method可以使用在global scope之中所import进来的函式以及module,也可以使用在global scope里面定义的函式及类别。通常,包含method的这个类别本身就定义在这个global space里面,而且下一段我们就要讲到为什么你会需要在method里面用到自己本身的类别。
10.5 继承(Inheritance)
当然啦,一个程序语言如果没有继承的话就不需要担心类别(``class'')这个字了。一个子类别(derived class)的定义看起来是这样的:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
其中,基础类别的名字 BaseClassName 这个字必须是在子类别所处的scope里面有定义的。除了直接使用基础类别的名字之外,你也可以使用一个expression。这在当你的基础类别是定义在别的module里的时候特别有用:
class DerivedClassName(modname.BaseClassName):
子类别定义的执行过程与基础类别定义的执行过程是一样的。当一个类别对象被创造出来时,基础类别也同样会存在内存中。这是为了要确保能够找到正确的attribute的所在,如果你的子类别没有定义某个attribute的话,就会自动去找基础类别的定义。如果这个基础类别也是某个类别的子类别的话,这个法则是一直延伸上去的。
子类别的特例化(instantiation)也没有什么特别之处,使用 DerivedClassName() 就会创造出子类别的一个新的instance。子类别的method 则是由以下的过程来寻找:会先找该类别的attribute,然后如果需要的话会沿着继承的路线去找基础类别,如果找到任何的函式对象的话,这个method的参考(reference)就是有效的。
子类别可以override基础类别里的method。因为method在呼叫自己对象的其它method的时候没有特别的权限,当一个基础类别的method呼叫原属于该基础类别的method的时候,有可能真正呼叫到的是一个在子类别里面定义的override的method。(给C++的程序设计师们:所有在Python里面的method都是 virtual 的。)
一个在子类别里面override的method也许会需要延伸而非取代基础类别里面同名的method,这时候你就需要呼叫在基础类别里面的method:你只需要呼叫 "BaseClassName.methodname(self, arguments)" 就可以了。这对于类别的使用者来说,有时候也是有用的。(注意的是,如果你要这样做,你需要将基础类别定义在global scope或是import进来global scope 里面。)
10.5.1 多重继承
Python也支持部分的多重继承形式。一个类别如果要继承多个基础类别的话,其形式如下:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
唯一需要解释的规则是,当你寻找一个attribute的定义时你要如何寻找。其规则是先深,而后由左至右(depth-first, left-to-right)。所以当你要找一个在子类别 DerivedClassName 里面的attribute却找不到时,会先找 Base1 ,然后沿着 Base1 的所有基础类别寻找,如果找完还没有找到的话再找 Base2 及其基础类别,依此类推。
(也许有些人认为先左至右然后在深才对,应该是先找 Base2 及 Base3 ,然后才找 Base1 的基础类别。如果你这样想的话,你可以再想一想,当你找 Base1 的时候,你需要先知道这个attribute到底是定义在 Base1 本身或是其基础类别里面,如此才不会与 Base2 里面的attribute有同名的困扰。如果你使用先深,而后由左至右的规则的话,就不会有这个困扰。)
大家都知道如果不小心使用的话,多重继承可能变成在维护程序时的一个恶梦。Python仰赖程序设计师们的约定俗成的习惯来避免可能的名称冲突。例如一个众所周知多重继承的问题,如果一个类别继承了两个基础类别,这两个基础类别又分别继承了同一个基础类别。也许你很容易就了解在这样的情况下到底会是什么状况,(这个instance将会只有一个单一共享基础类别的``instance variables''或是data attributes),但是很难了解这到底有什么用处。
10.6 Private变数
在Python里面只有有限度的支持类别中的private指称 (class-private identifiers,译:指变数及函式)。任何的identifier,在之前是以 __spam 这个形式存在的(最前面至少要有两个底线,最后面最多只能有一个底线) 现在都要以 _classname__spam 这个形式来取代之。在这里的 classname 指的是所在的类别名称,拿掉所有前面的底线。这个名称的变化不受限于这个identifier其语法上所在的位置,所以可以套用在定义类别的private instance,类别变量,method,global名称,甚至用来储存 其它 的类别instance里,对目前这个类别来说是private的instance变量。当这个变化过的名称超过255个字符时,有可能超过的部分是会被截掉的。在类别之外,或者是当类别的名称只包含底线的时候,就没有任何名称的变化产生。
这个名称的变化主要是用来给类别有一个简单的方法来定义``private''的instance变量及methods,而不需要担心其它子类别里面所定义的instance变量,或者与其它的在类别之外的程序代码里的instance变量有所混淆。注意的是这个变化名称的规则主要是用来避免意外的,如果你存心要使用或修改一个private的变量的话,这还是可行的。某方面来说这也是有用的,比如说用在除错器(debugger)上面,这也是为什么这个漏洞没有被补起来的一个原因。(如何制造bug:如果一个类别继承自某个基础类别时用了相同的名字,这会使得你可以从子类别里面使用基础类别里的private的变量。)
值得注意的是,被传到 exec, eval() 或 evalfile() 的程序代码并不用考虑引发这个动作的类别是目前的类别,这是相类似于 global 叙述的效果,但是这个效果只限于这个程序代码是一起被编译器编译成bytecode的时候的。同样的限制也存在于 getattr() , setattr() 以及 delattr(),或是当直接使用 __dict__ 的时候。底下这个例子是一个类别里面定义了自己的 __getattr__() 以及 __setattr__() 两个方法,并且把所有的attributes都储存在private的变量里面。这个例子适用于所有的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
10.7 其它
有的时候如果有一个像是Pascal的``record'',或者是C的``struct''这类的数据型态是很方便的,这类的数据型态可以把一些的数据成员都放在一起。这种数据型态可以用空白的类别来实作出来,例如:
class Employee:
pass
john = Employee() # Create an empty employee record
# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
如果有一段的Python程序代码需要一个特别的抽象数据型态的时候,通常你可以传给这段程序代码一个类似功能的类别来代替。例如,如果你有一个函式是用来格式化一些来自于file对象的数据,你可以定义一个类别,类别里面有类似 read() 以及 readline() 之类method可以从一个字符串缓冲区(string buffer)读出数据,然后再把这个类别传入函式当作参数。
Instance的method对象也可以有attributes: m.im_self 就是其method为instance的一个对象, m.im_func 就是这个method相对应的函式对象。
10.7.1 例外(Exceptions)也可以是类别
使用者自订的exception不用只是被限定于只是字符串对象而已,它们现在也可以用类别来定义了。使用这个机制的话,就可以创造出一个可延伸的屋exception的阶层了。
有两个新的有效的(语意上的)形式现在可以用来当作引发exception的叙述:
raise Class, instanceraise instance
在第一个形式里面, instance 必须是 Class 这个类别或其子类别的一个instance。第二种形式其实是底下这种形式的一个简化:
raise instance.__class__, instance
所以现在在except的语句里面就可以使用字符串对象或是类别都可以了。一个在exception子句里的类别可以接受一个是该类别的exception,或者是该类别之子类别的exception。(相反就不可以了,一个except子句里如果用的是子类别,就不能接受一个基础类别的exception。)例如,下面的程序代码就会依序的印出B, C, D来:
class B:
passclass C(B):
passclass D(C):
passfor 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子句被执行而已。
当一个没有被处理到的exception是一个类别时,所印出来的错误信息会包含其类别的名称,然后是(:),然后是这个instance用内建的 str() 函式转换成的字符串。
Footnotes... 一个namespace里面。 9.1
除了一件事之外。module对象有一个秘密的attribute叫做 __dict__ ,这个attribute会传回这个module的namespace所对应的dictionary。 __dict__ 这个名字就是一个attribute,但却不是一个global的名称。很明显的,使用这个attribite将会破坏这个namespace命名的抽象性(abstraction),所以应该只限制于像是检验尸体一样的除错器使用。
11 现在呢
希望读了这个教学文件已经加强了你对于使用Python的兴趣。那么,现在你应该要做什么呢?
你应该要读,或者至少应该翻阅过链接库参考手册,这会给你一个完整(虽然是简洁)的有关型态,函式,模块等的参考数据。而且当你在写Python程序的时候,会节省你很多的时间。标准Python发布的版本(distribution)里面,都包含了 许多的 C以及Python的程序代码。有用来读Unix信箱(mailbox)的module,从HTTP读取文件的module,以及分析档案列选项(command-line options),写CGI程序,压缩数据以及许多其它的module。如果你浏览过这个链接库参考手册的话,你至少会有一个初步的印象到底有哪些链接库可以用。
最主要的Python网站是 http://www.python.org/ ,其中包含了许多的程序代码,文件,以及许多其它有关Python的网页的连结。这个网站在世界的很多地方也有mirror,例如日本,欧洲以及澳洲。你也许可以找看看跟你的地区比较近的mirror站台,也许速度会快一些。另外一个非正式的网站是在 http://starship.python.net/ ,里面有一大堆的Python相关的个人网页,有许多人也从那里下载程序。
如果你有跟Python相关的问题,或是要报告问题的话,你可以post到 comp.lang.python 这个新闻群组,或者直接把你的问题送到 python-list@python.org 这个mailing-list。这个新闻群组及mailing list是相连起来的,所以你送到其中一个都会在两个都看得到。它们大概一天都有约120个新的 postings,其中包括问(及回答)问题,建议新功能,以及宣布新的module等等。在你要post之前,你应该先看看常见问题(Frequently Asked Questions (也叫做FAQ)),其网页在 http://www.python.org/doc/FAQ.html ,或者查看你的Python source distribution里面的 Misc/ 目录。Mailing list之前的存档也都在 http://www.python.org/pipermail/ 。但是FAQ里面已经包含了许多的一再被问到的问题,所以很有可能已经有你的问题的答案。
12 A. 交互式输入编辑及代换过去的内容
有些的Python的直译器版本有支持目前所在行的编辑,以及过去输入内容的代换,这跟在Korn Shell以及GNU Bash shell里面的一些功能有点像。这个功能是用 GNU Readline 的链接库做出来的,提供了类似Emacs以及类似vi的编辑功能。这个链接库本身有自己的参考文件,所以我在此不再重复,我只是简单的介绍基本的功能。这里所介绍的互动模式输入编辑及代换过去内容的功能通常在Unix以及CygWin的直译器版本都可以见得到。
本章 没有 包含在Mark Hammond的PythonWin package里的编辑功能,也没有包含在标准Python distribution里面的Tk之类的环境,或是IDLE的编辑功能。在DOS以及NT及其它类似的环境下,也有命令列过去内容记忆的功能,但是也不在本文的内容之中。
12.1 A.1 整行编辑
如果有支持整行编辑的话,当直译器印出primary prompt或是secondary prompt的时候,整行编辑的功能就会被启动起来。你可以用一般的Emacs控制字符来编辑目前所在的行。其中常见的有这些: C-A (Control-A)会移动cursor到目前行的最开头的地方, C-E 会移动到最尾端, C-B 会往左移动一个位置, C-F 会往右边一个位置。Backspace键会消去目前cursor所在处左边的一个字符, C-D 会消去右边的一个字符, C-K 会杀掉 (消去)本行在光标之后的所有字符, C-Y 会把刚刚所杀掉的字符串再次贴回来, C-underscore 会取消刚才所做的最后一个动作,这个功能也可以重复多次的使用。
12.2 A.2 代换过去的内容
要取代过去的内容其方法如下:所有的非空白行都会被储存在一个储存过去内容的缓冲区(history buffer)里面,当prompt出现的时候,你所在的位置就是在这个buffer的最底下,使用 C-P 会使得在buffer里面往上移动一行, C-N 会往下移动一行。任何在buffer里面的行都可以被编辑,若是在prompt之前出现星号的话就代表这个行已经被修改过了。当你按下 Return 键的时候就是把目前这一行送给直译器了。 C-R 会开始一个逐渐往上的搜寻,按下 C-S 会开始往前的搜寻。
12.3 A.3 键盘连结
键盘的连结以及一些其它有关Readline library的参数都可以被修改,其方法是在一个叫做 ~/.inputrc 的初始化档案中打入一些指令。键盘的连结有以下的几种形式:
key-name: function-name
或是
"string": function-name
你也可以设定一些选项:
set option-name value
请看下面的例子:
# I prefer vi-style editing:
set editing-mode vi
# Edit using a single line:
set horizontal-scroll-mode On
# Rebind some keys:
Meta-h: backward-kill-word
"\C-u": universal-argument
"\C-x\C-r": re-read-init-file
注意的是在Python里面预设的 Tab 键所连结的是输入一个 Tab 字符,而非Readline 链接库里面预设的文件名自动完成的功能。如果你坚持的话,你也可以这样子设定来盖过Python的预设:
Tab: complete
这应该在你的 ~/.inputrc 里面。(当然如果你这样做的话,你在缩排连续行的时候就费力一些了。)
自动完成变量及module的名称的功能是可以自由选择要或不要的。如果你要在互动模式下启动这一个功能的话,可以在启动文件里面加入底下的指令: A.1
import rlcompleter, readline
readline.parse_and_bind('tab: complete')
这样做的话会使TAB 键连结到完成的功能,所以按下TAB键两次就会建议一些完整的名称。其所搜寻的是Python的叙述名称,目前的local变量,以及可用的module名称。对于像 string.a 这样带有点的expression,这项功能会先是着先evaluate到最后一个点的意思,然后再从所得的对象中建议可用的变量名称。注意的是,如果这个对象含有的 __getattr__() 这个method是这个expression的一部份的话,就会执行一些特定的程序代码。
12.4 A.4 评注
这个功能跟其它之前的直译器版本比起来是很大的一个进步,但是我们还有很多希望有的功能。如果在连续行的时候可以建议适当的缩排距离(分析器(parser)应该知道何时需要缩排)。自动完成的功能应该也能够使用直译器的symbol table。应该也要有一个指令可以检查(甚至是建议)何时应该要有结束的括号,括号等等。
脚注... 底下的指令: A.1
当你启动一个Python的交互式直译器时,Python会自动执行在 $PYTHONSTARTUP 这个系统环境变量所代表的档案里面的指令。
___
posted on 2013-09-11 09:50 roland1982 阅读(1164) 评论(0) 编辑 收藏 举报