Table of Contents
1. 绪论
2. python编程概述
2.1. 知识点
2.2. 良好的编程习惯
2.3. 常见编程错误
2.4. 测试和调试提示
2.5. 移植性提示
3. 控制流程
3.1. 知识点
3.2. 良好的编程习惯
3.3. 常见编程错误
3.4. 移植性提示
3.5. 软件工程知识
4. 函数
4.1. 知识点
4.2. 良好的编程习惯
4.3. 常见编程错误
4.4. 移植性提示
4.5. 软件工程知识
4.6. 性能提示
5. 列表、元组和字典
5.1. 知识点
6. 公共网关接口(CGI)入门
6.1. 知识点
7. 基于面向对象的编程
7.1. 知识点
7.2. 良好的编程习惯
7.3. 常见编程错误
7.4. 测试和调试提示
7.5. 软件工程知识
7.6. 性能提示
8. 自定义类
8.1. 知识点
8.2. 良好的编程习惯
8.3. 常见编程错误
8.4. 软件工程知识
8.5. 性能提示
9. 面向对象编程:继承
9.1. 知识点
9.2. 常见编程错误
9.3. 软件工程知识
9.4. 性能提示
10. 图形用户界面组件(一)
10.1. 知识点
10.2. 良好的编程习惯
10.3. 常见编程错误
10.4. 界面知识
11. 图形用户界面组件(二)
11.1. 知识点
11.2. 测试和调试提示
11.3. 界面知识
12. 异常处理
12.1. 知识点
12.2. 良好的编程习惯
12.3. 常见编程错误
12.4. 测试和调试提示
12.5. 软件工程知识
12.6. 性能提示
13. 字符串处理和正则表达式
13.1. 知识点
13.2. 良好的编程习惯
13.3. 性能提示
14. 文件处理和序列化
14.1. 知识点
14.2. 良好编程习惯
14.3. 常见编程错误
14.4. 性能提示
15. 可扩展标记语言(XML)
15.1. 知识点
15.2. 常见编程错误
15.3. 移植性提示
15.4. 软件工程知识
15.5. 性能提示
15.6. 示例
16. Python的XML处理
16.1. 知识点
16.2. 良好编程习惯
16.3. 示例
17. 数据库应用程序编程接口(DB-API)
17.1. 知识点
17.2. 良好的编程习惯
17.3. 常见编程错误
17.4. 移植性提示
18. 进程管理
18.1. 知识点
18.2. 良好的编程习惯
18.3. 移植性提示
19. 多线程处理
19.1. 知识点
19.2. 性能提示
19.3. 常见编程错误
19.4. 测试和调试提示
19.5. 性能提示
20. 联网
20.1. 知识点
20.2. 常见编程错误
20.3. 软件工程知识
20.4. 性能提示
List of Examples
15.1. sort.xml
15.2. sorting.xsl
16.1. 动态生成XML内容
16.2. 一个XML论坛的例子
Chapter 1. 绪论
Chapter 2. python编程概述
Table of Contents
2.1. 知识点
2.2. 良好的编程习惯
2.3. 常见编程错误
2.4. 测试和调试提示
2.5. 移植性提示
2.1. 知识点
raw_input是python的内建函数,要求用户输入,输入结果是一个字符串。 example: test = raw_input("please input a number:/n")
python是一种区分大小写的语言。
id函数返回变量内存位置,type函数返回变量类型。
在python2.2前,只提供一种除法运算符(/),运算的行为(即是Floor整数除法,还是 True浮点除法)是由操作数的类型来决定的。如果操作数全是整数,就执行Floor除法。如一个或两个操作数是浮点数,就执行True浮点除法。
在python 2.2后的所有版本中,设计者决定去除(/)的随意性。决定采用两个操作符,其中/执行True除法;//执行Floor除法。但这样会造成旧版本的程序出错,所以设计者采取了一种折衷的办法。如果不先声明,python还是使用旧的/操作符。如果要使用新的方法就要进行声明,声明方式是: from __future__ import division,这样就可以用/ 和 //了。
格式化字符串 print "test is %d" % integer1,表示方法和c语言差不多。
如果语句太长需要用到“/”这个续行符。
在过程式编程中,程序员把重点放在写函数上,用于执行一些任务的行动被组合成函数,不同的函数进一步进行组合,即构成程序。
采用面向对象编程,程序员的工作主要放在创建自已的“类”上。每个类包含数据及一系列函数。类的数据组件被称为数据成员或属性,类的函数组件则称为方法。
重用,重用,再重用是影响软件开发三大因素。就如影响房地产价格三大因素是地段,地段,不是地段。
2.2. 良好的编程习惯
在程序中使用丰富的注释。注释有助于其他程序员理解程序,有助于程序员调试,并列出有用的信息。以后修改或更新代码时,注释还有助于你理解自已当初编写的程序。
每个程序都应以一条注释开始,描述该程序的用途。
加一些空行来增强程序的可读性。
有意义的变量名可改善程序的“自编档能力”,也就是说,只需读一读程序,就能轻松理解它。
避免标识符以下划线和双下划线开头,因为python解释器可能保留了那些名称,供内部使用。
在二元运算符两端添加一个空格。这样可以突出运算符,增强程序的可读性。
和代数一样,可在表达式中添加原本不需要的括号,使其更清晰。
2.3. 常见编程错误
试图访问一个未赋值的变量,会产生运行时错误。
不要把“==”相等和“=”赋值运算符操混了。赋值符号“=”不能出现在if等的条件语句中。
忘记在if结构中插入冒号是语法错误。if a == b: ...
不要忘记了缩进格式,否则会出现语法错误。由于tab键在不同系统中的长度不同,所以建议将3个空格定为一个缩进级别。如果在一个程序中缩进量不同,会造成语法错误。
2.4. 测试和调试提示
使用 -i 选项(python -i test.py)。会导致编译器在执行了文件中的语句后进行交互模式,这非常适用于调试程序。
为了避免难以察觉的错误,务必在程序中采用统一和正确的缩进。
2.5. 移植性提示
预计在python 3.0中,运算符/只执行True除法。3.0发布后,程序需更新自已的程序。
Chapter 3. 控制流程
Table of Contents
3.1. 知识点
3.2. 良好的编程习惯
3.3. 常见编程错误
3.4. 移植性提示
3.5. 软件工程知识
3.1. 知识点
所谓“算法”,是指解决一个问题的“过程”,它包含两个含义,1是要采取的行动,2 是采取这些行动的顺序。
研究表明,只要三种结构就可以写出所有程序,这三种结构是:顺序结构,选择结构以及重复结构。
算法求精,就是把算法用伪代码逐层分解成可以用python程序实现的过程。
+=符号将符号右边的表达式的值加到左边的变量上,再将结果存回左边的变量。变量要先初始化,如果没有会出错。
range(0,10,1)代表一个从0到9 共10个元素的序列,自增量为1。
与非结构化程序相比,结构化编程所生成的程序要容量理解得多,所以更易测试、调试和修改,而且不易出错。
3.2. 良好的编程习惯
初始化所有变量。
在信号值控制的循环中,当提示输入时,应明确指明信息值是哪一个。
当执行除法运算时,如除数可能为零,请务必明确检测。关在程序中进行相应处理,不要任由错误发生。
在每个控制结构前后各留一个空行,将其同程序的其余部份区分开。
嵌套级别过多,会使程序难以理解,通常应将嵌套控制在3级以内。
避免在for循环主体更改控制变量的值,这有可能导致不易发现的逻辑错误。
3.3. 常见编程错误
将所有浮点数假设为是精确的,会导致不正确的结果。浮点数在大多数计算机中只是近似数。
在赋值符号左边的变量初始化之前试图使用增量赋值是错误的。
如果忘记range函数返回的序列的第一个值是0,可以导致差1错误。
3.4. 移植性提示
python 2.0开始引入增量赋值符号,在老版本中python中使用增量赋值符号是语法错误。
3.5. 软件工程知识
经验表明,用计算机解决问题最有效的办法是为解决方案开发一种算法。一旦开发出正确的算法,通常能根据它方便地生成一个能实际工作的python程序。
在从事大型的、复杂的项目时,一定要开发算法。这样才可能不会导致严重错误,从而推迟项目进度。
Chapter 4. 函数
Table of Contents
4.1. 知识点
4.2. 良好的编程习惯
4.3. 常见编程错误
4.4. 移植性提示
4.5. 软件工程知识
4.6. 性能提示
4.1. 知识点
python的程序组件包括函数、类、模块和包。“模块”是包含函数和类定义的文件。许多模块可以组合成一个集合,称为“包”。
模块位于python安装目录的库目录下。在unix/linux下,是/usr/lib/python2.2或 /usr/local/lib/python2.2,在windows上,则是/python/lib。
函数定义中创建的所有变量都是“局部变量”--只存在于声明它们的函数中。
python定义了3个命名空间,分别是局部(local),全局(global)和内建(built-in)。程序访问标识符的值时,python会按特定顺序搜索命名空间(即按局部,全局和内建顺序)。
import random as randomModule 指定引用名。现在可以用randomModule来引用random 中的函数。
重复使用重复的结构,如for和while;递归使用选择结构,如if和if/else。之间的差别是,重复采用一个重复结构,而递归采用重复的函数调用。两者都要进行终止测试:重复会在循环继续条件为false时终止;递归在识别出基本条件是终止。由计数器控制的重复和递归都是逐渐终止:重复会不断改变一个计数器,直到计数器的值使循环继续条件变为false;递归则不断对原始问题进行简化,直到抵达基本条件。重复和递归都可无休止地进行:如果循环继续检测永远都不能变成false,会发生无限循环;如果递归调用永远不能将问题简化成基本条件,会发生无穷递归。
函数的默认参数设置需在def语句中定义。如:def test(aa =1,bb=2,cc=3):xxx 。
关键字参数可以不按位置顺序出现在函数调用中。用keyword = value方式就可以了。
4.2. 良好的编程习惯
尽快熟悉核心python模块提供的函数和类集合。
避免变量名遮蔽外层作用域中的名称。为此,要注意避免标识符与内建命名空间中的标识符同名,并避免在程序中使用重复的标识符。
使用默认参数可简化函数调用的编写,但有的程序员认为,显式指定所有参数会使程序更易读。
4.3. 常见编程错误
用局部命名空间中的一个标识符遮蔽模块或内建命名空间中的一个标识符,可能引起逻辑错误。
默认参数必须全部靠右。省略非靠右的参数是语法错误。
4.4. 移植性提示
使用核心python模块中的函数,通常可使用程序更易移植。
4.5. 软件工程知识
避免重复别人的劳动。尽量使用标准库模块函数,不要写新函数。这样可加快程序开发进度,并增强可靠性。因为你所使用的是经良好设计和测试的代码。
每个函数都应该只限执行单一的、良好定义的任务,函数名应清楚地描述那个任务。
如果实在想不出能准确表达函数作用的名称,就表明函数可能执行了太多的分散任务,通常,最好把这种函数分解成多个更小的函数。
程序应写为若干个小函数的集合。这样使程序更易编写、调试、维护和修改。
如函数需要大量的参数,表明它执行的任务可能过多。请考虑将函数分解成更小的函数,令其执行单独的任务。函数的def语句尽可能不超过一行。
采用递归方式能解决的任何问题也可采用重复方式(非递归方式)解决。如果递归方式能够更自然地反映问题,并使程序易于理解和调试,通常应该首选递归方式。通常,只需几行代码就可完成一个递归方式,重复方式则相反,它需要大量的代码来实现。选择递归的另一个原因是,重复方案也许不是很直观。
采用清晰的、层次清楚的方式对程序进行“函数化”,有助于保证良好的软件工程,但性能上要付出一定代价。
4.6. 性能提示
不要试图改写现成的模块函数使其更高效,因为这些函数已非常完美了。
一般不要编写会造成调用次数成指数级增加的“斐波拉契”式递归程序。
避免对性能要求高的时候使用递归。递归调用既费时、又耗内存。
一个由多个函数构成的程序,与一个没有任何函数的一体式程序相比,会产生大量的函数调用,这些函数调用会占用大量的处理器时间和内存。但另一方面,一体式程序的编程、测试、调试和维护都比较复杂。因此对程序进行函数化时要综合考虑。保证能兼顾良好的性能和软件工程。
Chapter 5. 列表、元组和字典
Table of Contents
5.1. 知识点
5.1. 知识点
Python支持3种基本序列数据类型:字符串(string),列表(list)和元组(tuple)。
“映射”在其它语言中称为关联数据或“哈希”,是用于存储“键-值”对的数据结构。 python支持一种映射数据类型,字典。
创建序列:aString = "";aList = [];aTuple = ()。列表是可变序列,字符串和元组是不可序列。
Chapter 6. 公共网关接口(CGI)入门
Table of Contents
6.1. 知识点
6.1. 知识点
CGI可用于几乎任何程序语言或脚本语言,比如C,PERL和PYTHON。
最常见的HTTP请求类型是GET AND POST。这些请求从WEB服务器获取资源,并将客户表单数据发送给WEB服务器。get请求将表单内容作为URL的一部份发送。大多数 WEB服务器将GET请求查询字符串限制在1024个字符以内。如果查询字符串超过这个限制,就必须使用POST请求。POST请求中发送的数据不是URL的一部份,用户看不到它们。如果表单包含许多字段,那通常由POST请求进行提交。一些敏感的表单字段,如用户名和密码,也通常使用这种请求类型来发送。GET请求的最简单形式的格式为GET /books/downloads.html HTTP/1.1。服务器收到请求后,会发送一个HTTP标头,如 Content-type:text/html。表明MIME类型,然后服务器发送请求的HTML/XHTML文档中文本(DOWNLOADS.HTML)。
web应用程序采用两类脚本编程,服务器端和客户端。CGI脚本是服务器端脚本的一个例子,客户端脚本的一个例子是javascript。
Chapter 7. 基于面向对象的编程
Table of Contents
7.1. 知识点
7.2. 良好的编程习惯
7.3. 常见编程错误
7.4. 测试和调试提示
7.5. 软件工程知识
7.6. 性能提示
7.1. 知识点
在过程式语言中,基本编程单元是“函数”,在面向对象语言中,基本编程单元是“类”,最终要通过它来实例化(即创建)对象。
__init__方法是类的“构造函数”方法。每次创建类的一个对象时,都会执行它的构造函数。它会初始化对象属性,并返回None。
包括构造函数在内的所有方法至少要指定一个参数。该参数代表要调用其方法的类的对象。人们常把这个参数称为“类实例对象”。但由于这术语容易混淆,所以我们将任何方法的第一个参数都称为“对象引用参数”,或简称“对象引用”。方法必须通过对象引用来访问从属于类的属性以及其它方法。按照约定,对象引用参数称为self。
类的特殊属性。
__bases__ 包含基类的一个元组,类可以从这些基类直接继承。如果类不从其他类继承,元组就会为空。
__dict__ 与类的命名空间对应的一个字典。其中每个键-值对都代表在命名空间中的一个标识符及其值。
__doc__ 类的文档字符串。如果类没有指定文档化字符串,值为None。
__module__ 包含模块(文件)名的一个字符串,类定义于这个模块中。
__name__ 包含类名的一个字符串。
在C++和java等程序语言中,类可明确指出类的客户能访问哪些属性或方法。这些属性或方法被认为是“公共”的。不能由类的客户访问的属性和方法则被认为是私有的。在python 中,对象的属性是肯定能访问的--没有办法阻止其它代码访问数据。然而,python提供一种特别的机制来防止任意访问数据。在属性名附加双下划线前缀。python解释器会对属性执行 “名称重整”。如self.__hour,python会创建一个_Classname__hour的属性。但它一样是可访问的,只是名字变了。
构造函数也可以定义默认参数,从而在客户没有指定参数的前提下,为对象属性指定初始值。还可以定义关键字参数。
析构函数__del__是构造函数__init__的相反,用于执行“终止清理”,然后由解释器回收对象的内存,使内存能被重用。析构函数通常只指定self参数,并返回None。
类的每个对象都拥有在构造函数中创建的所有属性的拷贝。特定情况下,类的所有对象只能共享属性的一个拷贝。为此要使用“类属性”。它是“类范围”的信息(也就是说,它是类的一个属性,而非类的特定对象属性)。它可节省空间和时间,提高性能。为访问类属性,只需为属性名附加类名前缀,再加一个小数点即可。(Classname.xxx)
7.2. 良好的编程习惯
文档化字符串习惯上是一个三引号字符串。这样可以在不改变引号样式的前提下,扩展一个程序的文档(例如添加更多的行)。
尽可包含文档化字符串,使程序更有条理。
将所有方法的第一个参数都命名为self。始终遵循这一命名约定,可确保不同程序员编写的python程序是一致的。
属性名以单下划线开头,虽然在python语法中没有特殊的含义,但单下划线是python 程序员使用类时约定使用的符号,表明程序员不希望类的用户直接访问属性。以单划线开头的属性揭示一个类的接口的相关信息。类如果定义了此类属性,它的客户就只能通过类提供的访问方法来访问并修改属性值。如果不是这样做,通常会导致程序执行期间出现不可预料的错误。
7.3. 常见编程错误
忘记将对象引用(通常是self参数)设为方法定义中的第一个参数,会导致该方法在运行时被调用时,造成严重逻辑错误。
直接访问对象的属性可能导致数据进入不一致状态。一个办法是让类提供“访问方法”,通过一种得到精心控制的方式来读写类数据。
如果忘记在方法内部通过对象引用(通常称为self)来访问由对象的类定义的另一个方法,就会导致严重的运行时错误或者逻辑错误。如全局命名空间包含的一个函数与类的某个方法同名,就会产生逻辑错误。此时,如果忘记通过对象引用来访问方法名,实际会调用全局函数。
7.4. 测试和调试提示
即使提供了访问方法,也无法自动确保数据完整性,程序员必须提供有效性验证。
7.5. 软件工程知识
本书的中心思想是“重用,重用,再重用”。我们的重点是“创建宝贵的类”,创造有价值的“软件资产”。
先初始化对象,再让客户代码调用对象的方法。不能依赖客户代码正确初始化对象。
利用访问方法控制对属性的访问(尤其是写访问)有助于确保数据完整性。
python的类和模块化机利于程序的独立实现。如果代码所用的一个类的实现发生了改变,这段代码是无需更改的。
并不是所有的方法都要作为类的接口的一部份。有的方法是类的其它方法的一种实用方法,不准备供类的客户使用。
将客户不应该访问的任何数据设为私有。
如果类的一个方法提供了构造函数(或其他方法)需要的全部或部份功能,请从构造函数(或其他方法)中调用那个方法。这样可简化代码的维护,并减少代码的实现改变后出错的可能。一个通用规则是:避免重复代码。
合成是软件重用的一种形式,即类的成员引用了其他类的对象。
在类成员引用了另一个类的对象的前提下,使那个成员对象能被公共访问,不但没有违反封装性,而且还可隐藏那个成员对象的私有成员
7.6. 性能提示
链式比较表达式(0 <= a < 10)的效率比非链式表达式( a >= 0 and a < 10)更高,因为链式比较表达式中的每个条件都只执行一次。
如果数据的一个拷贝已经够用,请用类属性以节省空间。
Chapter 8. 自定义类
Table of Contents
8.1. 知识点
8.2. 良好的编程习惯
8.3. 常见编程错误
8.4. 软件工程知识
8.5. 性能提示
8.1. 知识点
运算符+在python中具有多种用途,比如整数加法和字符串连接。这就是运算符重载的一个例子。它会在不同的背景下执行最恰当的运算。
python 类可定义特殊方法__str__,为类的对象提供一个不正式的(即人们更容易理解的)字符串表示。如果类的客户程序包含以下语句:print objectofclass ,那么python会调用对象的__str__方法,并输出那个方法所返回的字符串。如: print test 就会执行以下语句: print test.__str__
前面介绍客户访问对象属性的方法有两种,一种是客户可直接访问属性(使用点访问运算符):另外,也可通过客户定义的访问方法来访问属性。这一节讨论另外一种技术---定义特殊方法,自定义直接属性访问的行为。python提供了一系列特殊方法,类可定义这此方法,以控制点访问运算符操纵类对象的方式。如:
__delattr__ 客户删除一个属性时执行(例如 del anObject.attribute)
__getattr__ 客户访问一个属性名,但在对象__dict__属性中找不到这个名称时执行(例如anObject.unfoundName)
__setattr__ 客户将值指派给对象的属性时执行(例如 anObject.attribute = value)
在多数python运算符和增量赋值符号都能重载,有两个运算符不能重载,即{}和lambda。
如果重载一元运算符(如 + - * ),会自动重载与运算符对应的增量赋值语句。
8.2. 良好的编程习惯
如有必要,请为你创建的模块提供test函数,这些函数可确保模块正常工作,而且能通过演示模块的工作方式,向客户提供额外的信息。如以下语句:
if __name__ == "__main__":
test()
如果另一个程序导入模块,__name__的值就会是模块名,而test函数不会执行。如果模块作为单独的程序执行,__name__的值是“__main__”,test函数就会执行。
8.3. 常见编程错误
从__str__方法返回非字符串值是严重的运行时错误。
在__setattr__方法中,通过点访问运算符为对象属性指派值会造成无穷递归。相反,应使用对象的__dic__属性。见chapter8.2.py
8.4. 软件工程知识
对于大型系统,如果需要严格的数据访问,设计者就应使用__getattr__和__setattr__来确保数据的完整性。大型系统的开发者如果使用python2.2,可借助于Properties这种更高效技术来利用__getattr__ 和 __setattr__。
8.5. 性能提示
有时,更好的方法是重载运算符的增量赋值版本,以便能够“当场”执行操作(也就是说,不通过新建对象来占用额外的内存)。
Chapter 9. 面向对象编程:继承
Table of Contents
9.1. 知识点
9.2. 常见编程错误
9.3. 软件工程知识
9.4. 性能提示
9.1. 知识点
创建新类时,程序员不必编写全新的属性和方法,只需指明新类继承以前定义好的“基类”的属性和方法。新类称为“派生类”。每个派生类本身也可以是未来一些派生类的基类。在“单一继承”中,类只从一个基类派生;但在“多重继承”中,派生类要从多个基类继承。
覆盖后的派生类构造函数通常会调用基类构造函数,从而先初始化基类属性,再初始化派生类属性。
派生类可覆盖一个基类方法,做法是采取相同的名称提供那个方法的一个新版本。
通过继承,可自定义现有软件。首先继承现有类的属性和行为,再添加新的属性或行为,或覆盖基类行为,从而对类进行自定义,使之符合我们的要求。
基类指的是公共特性---从基类继承的所有类者得到了基类的功能。在面向对象设计过程中,设计者要找出公共特性,并对其进行归纳,以构成合理的基类。然后,派生类自定义基类功能之外的功能。
绑定方法调用是通过一个对象来访问方法名,如anObject.method()。非绑定方法调用需要通过类名来访问方法,并专门传递一个对象引用。如Point.__init__(self,x,y),self(Cricle 类的一个对象)作为对象引用传递。
我们在把类想像成一种类型时,就假定要创建那个类型的对象。但是,偶然也需要定义一些类(程序员永远不打算创建它的任何对象)。这样的类称为“抽象类”。由于它们在继承层次结构中作为基类使用,所以通常把它们称为“抽象基类”。我们不为抽象类创建对象。它惟一的用途是提供一个合适的基类,以便其他类从中继承接口,偶尔也继承它的实现,从中实际创建的类称为“具体类”。
python支持“多态性”,通过继承联系在一起的各个不同类的对象可针对同样的消息(方法调用)做出不同的响应。发送给多个类型的对象的相同的消息会呈现出“多种型态”--- 这正是“多态性”一词的来历。例如,假定矩形类从四边形类派生,那么矩形属于四边形的一个更具体的版本,能对四边形类的一个对象执行的操作(比台计算周长或面积),也能对矩形类的一个对象执行。
现在讨论一下多态性的应用。一个屏幕管理器需要显示不同类的大量对象,其中包括软件写好之后再加入系统的新类型。系统要能显示各种几何形状(基类是shape),例如正方形,圆形,三角形,矩形,点,线等等(都从基类shape类派生)。屏幕管理器使用基类引用来管理所有需要显示的类。绘制任何对象时(无论它位于继承层次结构的哪一级),屏幕管理器都只是向对象发送一条draw消息。draw方法在每个派生类中都能被覆盖。shape类的每个对象都知道怎样绘制自已。屏幕管理器不需要关心每个对象的类型,也不需要关心以前是否见过这一种类型的对象---它只需要告诉每个对象draw自已就可以了。
多态性尤其适合实现分层式软件系统。例如在操作系统中,每类物理设备的工作方式都是不同的,但无论如何,从设备“读”和“写”数据的命令肯定能统一。发送给设备驱动程序对象的“写”消息需要在那个设备驱动程序的背景下进行专门解释,这具体取决于设备驱动程序怎样操纵特定类型的设备。但是,就“写”调用本身来说,它和向系统中其它任何设备的“写入”操作没有任何区别---都是将一些数量的字节从内存中放到设备中。面向对象的操作系统可使用一个抽象基类提供适用于所有设备驱动程序的一个接口。然后,通过从这个抽象类继承,派生类可采取类似的方式工作。设备驱动程序具有的功能(即接口)作为抽象基类中的方法提供。在派生类中,对这些方法进行了具体的实现,它们与特定类型的设备驱动程序是相对应的。
通过多态性编程,程序可遍历一个容器,比如由类层次结构各个级别上的对象构成的一个列表。只需发关一条消息就能执行列表中的所有对象。
在python2.2之前的版本中,类和类型是两种截然不同的编程元素。所以程序员不能从内建类型继承,不便使用列表,字典和其它对象提供的高级数据处理能力。自python2.2起,类的本质与行为都发生了变化,消除了类型和类的差异。在将来的所有2.X版本中,程序员可区分两种不同的类,即所谓的“经典类”(它的行为方式与本章前面以及前两章所展示的类相同)以及“新类”(它们具有新的行为)。object类型用于定义新类型。直接或间接继承于object的所有类都具有为新类定义的行为。
所有类都可定义“静态方法”。静态方法可由一个客户调用,即使不存在类的任何对象。通常,静态方法是类的一个实用方法,不需要类的一个对象就能执行。一个类如果想把方法指定为静态的,就必须向内建函数staticmethod传递方法的名称。再为函数调用返回的值绑定一个名称。静态方法不将self指定为第一个参数。这样一来,即使没有类的对象,也能调用静态方法。
静态方法在java等语言中至关重要,这些语言要求程序员将所有代码都放入一个类定义中。使用这些语言,程序员经常要定义只包含表态实用方法的类。随后,类的客户可调用静态方法,这和python程序调用模块里定义的函数的方式非常相似。在python中,静态方法允许程序员更准确地定义一个类接口。如果类的方法不需要类的对象即可执行其的任务,程序员就可将这个方法指定为静态的。
从基类object继承的类也可定义__getattribute__方法,每次访问属性时都会执行它。派生类中的__getattribute__方法必须调用方法的基类版本,才能获取属性值,这是由于假如通过对象的__dict__来访问属性值,会造成对__getattribute__的另一次调用。
python2.2允许新类定义一个__slots__属性,它可列出只允许类的对象拥有的属性。
9.2. 常见编程错误
如果派生类的已覆盖构造函数需要调用基类构造函数来初始化基类的成员,派生类构造函数就必须显式地调用基类构造函数。不从派生类中调用基类构造函数是逻辑错误。
基类方法在派生类中被覆盖后,经常要让派生类版本调用基类版本,并执行一些附加的操作。在这种情况下,如果不使用基类名称来引用基类方法(也就是为基类方法附加基类名称和一个点前缀),会造成无穷递归,因为派生类方法实际调用的是自身。这最终导致系统死机。
不将非绑定方法调用的第一个参数指定为对象引用是逻辑错误。
要保证正确的属性访问,__getattribute__方法的一个派生类版本应该调用该方法的基类版本。如试图通过访问对象的__dict__来返回属性值,会造成无穷递归。
9.3. 软件工程知识
和其他任何类一样,派生类不一定要定义构造函数。如果派生类没有定义构造函数,一旦客户创建类的一个对象,就会创建类的基类构造函数。
创建派生类不会影响其基类源代码;基类的完整性通过继承获得了保持。
在面向对象系统中,类通常是紧密联系的。因此,请归纳出公共属性和行为,将其放入一个基类,然后通过继承来生成派生类。
修改基类时,只要到基类的接口保持不变,就不必修改派生类。
通过多态性,程序员可关注于公共特性,让执行时环境去关心具体特性。程序员可在不知道那些对象是什么类型的情况下,指示大量对象采取恰当的行动。
多态性增强了扩展性:软件如果要调用多态行为,编写时便不用关心要向其发送消息的对象类型。因此可在不修改基本系统的前提下,自由添加新类型对象,令其响应现有的消息。
抽象类为类层次结构的各个成员定义了一个接口。抽象类包含的方法将在派生类中定义。在多态性的帮助下,层次结构中的所有方法都可使用同一个接口。
如果新类定义了__slots__属性,但类的构造函数没有初始化属性值,那么一旦创建该类的一个对象,python就会将None值指派给__slots__中的每个属性。
派生类会继承它的基类的__slots__属性。然而,如果不希望程序为派生类的对象添加属性,派生类就必须定义自已的__slots__属性。在派生类的__slots__中,只包含允许的派生类属性名,但客户仍可为派生类的直接/间接基类所指定的属性赋值。
9.4. 性能提示
如果通过继承生成的类超过所需,可能会无谓地浪费内存和处理器资源。因此,请从最能满足你需求的类继承。
Chapter 10. 图形用户界面组件(一)
Table of Contents
10.1. 知识点
10.2. 良好的编程习惯
10.3. 常见编程错误
10.4. 界面知识
10.1. 知识点
GUI是事件驱动的,也就是说,一旦用户与GUI交互,GUI组件就会生成”事件“(动作)。常见交互包括移动鼠标、单击鼠标按钮、在文字段输入、从菜单选择一个选项以及关闭一个窗口等等。
Tkinter模块中的每一个Tk GUI组件都是从Widget类继承的一个类。所有从Widget派生的类都具有公共的属性及行为。
Entry(输入)组件是一种特殊的屏幕区域,用户可以在其中输入文本,也可显示一行文本。
pack方法规定了组件应该如何放到它的“父”中,以及应该放到什么地方。
如果程序员不指定名称,Tkinter会为每个组件分配一个不重复的名称。要想获得一个组件的完整名称,将组件对象传给str函数即可。
bind方法将一个
insert方法在Entry组件中写入文本,它要取得两个参数:文本插入的位置和包含插入文本的一个字符串。如果第一个参数是INSERT,文本就会在当前光标位置插入。如果是END,则在末尾插入,如:insert(END,text)。不可用delete(start,finish)方法从Entry组件中删除文本。在 Entry组件中,第一个位置编号是0,所以如果delete(0,END)则是删除所有文本。
Widget 的winfo_name方法返回组件名,Entry的get方法返回Entry的内容。showinfo() 函数显示一个标记为"message"的对话框。
Button的command关键字参数指定了在用户选择按钮之后要执行的事件处理程序。
pack的参数,side指明组件要靠在容器的哪一边,可选择TOP(DEFAULT),BOTTOM,LEFT 和 RIGHT。fill指定组件在容器中应占据的空间(在指定的方向上),可设为NONE(DEFAULT),X,Y 或 BOTH。expand设置组件只否自动扩展,填充容器中任何额外的空间。padx and pady可围绕组件插入空白填充。pack_forget方法可从容器中删除一个pack好的组件。
Grid布局管理器将容器分割为一个网格,使组件能以行、列形式放置。组件在网格中的位置由其row和column值决定;网格中的每个单元格都可包含一个组件。行、列编号从0开始。还可以调用rowconfigure和columnconfigure方法来设置行和列。
place布局管理器允许设置一个GUI组件的位置和大小---既可以是绝对值,也可是与另一个组件的相对位置和大小。它较复杂在这里不详细讨论。
10.2. 良好的编程习惯
为每个Button都单独定义一个回调方法,这有助于避免混淆,确保实现目标行为,并简化GUI 的调试。
妥善选择布局管理器可使GUI编程更容易。编程之前,先画好设计图,再选择最合适的布局管理器。可用的布局管理器有三种,Pack(按添加的顺序放置组件),Grid(以行、列形式排列组件), Place(允许程序员指定组件和窗口的大小和位置)。
10.3. 常见编程错误
在同一个容器中使用多种类型的布局管理器,一旦Tkinter试图协调不同管理器的不同需求,就会导致应用程序死机。
pack方法按定义顺序将各组件放到一个容器中,所以,如果错误定义顺序,会导致不可预料的结果,相反,如果放置组件时,采用了为side,expand,fill,padx,pady指派值的方式,就可以忽略其定义顺序,保证获得所需结果。
有时可能指定重叠组件。代码中先出现的组件会被最近添加的组件遮住。
10.4. 界面知识
通过改变按钮的显示效果,可提供一种视觉线索,让用户知道自已已经选中了按钮,而且发生了相应的动作。
Chapter 11. 图形用户界面组件(二)
Table of Contents
11.1. 知识点
11.2. 测试和调试提示
11.3. 界面知识
11.1. 知识点
python巨元件(Python Megawidgets,Pmw)提供高级的GUI组件,它是基于Tkinter模块提供的较少的组件开发的。每个Pmw组件都合并了一个或多个Tkinter组件,从而生成更有用,更复杂的组件。
ScrolledListBox组件叫滚动列表框,是由于列表中的项目个数较多,以至于列表无法在屏幕上显示时的解决方案。
ScrolledText组件其实是能滚动的Tkinter Text组件。
MenuBar组件用于创建菜单。
Canvas组件用于显示文本、图像、线条、和形状。
Scale(滑杆)组件允许用户从一系列整数值中选择。
其它GUI工具包有:PyGTK为Gimp TooKit组件提供了面向对象的接口。GTK是一个高级的组件集,主要在X WINDOWS系统中使用。PyGTK是GTK+的一部份,后者也是一个Python工具包,用于创建图形用户界面。另一个流行的GUI工具包是wxPython,它是一个Python扩展模块,提供了对wxWindows(一个用C++写成GUI库)访问途径。PyOpenGL为OpenGL库提供了一个python编程接口。OpenGL是开发交互式二维和三维图形应用程序的流行方案,
11.2. 测试和调试提示
程序使用Pmw时,如果不调用Pmw.initialise,就不能使用Pmw模块的完整功能。
11.3. 界面知识
菜单项按照它们添加的顺序出现,因此,务必按正确顺序添加。通常按添加顺序从左到右排列。
Chapter 12. 异常处理
Table of Contents
12.1. 知识点
12.2. 良好的编程习惯
12.3. 常见编程错误
12.4. 测试和调试提示
12.5. 软件工程知识
12.6. 性能提示
12.1. 知识点
Python中异常处理的样式及细节基于Modula-3语言,类似于c#和java。
Python使用try语句实现异常处理。try语句包围着可能引发异常的语句。try语句以关键字try 开头,后续一个冒号和一个可能在其中引发异常的代码块。try可指定一个或多个except子句,它们紧接在try块后,每个except子句指定了零个或多上异常类名,它们代表要由except子句处理的异常类型。可在except子句中指定一个标识答,程序用它引用被捕获的异常对象。处理程序利用标识符从异常对象获取与异常有关的信息。如果except子句中没有指定异常类型,则会捕捉所有类型的异常。在最后一个 except子句之后,可选择性地添加一个else子句。如果try块中的代码没有引发异常,就会执行else 子句中的代码。如果try语句中没有指定except子句,那不必须包含一个finally子句,该子句肯定会执行,无论是否发生异常。
异常是一些类的对象,这些类都是从Exception类继承的。如果有一个try块中发生异常,这个块会立即终止,程序控制权会移交给try块后的第一个except处理程序。解释器为了确定相匹配的except,需要将引发的异常类型与每个except类型比较,直到匹配为止。如果类型完全一致,或引发的异常类型是 except处理程序的异常类型的一个派生类,就表明发生了匹配。
如果try块中没有发生异常,解释器将忽略用于try语句的异常处理程序,并执行try语句的else子句(如果有的话),如果没有发生异常,或某个except子句处理了这个异常,程序会从try语句之后的下一个语句恢复执行。如果某个语句发生了异常,但该语句不在try块中,而是在一个函数中,那么包含这个语句的函数会立即终止,解释器会试图在(发出)调用(的)代码中查找一个封闭的try语句,这个过程称为“堆栈辗转开解”。
python使用“异常处理的终止模型”,因为引发异常的try块会在异常发生后立即“过期”。有的语言使用“异常处理的恢复模型”,完成异常处理后,控制权会返回异常引发点,并从那个位置恢复执行。
尽管可在try块中包含任意语句,但通常只包含可能引发异常的语句。在else块中,则放置不会引发异常的、而且只有在相应的try块中没有发生异常的前提下才应该执行的语句。
python的Exception异常类是一种层次结构,从Exception继承的4个类包括SystemExit,StopIteration, Warning以及StandardError。
python中不存在内存泄漏问题,解释器会自动进行“垃圾回收”。但解释器不能完全清除内存泄漏。只要存在对一个对象的引用,解释器就会对其进行垃圾回收。所以,如果程序员偶然保留了不需要的对象引用,仍会出现内存泄漏。
python异常处理机制提供了finally子句,只要程序控制进入相应的try块,就必然会执行这个finally 子句(无论try成功执行与否),所以finally是放置资源回收代码的理想地点。如果try块执行成功,则finally 会在try块终止后立即执行,如果try块发生异常,则会在导致异常的那一行之后,立即执行finally子句。
12.2. 良好的编程习惯
使每类问题都与一个命名得体的异常类关联,使程序结构清晰易读。
在创建程序员自定义异常类之前,应分析python层次结构中现有有异常类,看是否有可满足自已需要的类,只有在程序需捕捉和处理与现有异常类型不同的新异常时,才定义新异常类。
12.3. 常见编程错误
退出程序可能导致资源(比如文件或网络连接)无法由其它程序使用。这称为“资源泄漏”。
在try语句中同时包括except和finally子句是语法错误。可接受的组合形式中有:try/except, try/except/else以及try/finally。
每个try块与其第一个except处理程序之间、两个except处理程序之间、最后一个except处理程序与else子句之间或者try块和finally子句之间,不允许出现任何语句,否则是语法错误。
如果try语句既不包括finally语句,也不包括except语句,那就属于语法错误。如果try语句不包括 except子句,那么必须包括一个finally语句,如果try语句不包括finally语句,那么至少包括一个except子句。
在finally中引发异常是非常危险的,执行finally子句时,如果一个未被捕获的异常正在等候处理,而 finally又引发一个新的、未被该块捕获的异常,那么第一个异常就会丢失,新异常则传递给下一个封闭的try语句。
12.4. 测试和调试提示
在finally块中,必须将可能引发异常的代码封闭到一个try语句中,这样可避免丢失未捕捉的、在finally块执行之前发生的异常。
traceback展示了自异常发生之时起,一个完整的函数调用堆栈。这样一来,程序员就可查看导致异常的一系列函数调用。traceback中的信息包括辗转开解的函数名称:在其中定义了函数的那个文件的名称以及程序遇到错误时的行号。traceback中的最后一个行号是“引发点”(即初始异常引发的位置)。在它之前的一系列行号则是在traceback中每个函数的调用位置。
阅读traceback报告时,请按从下到上的顺序,先读错误消息。然后,向上阅读traceback剩余部份,查找报告中的第一个行号。通常,这就是导致异常的位置。
12.5. 软件工程知识
从设计阶段初期,就要将异常处理策略考虑到系统中。系统实现后再添加有效的异常处理是很难的。
过去,程序员用多种技术来实现错误处理代码。异常处理则提供了一种统一的机制来处理错误,因此,当许多程序员从事同一个大型项目时,相互之间可轻松理解对方的错误处理代码。
通常不要将异常显式地应用于常规控制流程(虽然也可这样做),否则会因为较难跟踪随后发生的大量异常,导致程序变得难以阅读和维护。
要精心选择放入try块中的代码,其中应有好几个语句都可能引发异常。尽量避免为可能引发异常的每个语句都单独使用一个try语句。但是,要想确保正确的异常处理,每个try语句都应尽可能封装一个足够小的代码区,这样一来,一旦发生异常,就可确切把握当时的背景,使except处理程序能正确地处理异常。如果一个try块中的多个语句都可以引发相同的异常类型,就必须用多个try语句确定每个异常的背景。
在一个except子句中,可以指定多个异常,只需在圆括号中使用由逗号分隔的一个异常名称序列即可,要用except子句指定我个异常,这些异常应以某种形式相互关联(如都是因算术计算错误而引发的异常)。将相关的异常分为一组,再为每一组单独使用一个except子句。
引发异常的代码要先释放代码中获取的任何资源,再引发异常。
如果try语句指定了一个finally子句,那么即使try块由一个return语句终止,finally子句的代码也会。执行完成后,才轮到通过return返回调用代码。
12.6. 性能提示
如果没有发生异常,异常处理代码对性能的影响极微(或者说根本没有),所以,相较于在程序逻辑中混合了错误处理逻辑的程序,实现了异常处理的程序效率更高。
Chapter 13. 字符串处理和正则表达式
Table of Contents
13.1. 知识点
13.2. 良好的编程习惯
13.3. 性能提示
13.1. 知识点
python提供了find 和 index等方法来查找字符串。
字符串的count方法返回子字符串在字符串或字符串分片中出现的次数,如果方法没有找到指定的子字符串,方法返回0。
find方法返回子字符串出现时的最低索引位置。如果找不到,则返回-1.index方法类似于find方法。只是假如字符串中不包含子字符串,方法会引发ValueErro异常。这样,程序就可以捕捉该异常,并进行处理。
startswith方法用于判断一个字符串是否以指定子字符串开始,如果是则返回1。
endswith方法用于判断一个字符串是否以指定子字符串结尾,如果是则返回1。
rfind方法从字符串尾部开始搜索,返回首次出现子字符串的索引位置。如果没有发现则返回-1。
rindex方法则返回字符串出现的最高索引位置,没有发现子串就引发ValueError异常。我们的程序就可捕捉该异常,以处理字符串中不包含指定字符串的情况。
replace方法可取得两个子字符串,并在文档中搜索第一个字符串,把它替换成第二个字符串。它还可取得第三个参数,指定最大的替换次数。如只允许替换3次,其它的忽略。
split和join方法用于分解和连接字符串。
所谓正则表达式,是一种"文本模式"(Text Pattern),用于查找与模式相匹配的子字符串。
原始字符串(Raw String)是在字符串之前加上字符前缀r后创建的一个字符串,在原始字符串中,Python 不会把反斜杠/视为转义字符,而是理解为一个/符号。
re.sub函数取得3个参数,第一个指定模式,第二个指定子字符串,第三个指定字符串。在第三个参数指定的字符串中,与模式匹配的每个子字符串都会被替换成第二个参数指定的子字符串。
re.split函数取得两个参数,第一个参数是正则表达式,用模式来描述定界符。函数在定界符处对第二个参数进行分解,返回一个标记列表。
13.2. 良好的编程习惯
如果进行简单的处理,请使用字符串方法。这样可避免复杂的正则表达式所带来的错误,并保证程序的可读性。
正则表达式的模式字符串中常常包含反斜杠字符。使用原始字符串来创建模式,可避免对其中每个反斜杠进行转义的必要,使模式字符串更容易理解。
13.3. 性能提示
构建复杂字符串时,效率更高的一种做法是将不同组件包括到一个列表中,再用join方法汇总字符串,而不要使用连接运算符(+)。
Chapter 14. 文件处理和序列化
Table of Contents
14.1. 知识点
14.2. 良好编程习惯
14.3. 常见编程错误
14.4. 性能提示
14.1. 知识点
python程序开始执行时,会创建3个文件流,包括sys.stdin(标准输入流),sys.stdout(标准输出流),以及sys.stderr(标准错误流)。这些流在程序和特定文件/设备之间建立沟通渠道。
文件打开模式指出文件打开后要进行读取,写入,还是两者兼而有之。各种模式说明如下:
a
所有输出都写到文件尾,如果指定的文件不存在,就创建一个。
r
打开文件以便输入。如果文件不存在,就引发IOError异常。
r+
打开文件以便输入和输出。如果文件不存在,就引发IOError异常。
w
打开文件以便输出。如果文件存在,就删除其中所有数据。如果不存在就创建一个。
w+
打开文件以便输入和输出。如果文件存在,就删除其中所有数据。如果不存在就创建一个。不有一中是以上所有模式后加b,如ab,rb,表示打开二进制文件(也就是非文本形式)输入或输出,注意,只是windows和macintosh平台支持这些模式。
options[]()结构表示,把()里的参数传给[]里的函数处理。这样的结构可避免使用冗长的if/else语句来判断用户菜单选项。
序列化(Serialization)是指将用户自定义类等复杂对象类型转换成字节集,以便存储或通过网络传输。序列化也称为“平坦化”(Flattening),或者“编组”(Marshalling),python用pickle和cPickle模块来执行序列化。
14.2. 良好编程习惯
如果不希望文件内容被修改,应以只读模式打开文件(使用"r"),防止因为不慎而修改文件内容。这是"最小权限原则”的一个例子。
python2.2 中允许程序员在for语句中使用文件对象。如for record in file: ,这样可每次读取file的一行,并将该行指派给recode。程序可立即处理那一行。与通过readlines方法读取一个大文件的内容相比,用这种方法遍历文件中的各行显得更高效。因为使用readlines,必须先将整个文件都读入内存,然后才能对文件内容进行处理。
文件对象提供seek方法,可用它重新定位“文件位置指针”,如file.seek(0,0),会将“文件位置指针”重新定位至文件头。seek的第一个参数是“偏移量”,它是一个整数值,以字节数的形式指定了文件中的一个位置(要根据文件的“seek方向”。第二个参数是可选的,指定偏移的开始位置,或称"seek方向"。seek方向可设为0,表明相对于文件头进行定位;也可设为1,相对于当前位置进行定位;或设为2,表明相对于文件尾进行定位。
python提供shelve模块模拟随机访问文件。shelve对象具有一个字典接口--也就是访问记录信息的记录键。
14.3. 常见编程错误
在用户希望保留原始文件内容的前提下,用"w"模式打开文件以进行输出,是一个逻辑错误,因为文件内容会在用户未得到警告的情况下删除。
14.4. 性能提示
一旦程序不再需要引用文件,就显式地关闭文件,这样可节省程序所用的资源,还有助于改善程序的可读性。
cPackle模块的执行效率高于pickle模块,因为cPickle是用c实现的,并被编译成每种平台上的原生机器语言。
Chapter 15. 可扩展标记语言(XML)
Table of Contents
15.1. 知识点
15.2. 常见编程错误
15.3. 移植性提示
15.4. 软件工程知识
15.5. 性能提示
15.6. 示例
15.1. 知识点
XML(Extensible Markup Language,可扩展标记语言)于1996年由万维网协分(W3C)下属的“XML工作组”开发成功。它是一种可移植的、获得普遍支持的开放式技术(也就是一种非专利技术),它用于对数据进行描述。XML很快就成为在应用程序之间交换数据的一个标准。
XML是区分大小写的,为处理XML文档,需要一种名为“XML解析器”的程序。解析器负责检查XML文档语法,并使XML文档的数据能提供应用程序使用。
在XML中也有命名空间的概念,就象python中的一样。以防止程序员自定义标识符与类库中的标识符出现“命名冲突”。命名空间的前缀标识元素属于哪个命名空间,从而对元素进行区分,如:
Python How to Program
为确保命名空间是独一无二的,文档作者必须提供唯一的URI,一种常见的做法是将URL作为URI使用,因为URL中使用的域名肯定是没有重复的。注意,解析器永远不会去访问这些URL,它们只是一系列用于区分名称的字符。并不引用实际的网页,也不一定需要具有正确的形式。
为了避免为每个标记名都附加命名空间前缀,文档作者可用“默认命名空间”。即不用
尽管XML是文本文件,但通过顺序文件访问技术从中获取数据,显得既不实际,也没效率,尤其是在其中的数据需要动态添加和删除的时候。所以我们需要一种解析器,对XML文档进行解析,有些XML解析器能将文档数据解析成树的形式存储在内存中,这个层次化的树结构被称为“文档对象模型(DOM)”树,能够创建这类结构的XML解析器称为“DOM”解析器。
SAX(Simple API for XML)是解析XML文档的另一种方法,使用的是一个“基于事件的模型”。SAX处理文档时,会生成名为“事件”的通知信息。软件程序可“侦听”这些事件,以便从文档获取数据。
DTD(文档类型定义)和Schema(模式)是指定XML文档结构的文档(包含哪些元素是允许的,一个元素可以有什么属性等等)。提供一个DTD或Schema之后,解析器就会读取它,用于验证XML文档的结构是否合法。
DTD使用EBNF(Extended Backus-Naur Form)语法来描述XML文档内容。
一个商务信函DTD文档的例子(节选)
...
以上声明为letter元素定义规则。letter包含一个或多个contact元素、一个salutation、一个或多个paragraph元素,而且必须按这个顺序排列。加号(+)是“出现次数指示符”,表明元素必须出现一次或者多次;星号(*)表示一个可选元素可以出现任意次数;问号(?)表示一个可选元素最多只能出现一次。如果省略出现次数指示符,就默认为刚好出现一次。
一个使用上面DTD的XML文档例子(节选)
...
第一行是XML文档的标准声明,第二行引用一个DTD文档,由三部份组成,1是DTD的根元素名称(letter),2是关键字SYSTEM,指定一个外部DTDY文档;3是DTD文档的名称和位置。
Schema 是W3C推荐规范,XML社区的许多开发者都认为DTD灵活性欠佳,不能满足当今的编程需要。例如,程序不能采取与XML文档相同的方式来处理DTD(例如搜索,或者转换成XHTML等不同的表示形式),因为DTD本身不是XML文档。正因为存在这些限制,促使Schema的问世。和DTD不同, Schema不使用EBNF语法。相反,它使用XML语法,而且本质就是XML文档,可能采取程序化方法进行处理。类似DTD,Schema也需要验证解析器。
Schema使用XSV(XML Schema Validator,XML模式验证程序)来验证XML文档。可在线使用XSV,请访问http://www.w3.org/2000/09/webdata/xsv,要想下载XSV,请访问http://www.ltg.ed.ac.uk/~ht/xsv-status.html
一个使用Schema的XML文档例子
Schema文档通常采用.xsd扩展名。这个例子是上面XML文档的模式文档。
targetNamespace = "http://www.xml.com/books" >
在Schema中,element元素定义一个元素。name和type属性分别指定元素名称和数据类型。complexType元素定义一个复杂类型的元素类型,xsd:string定义title元素的数据类型。
XML 允许作者自行创建标记,以便精确描述数据,各个学术领域的人和组织创建了大量XML词汇表来结构化数据。其中一些词汇表包括MathXML(数学标记语言)、可扩展矢量图形(SVG)、无线标记语言(WML)、可扩展商业报表语言(XBRL)、可扩展用户界面语言(XUL)、以及VoiceXML。 Schema和XSL(可扩展样式表语言)也都是XML语汇表的例子。
MathXML由W3C开发,用于描述数学符号及表达式。以前是需要专业软件包(如LaTeX)才能显示复杂的数学表达式。可解析和呈现MathXML的一个应用程序是W3C的Amaya浏览器/编辑器。可到以下网址下载http://www.w3.org/amaya/user/bindist.html
可扩展样式表语言(Extensible Stylesheet Language,XSL)是一种XML语汇表,用于格式化XML。XSLT是XSL的一部份,负责XSL转换(XSLT),它可根据XML文档创建以格式化好的文本为基础的文档。这个创建过程称为“转换”,其中牵涉到两个树结构,即源树(要转换的XML文档)和结果树(转换结果,例如XHTML)。完成转换后,源树不会发生变化。要执行转换,需要使用XSLT处理器。流行的XSLT处理器包括微软的MSXML、APACHE SOFTWARE FOUNDATION的Xalan 2和Python的4XSLT包。
15.2. 常见编程错误
不正确地嵌套XML标记会导致出错。
属性值必须用双引号或单引号封闭,否则就是错误。
以任何大小写组合形式创建名为xml的命名空间前缀都是错误的。
15.3. 移植性提示
尽管DTD是可选的,但DTD可使不同的程序生成的XML文档保持一致,所以建议使用。
15.4. 软件工程知识
属性无需用命名空间前缀加以限定,因为它们肯定同元素联系在一起。
尽管SAX已经获得了广泛的工业支持,但XML-DEV邮件列表的成员在开发SAX时是独立于W3C的。DOM是正式的W3C推荐规范。
许多组织和个人都在积极地为大范围的应用程序(比如金融业务、医药处方等)创建DTD和Schema。这些集合统称为Repositions,通常可从Web免费下载,如http://www.dtd.com
15.5. 性能提示
处理大型XML文档时,基于SAX的解析通常比基于DOM的解析更有效。尤其重要的是,SAX解析器不会将整个XML文档载入内存。
如果文档只需解析一次,最有效的就是基于SAX的解析,如果程序要从文档快速获取信息时,DOM解析通常比SAX解析更有效。要求节省内存的机器通常使用基于SAX的解析器。
15.6. 示例
Example 15.1. sort.xml
advanced XML
Intermediate XML
Parsers and Tools
Entities
XML Fundamentals
Example 15.2. sorting.xsl
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
doctype-public = "-//W3C//DTD XHTML 1.0 Strict//EN"/>
( | |
Chapter |
( |
Appendix |
( |
Pages:
Media Type:
Chapter 16. Python的XML处理
Table of Contents
16.1. 知识点
16.2. 良好编程习惯
16.3. 示例
16.1. 知识点
在python中,可以使用DOM(文档对象模型)和SAX(Simple API for XML)处理xml文档。
16.2. 良好编程习惯
尽管在python2.0或更高版本中不需要调用releaseNode方法,但请坚持这样做,以确保从内存中清除DOM树。
16.3. 示例
Example 16.1. 动态生成XML内容
perl,perl
ok,ok
test,test
check,check
python,python
jython,jython
上面是用于动态生成XML的原始文档。
#chapter16.1
#making up a text file's data as XML
import sys
print "Content-type:text/xml/n"
#write XML declaration and processing instruction
print """
href = "name.xsl"?>"""
#open data file
try:
file = open( "names.txt","r" )
except IOError:
sys.exit( "Error opening file" )
print "
#list of tuples:(special character,entity reference)
replaceList = [ ( "&", "&" ),
( "<", "<" ),
( ">", ">" ),
( '"', """ ),
( "'", "'" ) ]
#replace special characters with entity reference
for currentLine in file.readlines():
for oldValue, newValue in replaceList:
currentLine = currentLine.replace( oldValue, newValue )
#extract lastname and firstname
last, first = currentLine.split( "," )
first = first.strip() #remove carriage return
#write contact element
print """
file.close()
print ""
以上是将文本转换成XML的程序。
First Name | Last Name |
---|---|
Example 16.2. 一个XML论坛的例子
--- 用于表示一个论坛的XML文档(内含一篇文章)---
===============================================================================
--- 显示所有论坛的XML文档(内含一个feedback论坛) ---
===============================================================================
#!c:/python23/python.exe
# filename: default.py
# Default page for message forums
import os
import sys
from xml.dom.ext.reader import PyExpat
def printHeader( title, style ):
print """Content-type: text/html
"-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml-strict.dtd">
""" % ( title, style )
# open XML document that contains the forum names and locations
try:
XMLFile = open( "../htdocs/XML/forums.xml" )
except IOError:
print "Location: /error.html/n"
sys.exit()
# parse XML document containing forum information
reader = PyExpat.Reader()
document = reader.fromStream( XMLFile )
XMLFile.close()
# write XHTML to browser
printHeader( "my Forum", "/XML/site.css" )
print """
Available Forum
# determine client-browser type
if os.environ[ "HTTP_USER_AGENT"].find( "MSIE" ) != -1:
prefix = "../XML/"
else:
prefix = "forum.py?file="
# add links for each forum
for forum in document.getElementsByTagName( "forum" ):
#create link to forum
link = prefix + forum.attributes.item( 0 ).value
#get element nodes containing tag name "name"
name = forum.getElementsByTagName( "name" )[ 0 ]
#get Text node's value
nameText = name.childNodes[ 0 ].nodeValue
print '
print """
Forum Management
reader.releaseNode( document )
--- 论坛的默认页面 ---
===============================================================================
#!c:/python23/python.exe
# filename: addForum.py
# Adds a forum to the list
import re
import sys
import cgi
#4DOM packages
from xml.dom.ext.reader import PyExpat
from xml.dom.ext import PrettyPrint
def printHeader( title, style ):
print """Content-type: text/html
"-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml-strict.dtd">
""" % ( title, style )
form = cgi.FieldStorage()
# if user enters data in form fields
if form.has_key( "name" ) and form.has_key( "filename" ):
newFile = form[ "filename" ].value
#determine whether file has xml extension
if not re.match( "/w+/.xml___FCKpd___10quot;, newFile ):
print "Location: /error.html/n"
sys.exit()
else:
# create forum files from xml files
try:
newForumFile = open( "../htdocs/XML/" + newFile, "w" )
forumsFile = open( "../htdocs/XML/forums.xml", "r+" )
templateFile = open ( "../htdocs/XML/template.xml" )
except IOError:
print "Location: /error.html/n"
sys.exit()
# parse forums document
reader = PyExpat.Reader()
document = reader.fromStream( forumsFile )
#add new forum element
forum = document.createElement( "forum" )
forum.setAttribute( "filename", newFile )
name = document.createElement( "name" )
nameText = document.createTextNode( form[ "name" ].value )
name.appendChild( nameText )
forum.appendChild( name )
# obtain root element of forum
documentNode = document.documentElement
firstForum = documentNode.getElementsByTagName( "forum" )[ 0 ]
documentNode.insertBefore( forum, firstForum )
# write update XML to disk
forumsFile.seek(0, 0)
forumsFile.truncate()
PrettyPrint( document, forumsFile )
forumsFile.close()
# create document for new forum from template file
document = reader.fromStream( templateFile )
forum = document.documentElement
forum.setAttribute( "file", newFile )
# create name element
name = document.createElement( "name" )
nameText = document.createTextNode( form[ "name" ].value )
name.appendChild( nameText )
forum.appendChild( name )
# write generated XML to new forum file
PrettyPrint( document, newForumFile )
newForumFile.close()
templateFile.close()
reader.releaseNode( document )
print "Location: default.py/n"
else:
printHeader( "Add a forum", "/XML/site.css" )
print """
--- 向forums.xml添加新论坛的脚本 ---
===============================================================================
---用于生成新论坛的XML模板 ---
===============================================================================
#!c:/python23/python.exe
# Adds a message to a forum
import re
import os
import sys
import cgi
import time
#4DOM packages
from xml.dom.ext.reader import PyExpat
from xml.dom.ext import PrettyPrint
def printHeader( title, style ):
print """Content-type: text/html
"-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml-strict.dtd">
""" % ( title, style )
# identify client browser
if os.environ[ "HTTP_USER_AGENT" ].find( "MSIE" ) != -1:
prefix = "../XML/" #Internet Explorer
else:
prefix = "forum.py?file="
form = cgi.FieldStorage()
#user has submitted message to post
if form.has_key( "submit" ):
filename = form[ "file" ].value
# add message to forum
if not re.match( "/w+/.xml___FCKpd___10quot;, filename ):
print "Location: /error.html/n"
sys.exit()
try:
forumFile = open( "../htdocs/XML/" + filename, "r+" )
except IOError:
print "Location: /error.html/n"
sys.exit()
# parse forum document
reader = PyExpat.Reader()
document = reader.fromStream( forumFile )
documentNode = document.documentElement
# create message element
message = document.createElement( "message" )
message.setAttribute( "timestamp", time.ctime( time.time() ) )
# add elements to message
messageElements = [ "user", "title", "text" ]
for item in messageElements:
if not form.has_key( item ):
text = "( Field left blank )"
else:
text = form[ item ].value
# create nodes
element = document.createElement( item )
elementText = document.createTextNode( text )
element.appendChild( elementText )
message.appendChild( element )
#append new message to forum and update document on disk
documentNode.appendChild( message )
forumFile.seek(0,0)
forumFile.truncate()
PrettyPrint( document, forumFile )
forumFile.close()
reader.releaseNode( document )
print "Location: %s/n" % ( prefix + form[ "file" ].value )
# create form to obtain new message
elif form.has_key( "file" ):
printHeader( "Add a posting", "/XML/site.css" )
print """/n
""" % ( form[ "file" ].value, prefix + form[ "file" ].value )
else:
print "Location: /error.html/n"
--- 为论坛添加文章的脚本 ---
===============================================================================
by
at
--- 将XML转换成XHTML的XSLT样式表 ---
===============================================================================
#!c:/python23/python.exe
#display forum postings for non-Internet Explorer browser.
import re
import cgi
import sys
from xml.xslt import Processor
form = cgi.FieldStorage()
# form to display has been specified
if form.has_key( "file" ):
# determine whether file is xml
if not re.match( "/w+/.xml___FCKpd___10quot;, form[ "file" ].value ):
print "Location: /error.html/n"
sys.exit()
try:
style = open( "../htdocs/XML/formatting.xsl" )
XMLFile = open( "../htdocs/XML/" + form[ "file" ].value )
except IOError:
print "Location: /error.html/n"
sys.exit()
# create XSLT processor instance
processor = Processor.Processor()
# specify style sheet
processor.appendStylesheetStream( style )
# apply style sheet to XML document
results = processor.runStream( XMLFile )
style.close()
XMLFile.close()
print "Content-type: text/html/n"
print results
else:
print "Location: /error.html/n"
--- 为不支持XSLT的浏览器将XML转换成HTML ---
Chapter 17. 数据库应用程序编程接口(DB-API)
Table of Contents
17.1. 知识点
17.2. 良好的编程习惯
17.3. 常见编程错误
17.4. 移植性提示
17.1. 知识点
python程序员使用遵循Python DB-API(数据库应用程序编程接口)规范的模块与数据库通信。
DB-API描述了一个用于访问(连接)数据库的Connection对象。程序可用该对象创建Cursor对象,该对象负责处理和接收数据。
利用Cursor对象,可用3种方法从查询结果集中获取行,即fetchone、fetchmany和fetchall。fetchone方法返回一个元组,其中包含存储在Cursor中的一个结果集的下一行;fetchmany方法需要指定一个参数,即返回行的行数,并以元组的元组形式,从结果集中返回后续一系列行;fetchall方法则采用“元组的元组”的形式,返回结果集中的所有行。
DB-API的优点是,程序能方便地连接各种数库,python代码修改量很少,但不同数据库间转换时SQL代码需作修改。
17.2. 良好的编程习惯
习惯上,在不区分大小写的系统上,SQL关键字要全部采用大写字母。以突出显示SQL的关键字。
程序不再需要Cursor和Connection对象后,用相应的close方法显式关闭它们。
17.3. 常见编程错误
SQL语句将单引号(')作为字符串的定界符。如果字符串本身包含有一个单引号(如 don't),就必须用两个单引号来表示一个单引号(如 don''t)。如果没有这样转义,就会造成SQL语句语法错误。
17.4. 移植性提示
并非所有数据库系统都支持like运算符,有的数据库在like表达式中使用的不是%,而是*。
Chapter 18. 进程管理
Table of Contents
18.1. 知识点
18.2. 良好的编程习惯
18.3. 移植性提示
18.1. 知识点
有两种方式来实现并发性,一种方式是让每个“任务"或“进程”在单独的内在空间中工作,每个都有自已的工作内存区域。不过,虽然进程可在单独的内存空间中执行,但除非这些进程在单独的处理器上执行,否则,实际并不是“同时”运行的。是由操作系统把处理器的时间片分配给一个进程,用完时间片后就需退出处理器等待另一个时间片的到来。另一种方式是在在程序中指定多个“执行线程”,让它们在相同的内存空间中工作。这称为“多线程处理”。线程比进程更有效,因为操作系统不必为每个线程创建单独的内存空间。
新建进程用os.fork函数。但它只在POSIX系统上可用,在windows版的python中,os模块没有定义os.fork函数。相反,windows程序员用多线程编程技术来完成并发任务。
os.fork 函数创建进程的过程是这样的。程序每次执行时,操作系统都会创建一个新进程来运行程序指令。进程还可调用os.fork,要求操作系统新建一个进程。父进程是调用os.fork函数的进程。父进程所创建的进程叫子进程。每个进程都有一个不重复的进程ID号。或称pid,它对进程进行标识。子进程与父进程完全相同,子进程从父进程继承了多个值的拷贝,如全局变量和环境变量。两个进程的唯一区别是fork的返回值。子进程接收返回值0,而父进程接收子进程的 pid作为返回值。
用os.fork创建的子进程和父进程作为异步的并发进程而单独执行。异步是指它们各行其是,相互间不进行同步;并发是指它们可同时执行。所以我们无法知道子进程和父进程的相对速度。
os.wait 函数用于等待子进程结束(只适用于UNIX兼容系统)。该函数返回包含两个元素的元组,包括已完成的子进程号pid,以及子进程的退出状态,返回状态为 0,表明子进程成功完成。返回状态为正整数表明子进程终止时出错。如没有子进程,会引发OSError错误。os.wait要求父进程等待它的任何一个子进程结束执行,然后唤醒父进程。
要指示父进程等候一个指定的子进程终止,可在父进程中使用os.waitpid函数(只适用于unix兼容系统)。它可等候一个指定进程结束,然后返回一个双元素元组,其中包括子进程的pid和子进程的退出状态。函数调用将pid作为第一个参数传递,并将一个选项作为第二个选项,如果第一个参数大于0,则waitpid会等待该pid结束,如果第一个参数是-1,则会等候所有子进程,也就和 os.wait一样。
用os.system 和 os.exec函数族来执行系统命令和其它程序。os.system使用shell来执行系统命令,然后在命令结束之后把控制权返回给原始进程; os.exec函数族在执行完命令后不将控制权返回给调用进程。它会接管python进程,pid不变。这两个函数支持unix和windows平台。
os.popen ()函数可执行命令,并获得命令的stdout流。函数要取得两个参数,一个是要执行的命令,另一个是调用函数所用的模式,如“r"只读模式。 os.popen2()函数执行命令,并获得命令的stdout流和stdin流。函数返回一个元组,其中包含有两个文件对象,一个对象对应stdin 流,一个对象对应stdout流。
进程使用IPC机制在进程间传递信息,一种IPC机制是“管道”,它是一种类似于文件的对象,提供单向通信渠道。父进程可打开一个管道,再分支一个子进程。父进程使用管道将信息写入(发送到)子进程,而子进程使用管道从父进程读取信息。在 python中使用os.pipe函数创建管道。
os._exit()类似于sys.exit(),但它不执行任何的清除工作(例如刷新缓冲区)。所以os._exit()尤其适用于退出子进程。如果程序使用sys.exit(),操作系统会回收父进程或其它子进程可能仍然需要的资源。传给os._exit()函数的参数必须是进程的退出状态。退出状态为0,表示正常终止。
进程也可用信号进行通信。所谓“信号”,是操作系统采取异步方式传给程序的消息。如CTRL+C会传递一个“中断信号”,通常该信号导致程序中止。然而程序完全可以指定用不同的行动来响应任何一个信号。在信号处理中,程序要接收信号,并根据那个信号采取一项行动。错误(例如向已关闭管道写入)、事件(例如计时器变成0) 以及用户输入(例如按ctrl+c)都会产生信号。
针对每个信号,每个python程序都有一个默认的信号处理程序。例如,假定python解释器收到一个信号,该信号指出程序试图向已关闭的管道写入,或者用户敲入一个键盘中断,python就会引发一个异常。发生异常后,程序既可使用默认处理程序,也可使用自定义处理程序。
signal.signal函数为中断信号注册一个信号处理程序。函数要获得两个参数:一个信号和一个对应于信号处理程序的函数。
在unix/linux 系统中,子进程终止后,会保留在进程表中,让父进程知道子进程是否正常终止。如果创建大量子进程,但在终止后没有从进程表中移除它们,进程表便会积累越来越多的死进程,这些进程称为“zombies”(僵尸进程),消除僵尸进程的操作称为“reaping”,这是通过os.wait和os.waitpid 函数实现的。
18.2. 良好的编程习惯
进程应关闭不需要的管道端,因为操作系统限制了可同时打开的文件说明符数量。
18.3. 移植性提示
并不是所有操作系统都能从一个正在运行的程序创建单独的进程,所以,进程管理是移植性最差的一项python特性。
每个系统都定义了特有信号集。signal是依赖于具体平台的模块,其中只包含系统定义的信号。
Chapter 19. 多线程处理
Table of Contents
19.1. 知识点
19.2. 性能提示
19.3. 常见编程错误
19.4. 测试和调试提示
19.5. 性能提示
19.1. 知识点
线程是“轻量级”进程,因为相较于进程的创建和管理,操作系统通常会用较少的资源来创建和管理线程。操作系统要为新建的进程分配单独的内在空间和数据;相反,程序中的线程在相同的内存空间中执行,并共享许多相同的资源。多线程程序在结内存的使用效率要优于多进程程序。
python 提供了完整的多线程处理类,如果操作系统支持多线程,就可用python的threading模块创建多线程应用程序。程序员可以在一个应用程序中包含多个执行线程,而且每个线程都表明程序中的一部份要与其他线程并发执行。许多应用程序都可获益于多线程编程。Web浏览器下载大文件时(比如音乐或视频),用户希望立即可欣赏音乐或观看视频,这样就可以让一个线程下载,另一个线程播放已经下载的一部分。从而实现多个操作并发执行。
19.2. 性能提示
单线程程序问题在于要在结束费时较长的操作后,才能开始其它操作。而在多线程程序中,线程可共享一个或多个处理器,使多个任务并行执行。
解释器开始执行程序时,“主”线程开始执行。每个线程都可创建和启动其它线程。如果程序包含多个正在运行的线程,它们将依据指定的间隔时间(称为一个 quantum),依次进入和离开解释器。Python的“全局解释器锁”(Global Interpreter Lock,GIL)保证解释器在任何时刻只运行一个线程。GIL每次可用时,都会有单个线程包含它,然后,线程进入解释器,关在该线程的quantum时间段中执行它。一旦quantum到期,线程就离开解释器,同时释放GIL。
在任何时刻,线程都处于某种线程状态。新线程将从“born”状态开始它的生命周期。线程保持这个状态,直到程序调用线程的start方法,这会使线程进入“ready”(就绪)状态,有时也称为 “runnable”状态。另外,控制权会立即返回至调用线程(调用者)。之后,调用者可与已启动的线程以及程序中的其他任何线程并发执行。当 “ready”线程首次获得GIL(Global Interpreter Lock,全局解释器锁),会执行它的run方法,成为一人“running”(正在运行)线程。run方法会一直执行,直到线程引发一个未处理的异常,或者线程离开解释器。running线程离开解释器时,线程会记住它的当前执行位置。以后线程重新进入解释器,线程会从该位置继续执行。线程惟一能获得 GIL的状态就是“running”状态。
run方法返回或终止(如遇到一个未进行捕捉的异常),就会进入“dead”状态。解释器最终会对dead线程进行处理。如果running线程调用另一个线程的join方法,running线程会失去GIL,并等待加入的方法死亡之后才会继续。
大多数程序都使用外部资源(比如网络连接和磁盘文件)来执行任务。如果线程请求的资源不可用,线程就会进入“blocked”(暂停或阻塞)状态,直到资源再次可用,线程发了I/O请求后(比如从磁盘上读入文件,或将文件发送到打印机),会失去GIL,并离开解释器。其它线程就中使用解释器,从而可高效利用处理器,有助缩短程序的总体执行时间。I/O操作完成后,在“blocked”状态等候它的线程进入“ready”状态,之后,线程就会试图重新获得 GIL。
“running”线程调用time.sleep函数后,会释放GIL并进入“sleeping”(休眠)状态。指定的休眠时间到期,“sleeping”线程会返回“ready”状态。即使解释器可用,“sleeping”线程也不能使用解释器。
程序中的线程通常共享数据。如果多个线程修改相同的数据,数据会出错,在这样的程序中,需要同步对共享数据的访问。这意味着访问共享数据的每个线程首先必须获得与数据对应的一个同步对象锁。一旦线程处理完数据,就应释放同步对象,使其它线程能访问数据。
有时因为一个程序的逻辑需求,正在运行的线程即使为共享数据获得了同步对象,也不能对其执行操作。这种情况下,线程可调用同步对象的wait方法以主动释放对象。这会导致线程释放GIL并针对那个同步对象进入“waiting”状态。另一个线程调用同步对象的notify方法时,那个同步对象的一个 “waiting”线程会变成“ready”状态。在重新获得GIL后,线程就会恢复执行。“running”线程调用同步对象的notifyAll方法,处于“waiting”状态的每个线程都会变成“ready”状态。然后,解释器选择一个“ready”线程来执行。
threading.currentThread函数会返回对当前正在运行的线程的一个引用。
threading.enumerate函数返回一个列表,其中包含Thread类的当前所有活动对象(即run方法开始但未终止的任何线程)。
threading.activeCount函数返回上面列表的长度。
isAlive可测试线程是否死亡,如果返回1代表线程正在运行;setName方法设置线程名称;getName方法返回线程名称;为一个线程使用print方法会显示线程名称及其当前状态。
通常将访问共享数据的代码区称为“临界区”
访问共享数据的每个线程都禁止其他所有线程同时访问相同的数据。这称为“独占”或“线程同步”。threading模块提供了许多线程同步机制。最简单的同步机制是“锁”。锁对象用threading.RLock类创建,它定义了两个方法,即acquire(获得)和release(释放)。线程调用 acquire方法,锁会进入“locked”(锁定)状态,每次只有一个线程可获得锁。如另一个线程试图对同一个锁对象调用acquire方法,操作系统会将那个线程转变为“blocked”状态,直到锁变得可用。拥有锁的线程调用release方法,锁会进入“unlocked”(解锁)状态。 “blocked”的线程会收到一个通知,并可获得锁。如果多个锁处于“blocked”状态,所有线程都会先解除该状态。然后,操作系统选择一个线程来获得锁,再将其余线程变回“blocked”状态。
在多线程程序中,线程必须先获得一个锁,再进入临界区;退出临界区时,则要释放锁。
在复杂的线程中,有时只在发生某些事件时才访问一个临界区(比如在某个数据值改变时)。这是通过“条件变量”来完成的。线程用条件变量来监视一个对象的状态,或者发出事件通知。对象状态改变或事件发生时,处于blocked状态的线程会收到通知。收到通知后线程才访问临界区。
条件变量用threading.Condition类创建。条件变量包含基本锁,所以它们提供了acquire和release方法。条件变量的其它方法还有wait和nofify。线程成功获得一个基本锁后,调用wait方法会导致调用线程释放这个锁,并进入“blocked”状态,直到另一个线程调用同一个条件变量的notify方法,从而将其唤醒。notify方法可唤醒一个正在等待条件变量的线程;notifyAll则唤醒所有正在等待的方法。
下面通过一个“生产者/消费者”的关系,说明线程同步的应用情况。在这个关系中,应用程序的“生产者”部分生成数据,“消费者”部分使用数据,在多线程的生产者/消费者关系中,“生产者线程”调用一个“生产方法”来生成数据,并将数据放到名为“缓冲区”的共享内存区域。“消费者线程”则调用一个“消费方法” 来读取数据。如果正在等待放入下一批数据的生产者线程发现消费者线程尚未从缓冲区中读取上一批数据,生产者线程就会调用条件变量的wait方法;否则,消费者线程将无法看到上一批数据。消费者线程读取数据时,应调用条件变量的notify方法,使正在等待的生产者线程继续。如果消费者线程发现缓冲区为空,或上一批数据已读取,就应调用条件变量的wait方法;否则,消费者线程会从缓冲区读入“垃圾”数据,或者重复处理以前的数据项--任何一种可能都会导致应用程序出现逻辑错误。生产者将下一批数据放入缓冲区时,生产者线程应调用条件变量的notify方法,让消费者线程继续。
为尽可能缩短共享资源并以相同相对速度工作的各线程的等待时间,可用一个“队列(Queue)”来提供额外的缓冲区,便于生产者在其中放值,也便于消费者从中获得那些值。
python提供一个Queue模块,该模块定义一人Queue类,即队列的一个同步实现。
“信号机”(Semaphore)是一个变量,控制着对公共资源或临界区的访问。信号机维护着一个计数器,指定可同时使用资源或进入临界区的线程数。每次有一个线程获得信号机时,计数器都会自减。若计数器为0,其它线程便只能暂停访问信号机,直到另一个线程释放信号机。
threading模块定义了可用于线程通信的Event(事件)类。Event有一个内部标记,可为true或false。一个或多个线程能调用Event对象的wait方法以暂停并等待事件发生。事件发生后,暂停的线程会按它们抵达的顺序被唤醒,并恢复执行。
19.3. 常见编程错误
必须把所有临界区都封闭在acquire和release调用之间。
如果程序使用了锁,就要仔细检查,保证程序不会死锁。如果程序或线程永远处于“blocked”状态,就会发生死锁。
等待一个条件变量的线程必须用notify显式唤醒,否则它会永远地等待下去,导致死锁。
19.4. 测试和调试提示
保证每个wait调用都有一个对应的notify调用,以最终结束等待,也可调用notifyAll以策万全。
19.5. 性能提示
通过同步来确保多线程程序的正确性,可能会减慢程序的运行速度,这是由于锁造成了额外的开销,而且需在线程的不同状态间频繁切换。
Chapter 20. 联网
Table of Contents
20.1. 知识点
20.2. 常见编程错误
20.3. 软件工程知识
20.4. 性能提示
20.1. 知识点
python提供流套接字(tcp)和数据报套接字(udp)。
urlparse模块提供了用于解析url的函数,以及用于url处理的函数。
要在python中建立具有TCP和流套接字的简单服务器,需要使用socket模块。利用该模块包含的函数和类定义,可生成通过网络通信的程序。建立这个连接需要6个步骤:
第一步是创建socket对象。调用socket构造函数。如:
socket = socket.socket( family, type )
family参数代表地址家族,可为AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用于同一台机器上的进程间通信。
type参数代表套接字类型,可为SOCK_STREAM(流套接字)和SOCK_DGRAM(数据报套接字)。
第二步是将socket绑定到指定地址。这是通过socket对象的bind方法来实现的:
socket.bind( address )
由AF_INET所创建的套接字,address地址必须是一个双元素元组,格式是(host,port)。host代表主机,port代表端口号。如果端口号正在使用、主机名不正确或端口已被保留,bind方法将引发socket.error异常。
第三步是使用socket套接字的listen方法接收连接请求。
socket.listen( backlog )
backlog指定最多允许多少个客户连接到服务器。它的值至少为1。收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求。
第四步是服务器套接字通过socket的accept方法等待客户请求一个连接。
connection, address = socket.accept()
调用accept方法时,socket会时入“waiting”状态。客户请求连接时,方法建立连接并返回服务器。accept方法返回一个含有两个元素的元组(connection,address)。第一个元素connection是新的socket对象,服务器必须通过它与客户通信;第二个元素 address是客户的Internet地址。
第五步是处理阶段,服务器和客户端通过send和recv方法通信(传输数据)。服务器调用send,并采用字符串形式向客户发送信息。send方法返回已发送的字符个数。服务器使用recv方法从客户接收信息。调用recv 时,服务器必须指定一个整数,它对应于可通过本次方法调用来接收的最大数据量。recv方法在接收数据时会进入“blocked”状态,最后返回一个字符串,用它表示收到的数据。如果发送的数据量超过了recv所允许的,数据会被截短。多余的数据将缓冲于接收端。以后调用recv时,多余的数据会从缓冲区删除(以及自上次调用recv以来,客户可能发送的其它任何数据)。
第六步,传输结束,服务器调用socket的close方法关闭连接。
在python中建立一个简单客户需要4个步骤。
第一步是创建一个socket以连接服务器:
socket = socket.socket( family, type )
第二步是使用socket的connect方法连接服务器。对于AF_INET家族,连接格式如下:
socket.connect( (host,port) )
host代表服务器主机名或IP,port代表服务器进程所绑定的端口号。如连接成功,客户就可通过套接字与服务器通信,如果连接失败,会引发socket.error异常。
第三步是处理阶段,客户和服务器将通过send方法和recv方法通信。
第四步,传输结束,客户通过调用socket的close方法关闭连接。
20.2. 常见编程错误
套接字send方法只接受一个字符串参数。传递不同类型的值(比如整数)会出错。
20.3. 软件工程知识
端口号范围在0--65535之间,很多操作系统为系统服务保留了1024以下的端口号。应用程序在得到特别授权后才能使用这些保留端口号。服务器端应用程序一般不要把1024以下的端口号指定为连接端口。
利用python的多线程能力,程序员可创建多线程服务器,以便同时管理多个并发的客户连接。
多线程服务器可用每个accept调用返回的套接字来创建一个线程,由它来管理通过该套接字进行的网络IO。另外,可让多线程服务器维护一个线程池,以便管理通过新建套接字进行的网络IO。
20.4. 性能提示
在内存充足的高性能系统中,多线程服务器可创建一个线程池。可快速分配这些线程,以管理通过每个新建套接字进行的网络IO。以后收到连接请求时,服务器不会产生创建线程的开销。