第四章 高级数据类型
4.1采用序列组合数据
字符串、列表和元组都是Python内置的序列数据类型。序列类型代表一组有序的数据元素,元素类型可为任意类型。
列表和元组的区别是可以更改。列表适于管理很多个项,元组适于管理一个项的不同部分。
4.1.1创建列表
用一个空的方括号即可。
>>> x=[] # an empty list
可以调用list(seq)函数,将一个序列类型转换为列表:
>>> list((5,10))
[5,10]
range([lower,]stop[,step])函数可以生成一个范围内指定值的列表,成员可以是连续的整数。
>>> range(10)
[0,1,2,3,4,5,6,7,8,9]
也可以指定其实索引和终止索引,还可以给出步长。常用于循环。
>>> range(20,2,-3)
[20,17,14,11,8,5]
-
列表综合是Python2.0中新加入的特征,用于从已有列表创建新列表,并且使用过滤、计算等功能。
例如生成一个包含x的平方的列表,数字取值范围为1-10:
>>> [x*x for x in range(1,11)]
[1,4,9,16,25,36,49,64,81,100]
>>> [x*x for x in range(10) if x % 2 == 0]
[0,4,16,36,64]
>>> [a+b for a in 'ABC' for b in '123']
['A1','A2','A3','B1','B2','B3','C1','C2','C3']
>>> [(x,ord(x)) for x in 'Ouch']
[('O',79),('u',117),('c',99),('h',104)]
4.1.2创建元组
同创建列表类似。x=()
由于圆括号也可以用于括起来表达式,(by gashero)所以可以用如下确定一个元组的声明:
>>> x=('lonely',)
转换成元组:
>>> tuple('tuple')
('t','u','p','l','e')
>>> tuple([1,2,3])
(1,2,3)
4.2处理序列
4.2.1采用算术运算符连接和重复
序列的加法用于连接,序列的乘法用于重复,后面乘一个整数。这些运算符的扩充赋值版本也起作用。
>>> q=(1,2)
>>> q+=(3,4)
>>> q
(1,2,3,4)
4.2.2比较和成员资格测试
普通的比较运算符< <= >= > == !=可以用于序列对象,比较序列的对应元素,直到能够做出决策为止。有元素总比一个不存在的元素大。
in和not in运算符分别测试某个元素是否(by gashero)在列表或元组中。
4.2.3访问序列中的某部分
下标。用下标索引一个元素,第一个元素的索引是0。
切片。允许从已有序列中提取子序列。用"序列[start:end]"格式指定一个切片,如果一个元素索引i是start<=i<end,则会加入到切片中。负数索引是从末尾向前开始索引。start和end都是可选的,默认时为开头和结尾。
解包。执行创建元组的逆操作。
>>> s=(801,435,804)
>>> x,y,z=s
>>> print x,y,z
801 435 804
注意左侧变量数目一定要与解包的序列长度匹配。
4.2.4采用for...in进行循环
常见的操作是循环处理列表或元组中的各个元素。
>>> for x in ['sin','cos','tan']
... print x
4.2.5使用序列实用程序函数
len(x)、min(x[,y,z,...])、max(x[,y,z,...])
这三个函数不是序列特定的,但是非常有用。取元素数量、最大值、最小值。
filter(function,list)
使用function函数过滤列表list,当list中的一个值代入function的结果为True时的一个值,直到把所有符合的元素全部取出形成一个列表返回。用于过滤列表元素。如果使用None作为function则会删除列表中的0或空项。
filter()的返回序列类型与传递的类型相同。
map(function,list[,list,...])
遍历原始列表中的每一个元素代入function函数,并将返回值组成列表,返回一个列表。如果function函数允许传入多个参数,也可以同时使用多个列表提供值。
如果提供的列表长度不同,则空位用None补齐。如果用None代替function则返回元素的合并元组。
>>> a=[1,2,3];b=[4,5,6];c=[7,8,9]
>>> map(None,a,b,c)
[(1,4,7),(2,5,8),(3,6,9)]
reduce(function,seq[,init]
先获取列表前两项,代入函数function,之后将返回值和下一个项再代入function,直到全部处理完成。
>>> import operator
>>> reduce(operator.mul,[2,3,4,5])
120 # 120=((2*3)*4)*5
第三个参数init用于作为第一项。
>>> reduce(lambda x,y: y+x+y, "Hello",'-')
'olleH-Hello'
zip(seq[,seq,...])
将两个或多个序列中的对应项合并成一个元组,并返回元组列表,当最短的一个序列完成时就不再进行合并了,并返回。
很方便于同时处理几个列表。从Python2.0开始提供。
>>> for name,ext,age in zip(names,exts,ages):
将一个序列传递给zip会以一元组的格式返回每个项:
>>> zip((1,2,3,4))
[(1,),(2,),(3,),(4,)]
4.3使用附加的列表对象特征
4.3.1附加运算
使用赋值替换列表中的一个值:todo=[...];todo[1]='..'。除了可以替换元素之外,甚至还可以替换整个切片。
可以用del x[n]来删除列表中一个元素或切片,其余的补齐。
4.3.2列表对象方法
append(obj)和extend(obj)
都是用于再列表尾端继续添加元素,append()添加一个元素,extend()把参数作为一个列表,提取各个元素添加进去。
index(obj)
返回列表中第一个匹配项的索引,不存在时抛出ValueError异常。
>>> try:print x.index('famrer')
... except ValueError: print 'Not Found'
count(obj)
计算列表中与指定元素匹配的有多少个元素。字符串对象也包包含计数和索引的方法。
insert(j,obj)
在元素j之前插入新元素。如果j<=0则在开头添加,如果j大于列表长度则在列表尾部添加。
remove(obj)
在列表中查找obj并删除首个找到的元素。找不到则ValueError。
pop([j])
指定索引j则删除指定位置元素并返回,如无索引则删除并返回最后一项。指定一个不存在的位置则IndexError。
reverse()
将列表的顺序逆转。不进行排序,仅仅前后交换元素位置。
sort([func])
给列表元素排序,可选参数func提供排序的比较函数。
如果需要给排序列表添加或删除项,可以用bisect模块。使用insert(list,item)函数插入项时,就会使用平分算符轻易找到准确位置并插入,并保持列表的排序状态。同一模块中的bisect(list,item)函数用于找到准确的插入点,而并不插入元素。
4.4用词典映射信息
词典包含键/值对。键和值都可以任意取值。
4.4.1创建并添加到词典
通过在大括号中列出0个或多个键值对,可以创建词典。词典中使用的键必须是唯一的且不可变。
>>> logins{'yahoo':('join','jjj'),'hotmail':('trf','18th')}
>>> logins['hotmail']
('trf','18th')
4.4.2访问和更新词典映射
访问不存在的键时会抛出KeyError异常。如果不关心异常情况时,可以使用get(key[,obj])方法来替换,当映射不存在时就返回None,甚至可以指定默认值obj。
setdefault(key[,obj])方法与get()的运行方式一样,也采用默认参数,只是当键值对不存在时会添加到词典中。存在则返回值,不存在的在添加之后也会返回值。
判断是否存在一个键,用has_key(key)方法,返回True/Flase。
del用于删除词典中的项。
散列功能:一个对象拥有散列值,用于快速比较,如果两个对象都有散列值则无需直接比较,直接比较散列值即可。词典中使用散列的方法快速查询词典。使用hash(obj)函数可以查询对象的散列值。
>>> hash('hash')
-1671425852
>>> hash(10)
10
>>> hash(10.0)
10
>>> hash((1,2,3))
-821448277
在不可散列的对象之上会产生TypeError异常。
使用U
update(dict)方法将一个词典中所有的键值对添加到另一个词典中。
4.4.3附加的词典运算
len(dict)返回键值对的数量
dict.keys()返回键的列表
dict.values()返回值的列表
dict.items()返回键值对的元组形式列表
dict.clear()清除所有键值对
dict.popitem()每次弹出一个键值对,并删除词典中对应项。
>>> try:
... while True:
... print d.popitem()
... except KeyError: #当词典被全部删除空时会发生
... pass
popitem()是在Python2.1中开始提供的。
dict.copy()方法创建词典的副本。副本有深浅之分,详细查阅后面。
6.6理解引用
Python把一块数据存储在对象中,而且变量是对于对象的唯一引用;他么是计算机内存中特殊地点的名字。所有的对象具有唯一的身份号,类型和值。
4.5.1对象身份
Python把数据存储在对象中,变量是对对象的唯一引用;是计算机内存中的特殊地点的名字。所有对象都具有唯一的身份号、类型和值。
函数id(obj)用于检索对象的身份(在当前实现中指内存中对象的地址)。
is运算符用于比较两个对象的身份,查看是否相同。(by gashero)注意区别==。is运算符是查看是否引用同一个对象,而==查看值是否相同。
由于变量只是引用对象,所以更改对象的值,对所有引用他的变量都是可见的。但是对如同a=a+1或a+=1这类表达式,却是计算新值后创建一个具有新值的对象并赋给a。
>>> a=6
>>> b=a
>>> id(a);id(b)
9065476
9065476
4.5.2计算引用的数目
对象包含引用计数,表示当前有多少个变量正在引用该对象。当引用计数达到0时就可以撤消对象了。从Python2.0开始采用了循环引用收集对象。如:
a=[];b=[]
a.append(b);b.append(a)
a=5;b=10
这时可以正确识别出这种情况并收回内存。
del语句用于删除变量,而不是对象。
另外还有一种弱引用,和不影响对象引用计数的引用,参见第7章。
4.6复制复合对象
复制列表等等的方法。分两种。
4.6.1浅副本
列表或其他容器对象的浅副本(Shallow)能够生成对象本身的副本和新的引用。
生成序列的浅副本可以用整个对象的切片切片。
>>> faceCard=['A','J','K','Q']
>>> myHand=faceCard[:] #创建一个副本,而不是引用
>>> myHand is faceCard
0
>>> myHand == faceCard
1
也可以使用copy模块的copy(obj)函数
>>> import copy
>>> highCards=copy.copy(faceCards)
>>> highCards is faceCard,highCards==faceCard
(0,1)
4.6.2深副本
深副本(Deep)能够生成容器对象的副本,并递归的生成所有子对象的副本。
父列表的浅副本是对子列表的引用,而不是独立副本。而深副本也将同时复制子列表。
使用copy模块中的deepcopy(obj)函数可以创建深副本:
import copy
yourAccount=copy.deepcopy(myAccount)
这个函数会会考虑对象引用自身的情况,并且只创建一个对象的一个副本。
并不是所有的对象都可以安全的复制,如套接字就不可以,文件对象也不可以。并且对文件对象的复制会产生错误。
第7章会介绍通过自己定义对象的行为来控制响应浅副本和深副本的操作方式。
4.7标识数据类型
可以在运行时检查对象的数据类型,通过type(obj)函数:
>>> type(5)
<type 'int'>
>>> type('She sells seashells')
<type 'string'>
>>> type(operator)
<type 'module'>
types模块包含Python的内置数据类型的类型对象。下面的例子可以接收字符串或字符串列表来将字母大写。
>>> import types
>>> def upEm(words):
... if type(words)!=types.ListType:
... words=[words] #将字符串转换成列表统一处理
... for word in words:
... print word.upper()
>>> upEm('horse')
HORSE
>>> upEm(['horse','cow','sheep'])
HORSE
COW
SHEEP
下面是几种常见类型
BuiltinFunctionType DictType
FunctionType LambdaType
MethodType StringType
BuiltinMethodType FileType
InstanceType ListType
ModuleType TupleType
ClassType FloatType
IntType LongType
NoneType
类和类的实例分别具有ClassType和InstanceType类型。Python还有isinstance(obj)函数和issubclass(obj)函数来判断实例和子类。
>>> class Foo:
... pass
...
>>> a=Foo()
>>> isinstance(a,Foo)
1
4.8处理数组对象
列表可以处理多种类型,具有很好的灵活性,但是占用内存多,性能低,如果确实需要性能,或者低级的访问,可以使用array模块创建一个数组。
4.8.1创建数组
数组对象与列表相似,只是它仅包含某些类型的简单数据,并且只接受一种类型。需要指定所包含的数据类型。
>>> import array
>>> z=array.array('B') #创建一个字节型数组
>>> z.append(5)
>>> z[0]
5
>>> q=array.array('i',[5,10,-12,23]) #推荐的创建方式
>>> q
array('i',[5,10,-12,23])
下标列出了可用的数组类型,使用itemsize和typecode可以获得项目的大小和类型代码。
代码 | 等价的C类型 | 以字节为单位的最小尺寸 |
---|---|---|
c | char | 1 |
b(B) | byte(unsigned byte) | 1 |
h(H) | short(unsigned short) | 2 |
i(I) | int(unsigned int) | 2 |
l(L) | long(unsigned long) | 4 |
f | float | 4 |
d | double | 8 |
实际的尺寸也可能更大一些,取决于实现方式。
4.8.2在类型之间转换
在与列表和字符串之间的转换。
>>> z=array.array('h',[10,1000,500])
tolist()方法将数组转换成普通列表:
>>> z.tolist()
[10,1000,500]
fromlist(list)方法从列表中转换到数组的末尾:
>>> z.fromlist([2,4])
>>> z
array('h',[10,1000,500,2,4])
如果列表中的某个项目类型不正确,则fromlist函数不会添加任何对象到数组。
tostring()方法将数组转换成字节的序列:
>>> z.tostring()
'/n/x00/xe8/x03/xf4/x01/x02/x00/x04/x00'
>>> len(z.tostring())
6 # 3个项目、每个2字节
fromstring(str)从一个字节串转换为数组
>>> z.fromstring('/x10/x00/x00/x02') # x10=16,x0200=512
>>> z
array('h',[10,1000,500,2,4,16,512])
注意这里面的字节顺序,低字节在先,高字节在后。
tofile(file)将数组转换成字节的序列(如tostring),并将结果写入文件。
fromfile(file,count)从文件提取count数目的项,并附加到数组中。如果在请求的项数之前文件结束了,则产生EOFError异常,但是还会尽可能的给数组添加项。
marshal、pickle、struct模块也提供了附加的方法用于与字节序列之间的转换。以便用于文件和网络传输。
4.8.3数组方法和运算
数组对象支持列表中的很多函数和方法:len、append、extend、 count、index、insert、pop、remove、reverse。采用下标访问成员,并且可用切片返回另一个数组对象。
buffer_info()方法用于返回与当前数组有关的一些低级信息。返回的元组包含缓冲区的内存地址、长度(byte)。在撤消数组或更改长度之前,这个信息一直有效。
可用byteswap()方法更改数组中每个项的字节次序,可用于高序优先和低序优先之间的转换。参见平台相关的字节排序信息,12章。
NumPy是一个Python扩展用于计算领域,也可以创建自己的数组,并且有更好的支持。参见31章。
4.9小结
略...
完成...
------------------------------------------------------------
第五章 控制流
5.1用if语句做出决策
if语句计算表达式的值,如果表达式为真则执行if块。可以包含else块用于表达式为假时执行。if语句还可以包含几个elif块,是else if的缩写,当条件不符合时用于判断是否复合另外一个if块。
可以使用多个elif语句以实现其他语言中switch的功能。
Python计算布尔表达式的值时会使用短路操作,首先发现真值则不再计算下去,所以应该把最常出现的情况放在前面来提高性能。
if(条件):
pass
elif(条件):
pass
else:
pass
5.2使用for循环
5.2.1for循环的剖析
用于重复处理序列中的元素。
for <variable> in <sequence>:
(loop body)
对于序列中的每一个元素都会执行一次该循环体,按照顺序取遍序列中的所有元素。Python允许使用多种类型的序列类型,包括字符串。
5.2.2循环示例:给字符串编码
把字符串转换成一列十六进制值,再转换回来。
import string
def Encode(MessageString):
EncodeList=[]
for Char in MessageString:
EncodeList.append("%x" % ord(Char))
return EncodeList
def Decode(SecretMessage):
DecodeList=[]
for HexValue in SecretMessage:
DecodeList.append(chr(int(HexValue,16)))
return string.join(DecodeList,"")
if(__name__="__main__"):
SecretMessage=Encode("Remember to drink your Ovaltine")
print SecretMessage
print Decode(SecretMessage)
5.2.3Ranges和xranges
对于一些固定次数的循环可以用range()函数生成所需数的序列。
for x in range(10):
print x
定义:range([start,]end[,step])范围内的数字以start开始到end,每次步进距离为step。start和step都是可选的,默认分别为0和1。
xrange()用于表示一个范围内的数字对象,占用内存很小,只保存一些标记,而不是整个列表。参数同上。有tolist()方法用于生成列表。
5.2.4中断、继续和else子句
continue会跳到下一次重复、break会中断当前循环。而for语句接的else子句用于在整个循环过程中都没有发生break时进入,如果发生了break则不会进入else子句。
5.2.5在中途换马
不推荐在循环中修改循环序列,很不适合于阅读。另外,在第一次进入循环之前,循环次数就已经确定了,无论是否更改序列。
所以不要在循环中修改循环序列。
5.3使用while循环
可以在循环中同时使用if和for。
while(<expression>):
<block of code>
遇到while语句时会计算表达式的值,如果表达式为真则继续执行代码块,如果表达式为假则退出循环。
while循环内部也可以使用continue、break,并允许有else子句。
5.4抛弃和捕获异常事件
5.4.1扩散异常事件
当遇到不能处理的情况时就会产生一个异常,异常事件是代表错误的Python对象。发生异常时会立即停止正常的流程,并产生异常对象,如果调用程序没有处理则继续想外抛,直到被处理为止,如果依然无法处理则最终导致解释器停止。
通常有返回值的函数返回None代表合理的故障,而不合理的故障则返回异常。
5.4.2处理异常事件
将可疑代码放入try:块中,并在except子句接收异常并处理。
try:
#可疑脚本
except 异常:
#异常处理
5.4.3有关异常事件的更多信息
异常事件可以带有一个参量提供更多的附加信息。except 异常类型 参量:
也可以提供多个except子句处理不同的异常,上面使用较为具体的异常,下面使用概括的异常来截获尽可能多的异常。
except子句之后可以有else子句来处理没有异常发生时需要作的事情。
下例是处理文件打开异常IOError的:
try:
OptionsFile-open("secretOptions.txt")
except IOError,(ErrorNumber,ErrorString):
pass
else:
ParseOptionsFile(OptionsFile)
5.4.4定义和产生异常事件
可以用raise exceptionType,argument语句产生异常事件。分别指定事件类型和参量,参量是可选的,如果没有则默认为None。
异常事件可以是字符串、类、也可以是对象,Python核心产生的异常大多是类。
raise "out of level",Level
为了捕捉异常事件,except子句一定要引用已丢掉的同一个异常事件。Python根据引用标识符(is、not==)比较字符串异常事件。需要捕获异常时应该使用字符串常量或者类。
5.4.5用finally进行清除
finally块放置一些必须执行的代码,而不管是否产生异常事件,作为try:的子句,但是不可以与except子句同时存在。
比如释放线程锁。
5.5用断言进行调试
断言打开以发现错误,关闭来加快速度。产生AssertionError意味着程序员的错误。可以放在函数开头检查输入,并在函数末尾检察输出。
5.5.1Python中的断言
采用assert<expression>句法添加断言。遇到assert语句时计算表达式的值,为真则通过,为假则产生AssertionError异常。
assert Expression,ArgumentExpression句法可以引入一个断言参量,如果断言失败还可以传递一个参数过来。
例如一个转换摄氏温度和绝对温度的程序判断绝对温度的值必须大于0。
5.5.2触发断言
通常断言处于活动状态,由内部变量__debug__触发。打开优化(-O)就会关闭断言。
注意不要让断言改变了什么值。
5.6示例:Game of Life
一个示例程序,小游戏。
5.7小结
略...
完成...
----------------------------------------------------
第六章 程序组织
6.1定义函数
def FunctionName([parameters,...]):
用于定义函数。def的下一行语句是文档字符串,用于标识函数的用途,使用方法等。很多工具可以提取文档字符串。
return [expression]语句将退出函数,可选择性的把表达式传递给程序,没有表达式的return与return None相同。最后一条语句执行完成时也会自动返回。
6.1.1通过对象引用传递
Python变量是对某个对象的引用,但是在调用函数时使用值传递,更改参数所引用的内容,则不会影响实参。
myTip:形参是变量,是对实参变量所指对象的引用。如果在函数里改变了形参所引用的对象,则实参的引用不会改变,仍然指向原对象。但是如果通过形参改变了对象的值,则会反映到实参那里。
>>> def StupidFunction(InputList):
... InputList[0]=3
... InputList=["I","Like","Cheese"]
...
>>> MyList=[1,2,3]
>>> StupidFunction(MyList)
>>> print MyList
[3,2,3]
6.1.2有关参数的所有信息
函数参数可以包含默认值,对这种参数调用时可以不提供值。函数对其参数默认值只计算一次,所以即使给参数指定的默认值是时间相关的函数也只是取第一次调用的值。
一个例子,打印时间,如果没有提供时间则打印当前时间:
def PrintTime(TimeStamp=None):
if(TimeStamp==None):
TimeStamp=time.time()
print time.asctime(time.localtime(TimeStamp))
6.1.3任意参量
参数数量不限,将参数汇集到一个元组中并提供。
def LogObjectIDs(LogString, *args):
print LogString
for arg in args:
print id(arg)
也可以接收命名参数到词典:
def LogObjectIDs(LogString, **kwargs):
print LogString
for(ParamName,ParamValue) in kwargs.items():
print "Object:",ParamName,"ID:",id(ParamValue)
如果需要一个功能齐全的函数,可以获取一个有关任意命名参数的词典和一个有关未命名参数的元组。
6.1.4应用:从元组中传递参量
函数apply(InvokeFunction,ArgumentSequence)调用函数InvokeFunction,传递ArgumentSequence中的元素作为参量。apply()的一个用途是对任意长度的元组都可以从元组中分离初参量。
>>> print MyColor
(255,0,255)
>>> SetColor(MyColor[0],MyColor[1],MyColor[2]) #麻烦的方法
>>> apply(SetColor,MyColor) #效果同上,但是简洁了一些
6.1.5带有一点功能性的编程
可用lambda关键字定义匿名函数。
lambda [parameters,...]: <expression>
例如一个筛选列表项的函数。
>>> SomeNumbers=[5,10,15,3,18,2]
>>> filter(lambda x:x>10, SomeNumbers)
[15,18]
整个lambda函数是作为一个函数存在的,只不过直接写在固定的位置,接受参数,并返回表达式的值,而不需要函数名。另外lambda函数不可以包含语句。表达式可以返回的类型也不限。
lambda函数不可以调用print,因为其子句只可以是一个表达式。
lambda函数拥有自己的本地名字空间,而且不能访问他们的参数列表之外的变量,也不能访问全局名字空间的变量。
6.2用模块为代码分组
模块就是由Python代码组成的文件,可以定义函数、类、变量、可运行的代码。通常把独立模块称为脚本或程序。
6.2.1布局模块
模块元素的常规次序:
-
文档字符串或一般注释(修订版记录或版本信息)
-
导入语句
-
模块级变量("常量")定义
-
类和函数的定义
-
主函数(如果有)
这种结构不是必须的,但是有助于相互理解。人们通常会将有用的值频繁的存在ALL_CAPS_VARIABLES(全部大写的变量)中,使以后的代码更易于维护或阅读。例如某些变量全部由大写字母构成,Python没有禁止修改,但是按照惯例不应该去修改。
6.2.2获取模块的清单
函数dir(module)能够返回模块中定义的一列变量,函数和类。如果没有参数则返回当前的。dir(__builtin__)返回所有内置的名字。结果存放在列表中。
6.3导入模块
要使用模块就必须先导入,之后用点符号访问模块中的名字。
import 模块名
另一种方式
from ModuleName import Name1,Name2,...
这种句法会将模块中所有的名字导入到当前空间中。不需要用圆点符号来访问了,可以直接访问。
要导入一个模块中的所有名字到当前名字空间则用:
from module import *
这种方式会可能会造成混乱的代码,尤其是当两个模块具有同名函数时。但也会节省很多输入。
脚本的导入语句应该放在文件的开始位置,虽然中间使用也是可以的,但是比较混乱。
6.3.1导入时还会发生什么现象
模块内,内置变量__name__是模块的名字,当执行一个独立模块时,他的__name__将是__main__。这样就可以设置一个主函数,仅在独立调用时会执行,而被导入时不会执行。
if(__name__=="__main__"):
#主函数代码
6.3.2重新导入模块
一旦Python导入了一次模块后,其后的import语句就不会重新导入,但是有时用于调试的可以动态的重新导入模块,reload。
6.3.3外来的导入
通过实现__import(name[,globals[,locals[,fromlist]]])函数,模块可以超越标准导入行为。由于模块是一个类,所以在模块内定义__import__等于超越__import__的默认版本。
建议不要使用,因为是语言中的低级操作,有关超越导入行为的示例参见imp、ihooks和rexec库。
6.4定位模块
当导入模块时,Python解释程序优先在当前路径搜索这个模块,然后是PythonPath的每个目录,之后是默认路径。有些模块如sys是内置到语言当中的,不包含.py文件。
Python把可以搜索模块的目录列表存放在sys.path变量中。
6.4.1Python路径
PythonPath是一个环境变量,包含一列目录。Windows的典型值如下:
set PYTHONPATH=c:/python20/lib; c:/python20/lib/proj1; c:/python20/lib/bob
下面是UNIX系统的典型值:
set PYTHONPATH=/home/stanner/python;/usr/bin/python/lib
笔者一般使用临时文件夹保存正在使用的模块。而将其他文件放在lib目录下。显示的声明PythonPath对模块的不同版本之间切换是有用的。
6.4.2编译文件
可以把Python程序编译为独立于系统的字节码,扩展名为.pyc。这个预编译文件运行速度与源码相同,但是不再需要源码解析,所以加载速度快。采用优化标志编译的文件都是用.pyo扩展名,行为与.pyc相似。
当导入一个模块时Python优先查找已经编译的版本,如果找不到在去用.py文件,并且还是要编译。
从命令行运行脚本时不会编译,为了节省解析时间,可以创建一个专用于调用的小脚本。也可手动编译:
py_compile.compile(ScriptFileName)
或
compilcall.compile_dir(ScriptDirectoryName)
6.5理解作用域规则
变量是映射为对象的名字。可以访问局部和全局变量,对于同名,局部的屏蔽全局的。
每个函数都有自己的局部名字空间。类方法和普通函数规则相同。Python经由self变量访问对象属性,并不是作为属性引入名字空间。在模块层或交互回话中,局部名字空间和全局名字空间相同。对eval、exec、 execfile、input语句来说,局部名字空间和调用程序的名字空间相同。
6.5.1是局部还是全局
Python会猜测局部还是全局变量。先假定函数中的赋值变量是局部的。因此为了给函数内的全局变量赋值,一定要使用全局语句global VarName通知Python,VarName是全局变量。
例如如下函数在注释掉全局语句后会出错:
NumberOfMonkeys=11
def AddMonkey():
#global NumberOfMonkeys
NumberOfMonkeys=NumberOfMonkeys+1
6.5.2列出名字空间的内容
内置函数locals和globals将以目录格式返回局部名字空间内容和全局名字空间内容。可用于调试。
6.6将模块组合为包
可以将相关模块组合成一个包。包还可以包含子包,最终可以组成具有倒置树桩的包关系。例如seti.log.FlushLogFile()函数调用seti包内log模块的FlushLogFile()函数。
Python通过查找一个包含__init__.py文件的目录来定位包。该目录可以是子目录或sys.path中的任意子目录,目录名就是包名。
当导入包时,__init__.py脚本就会执行。它可以是空文件,但至少应该包含一个文档字符串。也可以定义特殊变量__all__,用于管理from PackageName import *表单的外层导入的行为。如果定义了这个变量,它就是要引入当前名字空间的一列模块名。如果__init__.py没有定义__all__则外层导入只会把__init__.py中已定义的名字及已导入的模块引入当前名字空间。
有关安装包,模块和发布自己的信息参见36章。
6.7按计划编译和运行
exec语句可以运行一大块Python代码。
exec ExecuteObject[ in GlobalDict[,LocalDict]]
eg: exec "print 'hello'" in {},{}
ExecuteObject是字符串、文件对象或包含Python代码的代码对象。GlobalDict和LocalDict分别是用于全局名字空间和局部名字空间的词典,都是可选的,优先选GlobalDict。省略后两个参数时,将以当前名字空间运行。
eval函数用于计算Python表达式的值。
eval (ExpressionObject[,GlobalDict[,LocalDict]])
其中ExpressionObject是字符串或代码对象。GlobalDict和LocalDict的语义学与exec中的相同。
execfile函数的用法与exec相同,但只是获取文件名对象。
这些函数如果有句法错误都会有异常发生
compile函数能够把代码字符串转换为可运行的代码对象。Python把该代码对象传给exec或eval。
compile(CodeString,FileName,Kind)
CodeString是字符串,FileName是描述代码起源的字符串,如果用于读出文件中的代码,则为文件名。Kind是描述字符串的。
exec执行一个或多个可执行语句。eval计算一个表达式。single一个语句,如果不是None则计算表达式的值。
多行表达式应该包含两个换行,一边传递,可能将来的版本会修复。
6.8小结
略...
完成...