Python
之旅 (三)文
/马儿 (marr@linux.org.tw)上期内容当中,读者已经浏览过
Python 语言里的一些基本资料型别元件,我们将接续地看到更多实用而有趣的范例。本期的重点摆在字串、函式、模组、系统资讯的处理上,这些学习经验仍然是 Python 程式设计的重要基础。连续数值的指定
: 内建函式 range()range 是一个便利的内建函式,可以传回一组整数值的串列。
>>> range(7)
[0, 1, 2, 3, 4, 5, 6]
>>> (mon, tue, wed, thu, fri, sat, sun) = range(7)
>>> mon
0
>>> sun
6
>>> range(-5, 5)
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
>>> range(0, 10, 3)
[0, 3, 6, 9]
range 的预设值由 0 为起始。
将一个值组与 range 函式所产生的串列进行对映,所以指定结果是 mon 为 0,而 sun 为 6。
可以为 range 函式指定起始值与终止值,如 (-5, 5) 是指 -5 到 4 所形成的整数值串列。
先前的例子都省略了间隔值的设定,而使用其预设间隔值 1,在此我们可以加入间隔值的设定,如例中的最后一个参数 3。下列的简单范例,可以将一组资料进行编号。
>>> weekday = ('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun')
>>> for day in range(len(weekday)):
... print day, weekday[day]
...
0 mon
1 tue
2 wed
3 thu
4 fri
5 sat
6 sun
实务程式写作上,
range函式 与 for 回圈经常搭配。还记得上期内容里的 ascii.py 范例吗? 我们可以使用 for 回圈与 range 函式加以改写,来个温故知新。example$ cat ascii.py
#!/usr/bin/python
for i in range(256) :
print chr(i),
if i != 0 and i % 16 == 0 :
相较之下,程式码本身显得简化了,但语法型态与
while 不同,读者应用时大抵挑自己习惯者即可。资源与效率的抵换
除了
range() 之外,还有个 xrange() 内建函式,不但名称相近,功能可说完全一致,其传回值最后是相同的。差别在于 range() 会以串列资料型别来储存所产生的传回值,实际在记忆体里占有完整空间,而 xrange() 则是传回一个 XRangeType 的物件,并不会在记忆体里占有太多储存空间,而是等到实际存取资料时才继续算出其值。所以在大笔资料的实务应用上,使用 range() 会占用较多记忆体,但效率快些,而 xrange() 则节省记忆体空间,而牺牲了效率。请读者善用 Python 的设计特性,依场合需要自行选用合适的函式。>>> range(1000000)
>>> xrange(1000000)
如果你的电脑系统资源丰富,上述两个例子‘看不出差别’,可以继续尝试更大的输入值来测试。
函式
(function) 的使用函式功能在程式语言里是必备的,不管是函式或模组的写作,都要求程式员尽可能做到易读易懂、功能独立、可重复使用的程式片段,‘避免重造轮子’,这样的设计理念,在
Python 语言里便极受重视。相信读者至少已经熟悉一种其他程式语言的函式功能,我们可以直接观察下列的范例:>>> def fact(n):
... """Return the factorial of the given number."""
... r = 1
... while n > 0:
... r = r * n
... n = n - 1
... return r
...
>>> fact.__doc__
'Return the factorial of the given number.'
>>> fact(5)
120
>>>
函式还是一个物件,其指定方式,是以保留字
def (即 define 之意) 为首,余下的函式内容,同样要按照‘Python 的程式码缩排原则’,否则会产生‘IndentationError’的错误讯息。惯例上,函式的第二行会是一段‘三引号注释字串’,即 """ 所括夹的文字,称为‘文件字串’(documentation strings),我们可以透过如 fact.__doc__ 的物件方法,再把文件字串内容显示出来,这样的实务惯例,通常在大型而正式的 Python 程式开发专案里显得有用。fact() 是一个阶乘函式范例,请注意到最后一行的保留字 return,如果少了 return 叙述 [1],则预设会以 None 值传回。以范例函式 fact() 来看,n 是函式的参数 (argument),其传回值 r 是 n 的阶乘结果。
保留字、识别字、内建函式名称
程式语言里的保留字
(reserved words) 与识别字 (identifiers),是用来识别变数、函式、类别、模组、物件的名称,我们先前见过如 print、is、while、for、def、return 等,都是 Python 保留字的范例。另外,以 _、__ 为开头的识别字名称,许多是 Python 的保留识别字,如 __doc__、__name__、__builtins__ 等,它们通常有特殊意义,日后我们会看到越来越多这样的例子。至于内建函式名称,读者已经见过的,如 chr()、id()、len()、max()、range() 等。值得注意的是,上述识别字相关名称,都是大小写有别的 (case sensitive)。对于打算长期与 Python 厮混的朋友而言,应该有必要手边准备一份保留字相关资讯。Python 识别字指定规则:
http://www.python.org/doc/2.0/ref/identifiers.html
Python 所有保留字资讯:
http://www.python.org/doc/2.0/ref/keywords.html
>>> def fact(n=10):
... """Return the factorial of the given number, with the defult input value."""
... r = 1
... while n > 0:
... r = r * n
... n = n - 1
... return r
...
>>> fact(5)
120
>>> fact()
3628800
>>>
上述程式片段示范了函式预设输入值的设定方式,试试以
fact(5) 呼叫范例函式,会得到传回值 120,而以 fact() 呼叫,则会传回以 10 为预设输入值的 3628800。>>> def power(x, y=2):
... r = 1
... while y > 0:
... r = r * x
... y = y - 1
... return r
...
>>> power(2, 4)
16
>>> power(3)
9
>>> power()
Traceback (innermost list):
File "<stdin>", line 1, in ?
TypeError: not enough arguments: expected 1, got 0
>>> power(2, 4, 3)
Traceback (innermost list):
File "<stdin>", line 1, in ?
TypeError: too many arguments: expected 2, got 3
>>>
一个函式可以设定多个输入值,上例
power() 函式可以接受两个输入值,但至少需要 (expect) 一个输入值,因为参数 y 有个预设值 2,输入参数时可以省略之。以 power(2, 4) 呼叫时,会成功传回 16,以 power(3) 呼叫时,会自动以 power(3, 2) 为输入值,传回 9。如果以 power() 呼叫,则会产生 TypeError 的错误讯息,它会明确告知‘参数不足’,你必须至少输入几个参数,如果是以 power(2, 4, 3) 来呼叫,则会得到‘参数过多’的 TypeError 讯息。>>> power(y=4, x=2)
16
>>>
另一个有用的应用技巧,称为‘关键字派定法’
(keyword passing),是以类似 power(x=2, y=4) 方式来呼叫,明确地将参数值配合变数名称通知函式,甚至也可以用 power(y=4, x=2) 方式来呼叫,如此一来,两者的呼叫结果是完全相同,意即其参数值顺序可以任意改变。我们接着来看一些任意个输入值的例子,相信其实用价值极高。
>>> def maximum(*numbers):
... if len(numbers) == 0:
... return(None)
... else:
... max = numbers[0]
... for n in numbers[1:]:
... if n > max:
... max = n
... return max
...
>>> maximum(3, 8, 5)
8
>>> maximum(2, -5, 9, 1, 7)
9
>>>
函式
maximum() 的用意很明显,我们可以输入任意个数的数值,而它最后会传回最大值。例如 maximum(3, 8, 5) 会传回 8,而 maximum(2, -5, 9, 1, 7) 会传回 9。值得注意的地方,就是其处理不定参数个数的技巧,参数指定以 *numbers 方式代表,而 numbers 本身是一个值组 (即 tuple,而非 list)。函式的演算规则倒简单,先把第一个数 numbers[0] 设为最大值,再将剩下的数 numbers[1:] 所形成之值组,丢进 for 回圈,以‘暴力法’比大小。下列的例子,将参数设定的可能状况大抵做了整合介绍,我们可以一窥函式参数派定的相关细节,值得读者反覆测试观察。
>>> def args_func(a, b, c=9, *other1, **other2):
... return [a, b, c, other1, other2.items()]
...
>>> args_func(1, 2, 3)
[1, 2, 3, (), []]
>>> args_func(b=1, a=2)
[2, 1, 9, (), []]
>>> args_func(1, 2, 3, 4, 5, 6, 7, 8, 9)
[1, 2, 3, (4, 5, 6, 7, 8, 9), []]
>>> args_func(1, c=3, b=2, d=4, e=5)
[1, 2, 3, (), [('d', 4), ('e', 5)]]
>>> args_func(d=4, e=5)
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: not enough arguments; expected 2, got 0
范例函式 args_func() 可以输入三个 (以上的) 参数,参数名称分别是 a、b、c,其中 c 有预设值为 9,a 与 b 是必要的输入参数。而 *other1 用以指定 a、b、c 关键字之外的参数值 (不定个数),**other2 则是用以指定 a、b、c 关键字之外的派定值 (同样是不定个数)。
函式 args_func() 非常简洁,直接把所有的输入参数值以串列资料型别传回。其中的 other1 属于值组资料型别,而 other2 则是辞典集 (dictionary) 资料型别。别慌,稍后会为读者解说辞典集的相关细节。
给定三个参数值,它们会分别成为函式 args_func() 的 a、b、c 的设定值,此时 other1 与 other2 都是空空如也。
使用关键字派定法来指定 a 与 b 的参数值,而使用 c 的预设参数值。
给定了九个参数值,前三个依序成为 a、b、c 的参数值,后六个数值则成为值组 other1 的元素内容。
给定了五个参数值,第一个成为 a 的参数值,b 与 c 以关键字派定法来指定,而最后的 d 与 e 则成为不定个数的关键字派定值,它们被辞典集 other2 所收留了。内建资料型别
dictionary 的使用辞典集是
Python 里的内建映射物件 (mapping object),也就是由一个物件集合来作为另一个物件集合的键值索引。映射物件和之前谈过的序数资料相较,在概念上有扩充、补强的涵意,善用映射物件的特性,可以协助我们将看似复杂的问题,以相当直觉的方式解决。两者具有若干不同之处,例如序数以索引运算作为其取值方式,映射物件的元素成份来源很有弹性,映射物件是不做排序的,而且映射物件是可变物件。●
TABLE_BEGIN●表
: 序数型别与映射型别的比较|
序数型别 |
映射型别 |
|
以索引运算作为其取值方式。 |
以 keys()、values()、items() 等内建函式来取值。 |
|
以整数作为其索引 (index)。 |
以不可变物件作为其键 (key)。 |
|
元素具排序效果。 |
元素储存时不做特定排序。 |
|
不一定都属于可变物件。 |
辞典集是可变物件。 |
●
TABLE_END●在功能上,辞典集的实作相当于资料结构里的杂凑表
(hash table) 或是相关阵列 (associative array),所以你会看到‘键-值’(key-value pairs)的表示法。通常我们会以字串来做为辞典集的 key,因为有意义的字串可以带来‘望文生义’的效果,不过,一定要以不可变物件来做辞典集的 key,而 value 的部份就全无限制了。>>> x = []
>>> y = {}
>>> x[0] = 'Beatles'
Traceback (innermost last):
File "<stdin>", line 1, in ?
IndexError: list assignment index out of range
>>> y[0] = 'John Lennon'
>>> y[1] = 'Paul McCartney'
>>> y[2] = 'George Harrison'
>>> y[3] = 'Ringo Starr'
>>> y[0] + " and Yoko Ono"
'John Lennon and Yoko Ono'
>>> y
{3: 'Ringo Starr', 2: 'George Harrison', 1: 'Paul McCartney', 0: 'John Lennon'}
>>> y.keys()
[3, 2, 1, 0]
>>> y.values()
['Ringo Starr', 'George Harrison', 'Paul McCartney', 'John Lennon']
>>> y.items()
[(3, 'Ringo Starr'), (2, 'George Harrison'), (1, 'Paul McCartney'), (0, 'John Lennon')]
>>>
分别起始建立一个空串列 x 与一个空辞典集 y。由于无法指定不存在的索引值给串列,所以 x[0] 的指定动作宣告失败,Python 回报 IndexError 错误讯息。
对于辞典集而言,并无上述的困扰,修改或增添元素资料均无限制。此例中,我们分别指定了 y[0]、y[1]、y[2]、y[3] 四笔元素资料。
可以针对元素进行各式运算,例如我们拿字串 y[0] 与另一字串合并显示。
注意到辞典集的储存,并没有特定的顺序方式,如果想要依特定的排序方法处理资料,可以另寻变通方法。
示范辞典集最常见的内建函式,即 keys()、values()、items(),它们都是传回串列物件。>>> Beatles = {'leader':'John','bass':'Paul','guitar':'George','drum':'Pete'}
>>> Hurricanes = {'drum':'Ringo'}
>>> Beatles.update(Hurricanes)
>>> Beatles
{'drum': 'Ringo', 'leader': 'John', 'bass': 'Paul', 'guitar': 'George'}
>>> Beatles.get('leader', "Not available")
'John'
>>> Beatles.get('manager', "Not available")
'Not available'
>>>
我们为一个叫做 Beatles 的乐团建立辞典集,指定其键值及元素值,共计四项元素。
另一个叫做 Hurricanes 的乐团,其辞典集元素,只有鼓手的设定资料。
透过 update() 物件方法,我们更新了 Beatles 鼓手的设定资料。
get() 物件方法是询问 Beatles 里是否有 leader 此一键值,若存在则传回其对应之元素值,否则会传回后头的字串资料。相信读者至此已对辞典集有了基础认识,其他辞典集相关的物件方法及运算元,请参考表格内容说明。值得一提的是,
Python 里的辞典集实作得相当有效率,就算和串列型别相较,你也应该会满意于它的便利与速度,适当的话,可以考虑多加使用。●
TABLE_BEGIN●表
: 辞典集的方法和操作|
项目 |
说明 |
|
len(dict) |
传回辞典集 dict 里的元素个数。 |
|
dict[k] |
传回键值 k 的元素内容。 |
|
dict[k]=v |
将 dict[k] 的内容设定为 v。 |
|
del dict[k] |
将 dict[k] 元素项目移除。 |
|
dict.clear() |
将辞典集 dict 所有元素项目全部移除。 |
|
dict.copy() |
将辞典集 dict 整个复制。 |
|
dict.has_key[k] |
如果辞典集 dict 含有键值 k 的话,则传回 1,否则传回 0。 |
|
dict.items() |
以值组 (key, value) 的串列型别传回辞典集中所有元素。 |
|
dict.keys() |
传回辞典集 dict 的所有键值。 |
|
dict.values() |
传回辞典集 dict 的所有元素值。 |
|
dict.update(other) |
将辞典集 other 所有物件更新至辞典集 dict 当中。 |
|
dict.get(k [, other]) |
如果 dict[k] 存在则传回 dict[k],否则传回 other。 |
●
TABLE_END●模组
(module) 的使用简单地说,模组代表着某个档案名称,该档案名称必须以
.py 延伸档名作结,我们可以到 Python 的安装目录一窥究竟,以 Linux Mandrake 7.2 为例,其安装目录为 /usr/lib/python1.5。目录里包含类似 string.py、os.py、find.py 的档案,我们可以透过 import string, os, find 之类的程式语法呼叫这些模组内容。也可以直接阅读这些 .py 档案的程式码,相信部份档案的内容,对你而言已不再全是天书。别误会了,模组本身也可以是编译过的档案,如
.pyc 或 .pyo 档案即属此类。在系统目录里存在这类档案,通常可以达到执行加速的效果。实际动手撰写自己的模组之前,我们得先认识内建函式
dir() 的功能,它可以将许多物件内部的资讯显示出来。>>> dir()
['__builtins__', '__doc__', '__name__']
>>> dir(__doc__)
[]
>>> print __doc__
None
>>> print __name__
__main__
>>> type(__builtins__)
<type 'module'>
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'FloatingPointError', 'IOError', 'ImportError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplementedError', 'OSError', 'OverflowError', 'RuntimeError', 'StandardError', 'SyntaxError', 'SystemError', 'SystemExit', 'TypeError', 'ValueError', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', 'abs', 'apply', 'buffer', 'callable', 'chr', 'cmp', 'coerce', 'compile', 'complex', 'delattr', 'dir', 'divmod', 'eval', 'execfile', 'exit', 'filter', 'float', 'getattr', 'globals', 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'len', 'list', 'locals', 'long', 'map', 'max', 'min', 'oct', 'open', 'ord', 'pow', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'round', 'setattr', 'slice', 'str', 'tuple', 'type', 'vars', 'xrange']
>>> print __builtins__.__doc__
Built-in functions, exceptions, and other objects.
Noteworthy: None is the `nil' object; Ellipsis represents `...' in slices.
>>>
当我们身处新启动之 Python 交谈环境里,输入 dir() 可以显示 local symbol table的名称串列,共计三个。
进一步提供 __doc__ 给 dir() 做为参数,传回空串列,表示 __doc__ 物件已无相关属性 (attributes)。print __doc__ 显示其为 None 物件。
__name__ 是一个字串物件,表示目前执行‘程式’的名称,其值为 __main__。
__builtins__ 则是一个模组物件,持续以 dir(__builtins__) 观察,可以显示模组 __builtins__ 的全部属性。
显示 __builtins__ 的 __doc__ 属性内容。注意到,每个正在执行的主要程式,其程式名称
(即 __name__ 属性) 会是 __main__,如果是以模组型态被 import 进来,那么该模组程式便会以原本档案名称为 __name__ 的值。请观察下列程式范例的说明,两个极其简化的‘土制模组’。example$ cat other_mod.py
#!/usr/bin/python
print "this is from other_mod."
print __name__
example$ chmod 755 other_mod.py; ./other_mod.py
this is from other_mod.
__main__
example$ cat my_mod.py
#!/usr/bin/python
"""Simple module example for illustrating how __*__ works."""
import other_mod
print "this is from my_mod."
print __name__
example$ chmod 755 my_mod.py; ./my_mod.py
this is from other_mod.
other_mod
this is from my_mod.
__main__
被
import 的模组档案,其内容会被执行,所以范例 my_mod.py 在执行之后,会先读进 other_mod.py 的程式片段,接着才是 my_mod.py 的程式片段。请特别留意 __name__ 属性值的变化,这项控制技巧经常被使用。模组的搜寻路径
Python 实际搜寻模组路径的设定值,可以由 sys 模组里的 path 变数值取得。
>>> import sys
>>> sys.path
['', '/usr/lib/python1.5/', '/usr/lib/python1.5/plat-linux-i386', '/usr/lib/python1.5/lib-tk', '/usr/lib/python1.5/lib-dynload', '/usr/lib/python1.5/site-packages', '/usr/lib/python1.5/site-packages/PIL']
>>> import marr
Traceback (innermost last):
File "<stdin>", line 1, in ?
ImportError: No module named marr
>>>
上述的搜寻路径是有顺序性的,也就是说,
import 所呼叫的模组名称,依序第一个找到的路径便会马上回传使用。如果找不到 import 的模组名称,则会传回 ImportError 的错误讯息。以载入 foo 模组为例,其实际寻找模组的过程顺序如下:1. 是否存在名为 foo 的目录,并且里头含有该模组的档案。
2. 是否存在 foo.so、foomodule.so、foomodule.sl 或是 foomodule.dll
3. 是否存在 foo.pyo
4. 是否存在 foo.pyc
5. 是否存在 foo.py
以一个
.py 的 Python 原始码档案而言,经过编译后,会产生一个名为 .pyc 的 bytecode执行档,当寻找某个模组名称时,要是 .py 档案的日期不比 .pyc 档案来得新,Python 直译器会直接将编译好的 .pyc 档案载入,若是 .py 档案的日期比 .pyc 档案来得新,通常就表示 .py 档案内容已更新,Python 直译器会重新编译之,以产生新的 .pyc 档案,然后才进入载入动作。而 .pyo 档案只有在直译器以 -O 选项启动之后才会产生,这类档案里的资讯通常比 .pyc 档案来得多,包含有原始程式的行号以及除错资讯,因此 .pyo 档案的载入速度会较慢,但程式的执行速度会较快。而
.pyc 或是 .pyo 档案的编译动作,是在程式里头呼叫 import 后才会发生,对 Python语言来说,模组档案不止是设计概念的切割,它更从强化模组执行效率的角度,鼓励程式员善用模组档案功能。如果自制的模组档案越来越多,其应用自然越显重要,此时便要认真为自制模组找个适当的存放路径,比较常见的方式之一,是设定相关的环境变数值,例如变数 PYTHONPATH。●
TABLE_BEGIN●表
: Python 相关环境变数设定|
变数名称 |
说明 |
|
PYTHONDEBUG |
与 python -d 启动模式相同。可产生 Python 的语法解析除错资讯。 |
|
PYTHONHOME |
与模组搜寻路径设定相关的变数。 |
|
PYTHONINSPECT |
与 python -i 启动模式相同。以交谈模式来执行 Python 程式。 |
|
PYTHONOPTIMIZE |
与 python -O 启动模式相同。以最佳化模执行 Python 程式。 |
|
PYTHONPATH |
增加模组搜寻路径。 |
|
PYTHONSTARTUP |
交谈模式就绪前所执行的程式路径。 |
|
PYTHONUNBUFFERED |
与 python -u 启动模式相同。记录未做缓冲的二元标准输出输入。 |
|
PYTHONVERBOSE |
与 python -v 启动模式相同。执行过程详列相关处理资讯。 |
●
TABLE_END●名称空间
(namespace) 及其有效领域 (scope)回过头来,我们再次透过
dir() 来观察模组档案载入后的物件变化情况。请将现行目录设定在 my_mod.py 档案存放的目录,然后进入 Python 的交谈环境。●图●
图
: 名称空间阶层示意
●图●
example$ python
Python 1.5.2 (#1, Sep 30 2000, 18:08:36) [GCC 2.95.3 19991030 (prerelease)] on linux-i386
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> dir()
['__builtins__', '__doc__', '__name__']
>>> import my_mod
this is from other_mod.
other_mod
this is from my_mod file.
my_mod
>>> dir()
['__builtins__', '__doc__', '__name__', 'my_mod']
>>> dir(my_mod)
['__builtins__', '__doc__', '__file__', '__name__']
>>> print my_mod.__doc__
Simple module example for illustrating how __*__ works.
>>> print my_mod.__file__
my_mod.py
>>>
整个运作过程,依序可以分解如下:
1. 每次 Python 启动时,它会产生一个名为 __main__ 的模组物件,此物件本身当然有其相关资讯,所以我们可以透过 dir() 来取得。
2. 每次呼叫 import 内建函式之后,Python 会载入另一个模组物件,并将其物件相关资讯并进 __main__。
3. 由于两个模组物件的变数名称,有若干重复之处,如上述例子里的 __builtins__、__doc__、__name__,在取用变数资讯时,必须有办法加以区别才行。
4. Python 区别变数资讯的方法,如上例所示,是类似 __doc__ 与 my_mod.__doc__ 这样的不同表示法。
5. 每个模组物件,都会伴随一份物件资讯,以辞典集型别储存在系统当中,而这样的物件资讯内容,包括其变数、函式、物件等,都成为辞典集里所记录的键值或属性值。
6. 上述的辞典集物件资讯,我们把它简称为‘名称空间’(namespace)。
7. 内建函式 dir() 的功能,可以把名称空间的名称资讯全部列出。
除了基本的资料型别之外,Python 里的每一个模组物件都拥有自己的名称空间。下列是一个范例程式,可以协助列出模组物件的名称资讯。
example$ cat namespace.py
#!/usr/bin/python
import sys
k = sys.modules.keys()
print "Keys:", k
print "-" * 30
for i in k:
if i == "__main__":
print ">>>", i, "__dict__", sys.modules[i].__dict__
print "-" * 30
print dir()
在实务设计上,
Python 程式员会运用模组的方式,将物件做适当的切割,切割好的程式可以被包在一个 Python 类别库 (package) 里,以便进一步有效管理大型的软体专案。有了函式、模组、类别库等一系列的架构,我们便可更直觉地管理软体专案的物件体系。着名的 Zope 系统便是这样的专案管理实例 [2],其整套系统广泛应用类别库的技巧,至今仍在积极发展中。其他有用的函式
为了方便接续的内容,我们先来认识一个实用的模组,称为
string,顾名思义,可用于协助处理字串物件。>>> import string
>>> date = "Fri May 18 CST 2001"
>>> piece1 = string.split(date)
>>> piece1
['Fri', 'May', '18', 'CST', '2001']
>>> time = "12:03:27"
>>> piece2 = string.split(time, ':')
>>> piece2
['12', '03', '27']
>>> string.digits
'0123456789'
上述范例,让我们见识到模组
string 里有个 split() 的物件方法,可以将一个字串变数值,依空白字元 (预设状况) 为切割点,分解成数个小字串,形成一个字串串列传回。如果切割条件不是空白字元时,在 split() 所接参数中予以指定,如 split(time, ':') 就是指定要以 ':' 字元为切割点。最后则是显示模组 string 有个字串变数 digits,内容设定为 '0123456789'。如果我们想把上述字串串列里的‘数字’,如
'18' 与 '2001',由字串型别转换成数值型别,可以怎么做呢? 下列是个方法。>>> def try_ai(s):
... if s[0] in string.digits:
... return string.atoi(s)
... else:
... return s
...
>>> import string
>>> date = "Fri May 18 CST 2001"
>>> piece = string.split(date)
>>> finish_ai = map(try_ai, piece)
>>> print finish_ai
['Fri', 'May', 18, 'CST', 2001]
>>>
首先,定义一个叫做
try_ai() 的函式,它在读进字串后,会比对字串的第一个字元,如果第一个字元是属于阿拉伯数字,那么就会尝试将字串转换成整数,最后传回其整数型别资料。是的,你会发现它的演算规则有些天真,不过,我们暂时还不需要一个无懈可击的转换函式。接着,我们载入模组
string 之后,利用内建函式 map() 将自制函式 try_ai 与字串串列 piece 连结起来,如此一来,便能如愿将 piece 里的部份字串,转换成数值型别。显然 map() 函式在此例中帮上大忙,简洁地协助我们将自制函式与序列资料做了巧妙结合。接下来,我们就可以进一步稍微改良原本天真的
try_ai() 函式。>>> def try_ai(s):
... if ':' in s:
... ts = string.split(s, ':')
... return map(string.atoi,ts)
... if s[0] in string.digits:
... return string.atoi(s)
... else:
... return s
...
>>> import string
>>> date = "Fri May 18 12:03:27 CST 2001"
>>> piece = string.split(date)
>>> finish_ai = map(try_ai, piece)
>>> print finish_ai
['Fri', 'May', 18, [12, 3, 27], 'CST', 2001]
>>>
这个改良过的版本,可以进一步处理像
'12:03:27' 这样的‘数字’,否则原本更天真的版本会传回 ValueError 的错误讯息。>>> piece = ['Fri', 'May', '18', '12:03:24', 'CST', '2001']
>>> def strp(x, y):
... return x + ' ' + y
...
>>> r = reduce(strp, piece)
>>> r
'Fri May 18 12:03:24 CST 2001'
>>>
上述程式片段,处理效果刚好与之前的程式相反,它会把字串串列重组成一个长字串。重点就是利用了内建函式
reduce(),其运作方式同样要输入一个函式名称及一个序数资料,不过,目的是要把序数资料的元素‘合并减少’成一个。reduce() 也可以应用在数值串列上,以下便是这样的范例。>>> n = range(1, 11)
>>> def mult(x, y):
... return x * y
...
>>> f = reduce(mult, n)
>>> f
3628800
>>>
说穿了,它还是一个阶乘的范例,每呼叫一次
mult() 函式,数值串列的个数会越来越少,最后传回一个阶乘结果,在此例中,即 10! 的答案。下列是一个简化版本的闰年判断程式,我们将介绍另一个函式
filter()。>>> def leap(y):
... if (y%400) == 0:
... return 1
... elif (y%100) == 0:
... return 0
... elif (y%4) == 0:
... return 1
... return 0
...
>>> n = range(1900, 2001)
>>> leap_year = filter(leap, n)
>>> leap_year
[1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936, 1940, 1944, 1948, 1952, 1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000]
>>>
函式
filter() 同样是接一个函式名称与一个序数资料为参数,重点在于,在自制函式中,你必须把‘想留下来的资料’,其函式传回值设为 1 (代表 true),而把‘不想留下来的资料’,其函式传回值设为 0 (代表 false)。如此一来,filter() 函式在依序处理完序数资料后,还是会传回一个序数资料,而且应该只留下你想要的资料。lambda
表示式lambda 表示式是个实用而重要的工具,其功能及本质还是函式,差别在于 lambda 没有‘明确的函式名称’,而且通常只用于简洁的函式叙述。
>>> n = range(1, 11)
>>> f = reduce(lambda x, y: x * y, n)
>>> f
3628800
>>>
又是熟悉的阶乘函式,有趣的是,它的表示方式非常简洁,乍看之下,初学者可能以为是天书了。
lambda 表示式的语法如下:lambda 参数串列: 表示式
例如 x + y、x * y、s[9] 都是表示式的例子。早期,lambda 表示式的概念是取自 Lisp 语言,使用上有其便利及优势,不过,对初学者而言,使用 lambda 表示式通常还是得花时间熟悉,若是‘画虎不成反类犬’,搞到程式大乱就得不偿失了。
相关说明
[1] 有人把缺少 return 叙述的‘函式’称为 procedure。本质上,由于函式不明确描述 return 的话,会自动以 None 值 (空型别) 传回,所以视之为函式并无不妥。
[2] Zope 是一套网站 Application Server 系统,完全由 Python 语言所开发出来,程式本身就是一个庞大的物件系统,有兴趣的朋友,可到网站 http://www.zope.org/ 观摩。
[3] 来点不一样的吧,欢迎访阅 Instant Python 网页,网址 http://www.hetland.org/python/instant-python.php
[4] 想让 Python 执行更有效率吗? 试试Python Performance Tips 网页,网址http://musi-cal.mojam.com/~skip/python/fastpython.html
[5] Python for Lisp Programmers 网页,网址 http://www.norvig.com/python-lisp.html
[6] A Comparison of Python and LISP 网页,网址http://www.strout.net/python/pythonvslisp.html
[7] A Python/Perl phrasebook 网页,网址http://starship.python.net/~da/jak/cookbook.html
[8] Major Python changes 网页,提供Python 改版增修重点说明,网址http://shell.rmi.net/~lutz/errata-python-changes.html
[9] Python Language Mapping 网页,网址http://www.informatik.hu-berlin.de/~loewis/python/pymap.htm