Python入门
人气:0脚本语言是类似DOS批处理、UNIX shell程序的语言。脚本语言不需要每次编译再执行,并且在执行中可以很容易地访问正在运行的程序,甚至可以动态地修改正在运行的程序,适用于快速地开发以及完成一些简单的任务。在使用脚本语言时常常需要增的新的功能,但有时因为脚本语言本来就已经很慢、很大、很复杂了而不能实现;或者,所需的功能涉及只能用C语言提供的系统调用或其他函数——通常所要解决的问题没有重要到必须用C语言重写的程度;或者,解决问题需要诸如可变长度字符串等数据类型(如文件名的有序列表),这样的数据类型在脚本语言中十分容易而C语言则需要很多工作才能实现;或者,编程者不熟悉C语言:这些情况下还是可以使用脚本语言的。
在这样的情况下,Python可能正好适合你的需要。Python使用简单,但它是一个真正的程序语言,而且比shell提供了更多结构和对大型程序的支持。另一方面,它比C提供更多的错误检查,它是一个非常高级的语言,内置了各种高级数据结构,如灵活的数组和字典,这些数据结构要用C高效实现的话可能要花费你几天的时间。由于Python具有更一般的数据结构,它比Awk甚至Perl适用的范围都广,而许多东西在Python内至少和在这些语言内一样容易。
Python允许你把程序分解为模块,模块可以在其他Python程序中重用。它带有一大批标准模块可以作为你自己的程序的基础——或作为学习Python编程的例子。系统还提供了关于文件输入输出、系统调用、插座(sockets)的东西,甚至提供了窗口系统(STDWIN)的通用接口。
Python是一个解释性语言,因为不需要编译和连接所以能节省大量的程序开发时间。解释程序可以交互使用,这样可以可以很容易地试验语言的各种特色,写只用一次的程序,或在从底向上程序开发中测试函数。它也是一个方便的计算器。
Python允许你写出非常严谨而且可读的程序。用Python写的程序通常都比相应的C程序要短,因为如下几个理由:
高级的数据结构允许你用一个语句表达复杂的操作;
复合语句是靠缩进而不是用表示开始和结束的括号;
不需要变量声明或参量声明。
Python是可扩充的:如果你会用C语言编程就很容易为解释程序增加新的内置函数或模块,这样可以以最快速度执行关键操作,或把Python程序和只能以二进制码提供的库(如不同厂商提供的图形库)连接起来。当你变得确实很在行时你可以把Python解释器与用C写的应用相连接,把它作为该应用的扩展或命令语言。
Python的命名是由BBC的“Monty Python's Flying Circus”节目而得,与蟒蛇没有什么关系。
第二章 解释程序的使用
在命令行键入
python
或在Windows环境下双击相应的图标可以进入Python的解释程序。如果要运行储存在文件中的Python程序,可以用
python 文件名
的形式。
进入解释程序的环境后,解释程序称为处于交互状态。在这种状态下系统用 主提示提示输入下一个命令,这一般是三个大于号(>>>),如果需要续行系统用 次提示提示输入,缺省为三个小数点(...)。在主提示下键入文件尾符号(在UNIX中为Control-D,在DOS或Windows中为Control-Z)可以正常退出解释程序。
Python解释程序的有些版本支持命令行编辑和命令历史,使用用Emacs或vi的键组合。
第三章 基本使用
下面我们用例子来介绍Python的基本用法。在例子中,用户输入和系统输出靠有没有提示(>>>和...)来分别。如果要试这些例子的话,需要键入提示后的所有命令,例子中没有提示的行是系统的输出。注意只有次提示的行意味着需要键入一个空行,这用于结束多行命令。
3.1 用Python作计算器使用
启动解释程序,等待主提示>>>出现。解释程序可以作为计算器使用。键入一个表达式,解释程序就可以输出结果。表达式的写法很直观:+,-,*,/, %, **等算符的作用和其它大多数语言(如Pascal或C)没什么差别,括号可以用来组合。例如:
>>> 2+2
4
>>> # 这是一个注释
... 2+2
4
>>> 2+2 # 和代码在同一行的注释
4
>>> (50-5*6)/4
5
>>> # 整数除法得下面的整数
... 7/3
2
>>> 7/-3
-3
>>>
和C中一样,等于号用来给变量赋值,赋值的结果不显示:
>>> width = 20
>>> height = 5*9
>>> width * height
900
>>>
可以同时给几个变量赋同一个值:
>>> x = y = z = 0 # 把 x, y 和 z赋零
>>> x
0
>>> y
0
>>> z
0
>>>
Python完全支持浮点数,混合类型的运算会把整数先转换成浮点数:
>>> 4 * 2.5 / 3.3
3.0303030303
>>> 7.0 / 2
3.5
>>>
Python也提供了复数,方法是用j和J作为虚数单位,如1+1j,3.14e-10j,等等。
3.2. 字符串
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.'
>>>
字符串输出格式与输入的样子相同,都是用撇号包裹,撇号和其它特殊字符用用反斜杠转义。如果字符串中有单撇号而没有双撇号则用双撇号包裹,否则应该用单撇号包裹。后面要介绍的print语句可以不带撇号或转义输出字符串。
字符串可以用+号连接起来,用*号重复:
>>> word = 'Help' + 'A'
>>> word
'HelpA'
>>> '<' + word*5 + '>'
'<HelpAHelpAHelpAHelpAHelpA>'
>>>
字符串可以象在C中那样用下标索引,字符串的第一个字符下标为0。
Python没有单独的字符数据类型,一个字符就是长度为一的字符串。象在Icon语言中那样,可以用片段(slice)记号来指定子串,片段即用冒号隔开的两个下标。
>>> word[4]
'A'
>>> word[0:2]
'He'
>>> word[2:4]
'lp'
>>>
片段有很好的缺省值:第一下标省略时缺省为零,第二下标省略时缺省为字符串的长度。
>>> word[:2] # 前两个字符
'He'
>>> word[2:] # 除前两个字符串外的部分
'lpA'
>>>
注意s[:i] + s[i:] 等于 s 是片段运算的一个有用的恒等式。
>>> word[:2] + word[2:]
'HelpA'
>>> word[:3] + word[3:]
'HelpA'
>>>
不合理的片段下标可以很好地得到解释:过大的下标被换成字符串长度,上界小于下界时返回空串。
>>> word[1:100]
'elpA'
>>> word[10:]
''
>>> word[2:1]
''
>>>
下标允许为负数,这时从右向左数。例如:
>>> word[-1] # 最后一个字符
'A'
>>> word[-2] # 倒数第二个字符
'p'
>>> word[-2:] # 最后两个字符
'pA'
>>> word[:-2] # 除最后两个字符外的部分
'Hel'
>>>
但要注意的是 -0 实际还是 0,所以它不会从右向左数!
>>> word[-0] # (因为 -0 等于 0)
'H'
>>>
超出范围的片段下标被截断,但在非片段的情况下不要这样:
>>> word[-100:]
'HelpA'
>>> word[-10] # 错误
Traceback (innermost last):
File "<stdin>", line 1
IndexError: string index out of range
>>>
记住片段意义的最好方法是把下标看成是字符 之间的点,第一个字符的左边界号码为0。有n个字符的字符串的最后一个字符的右边界下标为n,例如:
+---+---+---+---+---+
| H | e | l | p | A |
+---+---+---+---+---+
0 1 2 3 4 5
-5 -4 -3 -2 -1
第一行数字给出字符串中下标0到5的位置,第二行给出相应的负下标。从i到j的片段由在边界i和j之间的字符组成。
对于非负下标,如果下标都在界内,则片段的长度为下标的差。例如,word[1:3] 的长度为 2。
内置函数len()返回字符串的长度:
>>> s = 'supercalifragilisticexpialidocious'
>>> len(s)
34
>>>
多行的长字符串也可以用行尾反斜杠续行,续行的行首空白不被忽略,如
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.
对于特别长的字符串(比如包含说明的几段文字),如果用上面的方式每行都用\n\结尾是很麻烦的,特别是这样无法用象Emacs这样的功能强大的编辑器重新编排。对这种情况,可以使用三重撇号,例如
hello = """
This string is bounded by triple double quotes (3 times ").
Unescaped newlines in the string are retained, though \
it is still possible\nto use all normal escape sequences.
Whitespace at the beginning of a line is
significant. If you need to include three opening quotes
you have to escape at least one of them, e.g. \""".
This string ends in a newline.
"""
三重撇号字符串也可以用三个单撇号,没有任何语义差别。
多行的字符串常量可以直接连接起来,字符串常量之间用空格分隔则在编译时可以自动连接起来,这样可以把一个长字符串连接起来而不需要牺牲缩进对齐或性能,不象用加号连接需要运算,也不象字符串串内的换行其行首空格需要保持。
3.3 列表
Python中有几种复合数据类型,用来把其它值组合到一起。其中最灵活的是列表,可以写成在方括号之间用逗号隔开的若干值(项)。列表的项不必取同一类型。
>>> a = ['spam', 'eggs', 100, 1234]
>>> a
['spam', 'eggs', 100, 1234]
>>>
象字符串下标那样,列表下标从0开始,列表可以取片段,可以连接,等等:
>>> 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!']
>>>
与字符串不同的是列表是可变的,可以修改列表的每个元素:
>>> a
['spam', 'eggs', 100, 1234]
>>> a[2] = a[2] + 23
>>> a
['spam', 'eggs', 123, 1234]
>>>
也可以给一个片段重新赋值,这甚至可以改变表的大小:
>>> # 替换若干项:
... a[0:2] = [1, 12]
>>> a
[1, 12, 123, 1234]
>>> # 去掉若干项:
... a[0:2] = []
>>> a
[123, 1234]
>>> # 插入若干项:
... a[1:1] = ['bletch', 'xyzzy']
>>> a
[123, 'bletch', 'xyzzy', 1234]
>>> a[:0] = a # 在开头插入自身
>>> a
[123, 'bletch', 'xyzzy', 1234, 123, 'bletch', 'xyzzy', 1234]
>>>
内置函数也使用于列表:
>>> len(a)
8
>>>
可以建立嵌套列表(表的元素也是列表),如:
>>> q = [2, 3]
>>> p = [1, q, 4]
>>> len(p)
3
>>> p[1]
[2, 3]
>>> p[1][0]
2
>>> p[1].append('xtra') # 列表方法
>>> p
[1, [2, 3, 'xtra'], 4]
>>> q
[2, 3, 'xtra']
>>>
注意这个例子中p[1]和q实际是同一个对象!也就是说它们只不过是同一个东西的两个名字(引用)而已。
3.4 编程初步
Python当然不是只能用来把两个数加到一起,它可以完成很复杂的工作。例如,我们可以写出Fibonacci序列的开始几个:
>>> # Fibonacci 序列:
... # 两个元素的和定义下一个
... a, b = 0, 1
>>> while b < 10:
... print b
... a, b = b, a+b
...
1
1
2
3
5
8
>>>
这个例子介绍了几个新特色。
第一行包含一个多重赋值: 变量a和b同时得到新值0和1。在最后一行又用了多重赋值,我们可以看出赋值时先把右边都算出后再进行赋值。
while循环当循环条件(这里即: b < 10)成立时不断执行。在Python中和C中一样,非零整数值为真值,零为假值。条件也可以是字符串或列表或任何序列,长度为非零的为真,空序列为假。例子中所用的是一个简单比较。标准的比较算符和C一样:
<, >, ==, <=, >= 和 !=。
循环体是缩进的:缩进是Python用来组合语句的方式。Python目前还不能提供智能自动缩进,所以你需要自己为每个缩进行键入制表符或空格。实际使用中你可以用文本编辑程序为Python 准备复杂的输入,多数文本编辑程序都有自动缩进的功能。在交互输入复合语句时必修附加一个空行以指示复合语句的完成(因为解释程序无法猜到哪是语句的最后一行)。print语句显示后面的表达式值。这和直接写出表达式不同,它可以显示多个表达式和字符串,而且可以用于程序文件中。显示时字符串没有撇号,各项目之间插入一个空格,所以你可以以精美的格式显示,如:
>>> i = 256*256
>>> print 'The value of i is', i
The value of i is 65536
>>>
在尾部写一个逗号可以避免最后换行:
>>> 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
>>>
注意如果前一行没有结束的话系统在显示提示之前先换行。
Python还提供了和C语言一样的printf格式的输出方式,这是用%实现的,左边是格式,如:
>>> print 'The value of 1/7 is approximately %5.3f.' % 0.142857
The value of 1/7 is approximately 0.143.
>>>
如果有多个需要输出的项百分号右边的项可以是一个序组,如
>>> print "Name: %-10s Age: %3d" % ("White", 31)
Name: White Age: 31
第四章 流程控制
前面我们已经见到了如何由用while结构控制流程运行。这一章我们介绍更多的控制结构。Python具有和其它语言类似的控制结构但略有差别。
4.1 If 语句
If 语句可能是最基本的程序分支语句了。例如:
>>> 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的缩写,这样可以缩短语句行长度。其它语言中switch 或 case 语句可以用if...elif...elif...语句组来实现。
4.2 for 语句
Python中的for语句与你可能熟悉的C或者Pascal中的相应语句略有不同。不象Pascal 那样总是对数字序列进行循环,也不是象C中那样完全由程序员自由地控制循环条件和循环体,Python的for循环是对任意种类的序列(如列表或字符串)按出现次序遍历每一项。例如:
>>> # 计算字符串长:
... a = ['cat', 'window', 'defenestrate']
>>> for x in a:
... print x, len(x)
...
cat 3
window 6
defenestrate 12
>>>
尽量不要在循环体内修改用来控制循环的序列(当然,只有可变的序列类型如列表才有可能被修改),这样程序可能会出问题。如果需要这样,比如说要复制某些项,可以用序列的副本来控制循环。片段记号让你很容易生成副本:
>>> for x in a[:]: # 生成整个列表的片段副本
... if len(x) > 6: a.insert(0, x)
...
>>> a
['defenestrate', 'cat', 'window', 'defenestrate']
>>>
结果是把列表中长度超过6个字符的字符串插入到列表开头。
4.3 range() 函数
如果确实需要对一列数字进行循环的话,可以使用内置函数range()。它生成包含数字序列的列表,如:
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
注意给出的终点永远不出现在生成的列表中,range(10)生成一个十个数的列表,恰好是长度为10的序列的合法下标的各个值。也可以指定不同的起始点,或者指定不同的间隔(甚至负数):
>>> range(5, 10)
[5, 6, 7, 8, 9]
>>> range(0, 10, 3)
[0, 3, 6, 9]
>>> range(-10, -100, -30)
[-10, -40, -70]
>>>
为了对序列的下标进行循环,如下联合使用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
>>>
4.4 break语句,continue语句和循环中的else子句
如同C语言一样,break语句跳出其所处的最内层的for 或 while循环,continue语句继续下一循环步。
循环语句还可以带一个 else 子句,当循环正常结束时执行其内容,但如果循环是用break 语句跳出的则不执行其内容。下例说明了这种用法,此例求素数:
>>> 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
>>>
4.5 pass 语句
pass语句不执行任何操作,当语法要求一个语句而程序不需要执行操作时就用此语句。例如:
>>> while 1:
... pass # 等待键盘中断
...
4.6 函数定义
我们可以定义一个函数用来计算某一界限以下的所有Fibonacci序列值:
>>> def fib(n): # 写出 n 以下的所有Fibonacci序列值
... a, b = 0, 1
... while b < n:
... print b,
... a, b = b, a+b
...
>>> # 调用刚刚定义的函数:
... fib(2000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
>>>
其中关键字 def 开始一个函数定义,其后应该是函数名,括号内的形参表,以冒号结束。构成函数体的各语句从下一行开始,用一个制表符缩进。函数的第一个语句可以是一个字符串,如果是的话,这个字符串就是函数的文档字符串,简称为docstring。有一些工具可以利用文档字符串自动生成可打印的文档,或者让用户交互地浏览代码,所以在自己编程时加入文档字符串是一个好习惯,应该养成这样的习惯。
函数在执行时对局部变量引入一个新的符号表。函数中的变量赋值都存入局部符号表;引用变量时变量名先从局部符号表中查找,然后在全局符号表中查找,最后从内置的名字中查找。因此,在函数中不能直接对全局变量赋值(除非用了global语句来说明),但可以引用全局变量的值。
函数调用的实参被引入函数的局部符号表,即函数的参数是按值调用的。函数再调用其它函数时为该函数生成一个新的符号表。但是严格地说,函数的调用是按引用调用的,因为如果参数是一个可变类型如列表的话在函数中改变形参的内容将导致实参的内容被改变(不改变的是实参名字的绑定关系)。
函数定义把函数名放入当前符号表。函数名的值类型为用户自定义函数,这个值可以赋给另一个名字,从而这个名字也代表相同的函数。这可以作为一般的改名方法:
>>> fib
<function object at 10042ed0>
>>> f = fib
>>> f(100)
1 1 2 3 5 8 13 21 34 55 89
>>>
你可能会说 fib 不是函数而是过程。Python和C一样,过程只是不返回值的函数。实际上,严格地说,过程也返回一个值,只不过是一个很没意思的值。这个值叫做 None(这是一个内置的名字)。解释程序交互运行时如果只需要显示这个值的话就会忽略不显示。如果希望显示的话可以用 print 语句:
>>> print fib(0)
None
>>>
也可以写一个函数返回Fibonacci 序列的数值列表而不是显示这些值:
>>> def fib2(n): # 返回直到n的Fibonacci 序列值
... result = []
... a, b = 0, 1
... while b < n:
... result.append(b) # 解释见下面
... a, b = b, a+b
... return result
...
>>> f100 = fib2(100) # 调用
>>> f100 # 输出结果
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>>
这个例子也演示了新的Python特色:return语句从函数中退出并返回一个值。不带返回值的return可以从过程中间退出,运行到过程的末尾也可以退出,这两种情况下返回None。
语句result.append(b)调用列表对象result的一个方法。方法是“属于”一个对象的函数,引用格式为obj.methodname,其中obj是某个对象(也允许是一个表达式), methodname 是由该对象的类型定义的一个方法的名字。不同的不同的方法。不同类型的方法可以使用相同的名字而不致引起误解。(可以定义自己的对象类型和方法,使用类,本文后面会讨论这个话题)。例子中的append()方法时列表对象的方法,它在列表末尾增加一个新元素。在本例中这等价于“result = result + [b]”,只是更有效。
4.7 函数参数
可以定义使用可变个数参数的函数。这样的定义方法有三种,可以联合使用。
4.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)。缺省值是在函数定义时的定义作用域中计算的,所以例如:
i = 5
def f(arg = i): print arg
i = 6
f()
将显示5。
注意:缺省值只计算一次。当缺省值是可变对象如列表或字典时这一点是要注意的。例如,以下函数会在以后的调用中累加它的值:
def f(a, l = []):
l.append(a)
return l
print f(1)
print f(2)
print f(3)
This will print
[1]
[1, 2]
[1, 2, 3]
如果你不希望缺省值在连续的调用中被保留,可以象下面这样改写函数:
def f(a, l = None):
if l is None:
l = []
l.append(a)
return l
4.7.2 关键字参数
函数调用时也可以象“关键字 = 值”这样指定实参,其中关键字是定义时使用的形参的名字。例如:
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() # 非缺省的参数没有提供
parrot(voltage=5.0, 'dead') # 关键字参数后面又出现了非关键字参数
parrot(110, voltage=220) # 参数值重复提供
parrot(actor='John Cleese') # 未知关键字
一般说来,实参表中位置参数在前,关键字参数在后,关键字名字必须是形参名字。形参有没有缺省值都可以用关键字参数的形式调用。每一形参至多只能对应一个实参,因此,已经由位置参数传入值的形参就不能在同一调用中再作为关键字参数。
如果形参表中有一个形为**name的形参,在调用时这个形参可以接收一个字典,字典中包含所有不与任何形参匹配的关键字参数。形参表中还可以使用一个特殊的如*name的形参,它将接受所有不能匹配的位置参数组成的一个序表。*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
4.7.3 任意个数参数
在所有有名的形参的后面可以有两个特殊的形参,一个以*args的形式命名,一个以**kw 的形式命名。有了*args形式的形参后函数在调用时就可以在正常的能匹配的实参表后面输入任意个数的参数,这些参数组成一个序表赋给args形参,不能匹配的关键字参数组成一个字典赋给kw形参。在任意个数形参之前可以有0到多个正常的参数。例如:
def fprintf(file, format, *args):
file.write(format % args)
4.7.4 Lambda形式
因为许多人的要求,Python中加入了一些在函数编程语言和Lisp中常见的功能。可以用lambda 关键字来定义小的无名函数。这是一个返回其两个参数的和的函数:“lambda a, b: a+b” 。Lambda形式可以用于任何需要函数对象的地方。从句法上讲lambda形式局限于一个表达式。从语义上讲,这只是正常的函数定义的句法甜食。像嵌套函数定义一样,lambda形式不能访问包含其定义的作用域中的变量,但审慎地使用缺省参数之可以绕过这个限制。例如:
def make_incrementor(n):
return lambda x, incr=n: x+incr
4.7.5 文档字符串
关于文档字符串的内容与格式正在形成一些惯例。第一行应该为简短的对象目的概括说明。为了简明起见,这一行不应该提及对象的名字或类型,因为这些可以通过其他途径得知(当然如果对象名字就是一个描述函数操作的动词则当然可以提及其名字)。着以行应该用大些字母开始,以句点结尾。如果文档字符串中有多行,第二行应该是空行,把概括说明与其它说明分开。以下的行可以是一段或几段,描述对象的调用方法,它的副作用,等等。
Python的扫描程序不会从多行字符串中去掉缩进空白,所以处理文档的工具需要自己处理缩进。只要遵循如下的惯例就可以有利于缩进空白的处理。在第一行之后的第一个非空白的行决定整个文档字符串的缩进数量(我们不用第一行,因为它经常是直接跟在表示字符串开始的引号后面)。文档字符串中除第一行以外的各行都要删除等价于此行的缩进量的空白。对制表符将扩展为空格后再删除。
第五章 Python数据结构
本章更详细地讨论一些已经讲过的数据类型的使用,并引入一些新的类型。
5.1 列表
列表数据类型还有其它一些方法。下面是列表对象的所有方法:
insert(i, x) ---- 在指定位置插入一项。第一自变量是要在哪一个元素前面插入,用下标表示。例如,a.insert(0, x)在列表前面插入,a.insert(len(a), x)等价于a.append(x) 。
append(x) ---- 等价于a.insert(len(a), x)
index(x) ---- 在列表中查找值x然后返回第一个值为x的元素的下标。没有找到时出错。
remove(x) ---- 从列表中删去第一个值为x的元素,找不到时出错。
sort() ---- 对列表元素在原位排序。注意这个方法改变列表,而不是返回排序后的列表。
reverse() ---- 把列表元素反序。改变列表。
count(x) ---- 返回x在列表中出现的次数。
下例使用了所有的列表方法:
>>> 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]
5.1.1 函数程序设计工具
Python中有一些函数程序设计风格的东西,例如前面我们看到的lambda形式。关于列表有三个非常有用的内置函数:filter(), map()和reduce()。
“filter(函数, 序列)”返回一个序列(尽可能与原来同类型),序列元素是原序列中由指定的函数筛选出来的那些,筛选规则是“函数(序列元素)=true”。filter()可以用来取出满足条件的子集。例如,为了计算一些素数:
>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]
“map(函数,序列)”对指定序列的每一项调用指定的函数,结果为返回值组成的列表。map() 可以对序列进行隐式循环。例如,要计算三次方,可用:
>>> def cube(x): return x*x*x
...
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
可以有多个序列作为自变量,这时指定的函数必须也有相同个数的自变量,函数从每个序列分别取出对应元素作为自变量进行调用(如果某个序列比其它的短则取出的值是None)。如果指定的函数是None,map()把它当成一个返回自己的自变量的恒同函数。在函数用None的情况下指定多个序列可以把多个序列搭配起来,比如“map(None, list1, list2)”可以把两个列表组合为一个成对值的列表。见下例:
>>> 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(函数, 序列)”用来进行类似累加这样的操作,这里的函数是一个两个子变量的函数,reduce()先对序列的前两项调用函数得到一个结果,然后对结果和序列下一项调用函数得到一个新结果,如此进行到序列尾部。例如,要计算1到10的和:
>>> def add(x,y): return x+y
...
>>> reduce(add, range(1, 11))
55
如果序列中只有一个值则返回这个值,序列为空时会产生例外。可以指定第三个自变量作为初始值。有初始值时对空序列函数将返回初始值,否则函数先对初始值和序列第一项作用,然后对结果和序列下一项作用,如此进行到序列尾。例如:
>>> def sum(seq):
... def add(x,y): return x+y
... return reduce(add, seq, 0)
...
>>> sum(range(1, 11))
55
>>> sum([])
0
5.2 del语句
上面我们看到,列表的remove()方法可以从列表中删去某个取值的项,我们还可以用del 语句来删除指定下标的项。也可以用del语句从列表中删除一个片断(前面我们是用给片断赋空列表的办法删除片断的)。例如:
>>> 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的其它一些应用。
5.3 序表和序列
我们看到列表和字符串有许多共同点,例如,下标和片断运算。它们都属于序列数据类型。因为Python是一个正在不断发展的语言,以后还可能会加入其它的序列数据类型。现在还有一种标准的序列数据类型,称为序表(tuple)。
序表由一系列值用逗号分隔而成,例如:
>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # 序表允许嵌套:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
输出的序表总是用括号包围,这样可以保证嵌套序表得以正确解释。输入时可以有括号也可以没有括号,当经常是必须有括号(如果序表是一个大表达式的一部分)。
序表有许多用处,例如,(x,y)坐标对,数据库中的职工纪录,等等。序表与字符串一样是不可变的:不允许对序表的某一项赋值。
生成序表时对0项或1项的序表有特殊的规定:空序表用一对空括号表示;只有一项的序表用一个之后面跟一个抖好表示(指把这个值放在括号内是不够的)。这样写不够美观,但很有效。例如:
>>> empty = ()
>>> singleton = 'hello', # <-- note trailing comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)
语句t = 12345, 54321, 'hello!'是序表打包的一个实例:12345, 54321和'hello!'这些值被打包进了一个序表中。相反的操作也是允许的,例如:
>>> x, y, z = t
这叫做序表解包。序表解包要求等号左边的变量个数等于序表的长度。注意多重赋值只是序表打包和序表解包的联合使用。有时也对列表进行类似操作,即列表解包。只要把各变量写成一个列表就可以进行解包:
>>> a = ['spam', 'eggs', 100, 1234]
>>> [a1, a2, a3, a4] = a
5.4 字典
Python内置的另一个有用的数据类型是字典。字典在其它语言中有时被称为“关联记忆” 或“关联数组”。字典不象序列,它不是用在一个范围之内的数字下标来索引,而是用键值来索引,键值可以是任何不可变类型。字符串和数值总可以作键值。如果序表只包含字符串、数值或序表则序表也可以作键值使用。列表不能用作键值,因为列表可以用其append()方法就地改变值。
最好把字典看成是一系列未排序的“键值:值”的集合,在同一字典内键值是互不相同的。一对空大括号产生一个空字典:{}。在大括号内加入用逗号分开的“键值:值”对可以在字典内加入初始的键值和值对,字典在输出时也是这样显示的。对字典的主要操作是以某个键值保存一个值,以及给定键值后查找对应的值。也可以用del删除某个键值:值对。如果用一个已有定义的键值保存某个值则原来的植被遗忘。用不存在的键值去查找会出错。
字典对象的keys()方法返回字典中所有键值组成的列表,次序是随机的。需要排序时只要对返回的键值列表使用sort()方法。为了检查某个键值是否在字典中,使用字典的has_key() 方法。
下面是字典使用的一个简单例子:
>>> 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
5.5 条件的进一步讨论
在while语句和if语句中使用的条件除了可以使用比较之外还可以包含其它的运算符。比较运算符“in”和“not in”可以检查一个值是否在一个序列中。运算符“is”和“is not ”比较两个对象是否恰好是同一个对象,这只对象列表这样的可变对象有意义。所有比较运算优先级相同,而比较运算的优先级比所有数值运算优先级低。
比较允许连写,例如,a < b == c检查是否a小于等于b而且b等于c。
比较可以用逻辑运算符and和or连接起来,比较的结果(或其它任何逻辑表达式)可以用not 取反。逻辑运算符又比所有比较运算符低,在逻辑运算符中,not优先级最高,or的优先级最低,所以“A and not B or C”应解释为“(A and (not B)) or C”。当然,可以用括号来表示所需的组合条件。
逻辑运算符and和or称为“短路”运算符:运算符两侧的表达式是先计算左边的,如果左边的结果已知则整体结果已知就不再计算右边的表达式。例如,如果A和C为真而B为假则“A and B and C”不会计算表达式C。一般地,当短路运算符的运算结果不是用作逻辑值的时候返回的是最后求值的那个表达式的值。
可以把比较或其它逻辑表达式的结果赋给一个变量。例如:
>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_null
'Trondheim'
注意Python和C不同,表达式中不能进行赋值。
5.6 序列与其它类型的比较
序列对象可以和其它同序列类型的对象比较。比较使用字典序:先比较最前面两项,如果这两项不同则结果可以确定;如果这两项相同,就比较下面的两项,如此下去,直到有一个序列到头为止。如果某两项本身也是同类型的序列,则进行递归的字典序比较。如果两个序列的所有各项都相等,则这两个序列相等。如果一个序列是另一个序列的一个初始子序列,短的一个是较小的一个。字符串的字典序比较按各个字符的ASCII次序进行。下面是一些序列比较的实例:
(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)总是小于字符串(string),字符串总是小于序表(tuple),等等。但是程序中不能依赖这样的比较规则,语言实现可能会改变。不同的数值类型可以按数值来比较,所以0等于0.0,等等。
第六章 模块
如果退出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
<open file '/tmp/workfile', mode 'w' at 80a0960>
第一自变量是一个包含了文件名的字符串,第二自变量是文件打开方式的字符串。模式‘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 "<stdin>", 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 "<stdin>", line 1
while 1 print 'Hello world'
^
SyntaxError: invalid syntax
语法分析器重复出错行,并用一个小‘箭头'指向行内最早发现错误的位置。错误是由箭头前面的记号引起的(至少是在这里检测到的)。在本例中,错误在关键字print处检测到,因为它前面应该有一个冒号(“:”)。错误信息中显示了文件名和行号这样如果错误发生在一个脚本文件中你就知道到哪里去找。
8.2 例外
即使语句或表达式句法没有问题,在试图运行的时候也可能发生错误。运行时检测到的错误叫做例外,这种错误不一定必然是致命的:你很快就会学到如何在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
错误信息的最后一行显示发生的情况。例外有不同的类型,类型作为错误信息的一部分显示:上例中错误的类型有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 "<stdin>", 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 "<stdin>", line 1
my_exc: 1
许多标准模块用这种方法报告自己定义的函数中发生的错误。
8.6 定义清理动作
try语句还有另一个finally可选子句,可以用来规定不论出错与否都要执行的动作。例如:
>>> try:
... raise KeyboardInterrupt
... finally:
... print 'Goodbye, world!'
...
Goodbye, world!
Traceback (innermost last):
File "<stdin>", 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()转换成字符串的结果。
第十章 进一步学习
希望通过这个入门课程的学习增强了你对使用Python的兴趣。下一步怎么办呢?
你可以进一步去读《库参考手册》,该手册对类型、函数、模块等给出了完整的(尽管比较简短)参考材料,可以节省你写Python程序时的大量时间。标准Python软件包括了大量的C 和Python的代码,有读取Unix信箱的,用HTTP接收文档的,生成随机数的,解析命令行选项的,写CGI程序的,压缩数据的,等等许多功能;粗略看一看库参考手册就可以知道有那些内容。
Python的主网站是http://www.python.org,这里有程序代码、文档及有Python相关的其它网页的信息。这个网站在世界上许多地方有镜像,例如欧洲、日本、澳大利亚,访问距离近的镜像网站可能比访问主网站快。还有一个非正式的网站是 http://starship.skyport.net ,包含了一系列有关Python的个人网页,许多人在这里放了可下载的软件。
对于关于Python的问题及错误报告,可以向comp.lang.python新闻组投稿,或向python-list@cwi.nl 邮件表发邮件。该新闻组和邮件表是互相转发的,所以向其中一个发信会自动转寄到另一个。每天有35-45份邮件,有问(答)问题的、建议新功能的、声明新模块的。在发信前应该先查阅 http://www.python.org/doc/FAQ.html 处的常见问题表(FAQ),或在Python软件的Misc/ 子目录中寻找该文件。该FAQ回答了许多反复出现的问题,可能已经有了你的问题的答案。
你可以通过加入Python软件活动来支持Python团体,该团体负责python.org网站、ftp和电子邮件服务器,并组织Python研讨班。关于如何加入参见 http://www.python.org/psa/。
加载全部内容