winston 发表于 2012-6-9 21:05:39

探索Python:Python初学者入门,功能强大的面向对象脚本语言



第 1 部分: Python 的内置数值类型

Python 编程语言具有很高的灵活性,它支持多种编程方法,包括过程化的、面向对象的和函数式的。但最重要的是,在 Python 中编写程序是件令人愉悦的事。该语言对开发进程有支持作用,决不会妨碍它。本文是关于 Python 编程系列文章的第一篇,主要介绍 Python 及其内置数值类型。

Python 是一种敏捷的、动态类型化的、极富表现力的开源编程语言,可以被自由地安装到多种平台上(参阅 参考资料)。 Python 代码是被解释的。如果您对编辑、构建和执行循环较为熟悉,则 Python 代码对您来说更简单。但是,请不要搞错:Python 器可以是简单的脚本,也可以是大型的复杂程序。事实上,Python 解释器的最大特点是鼓励探索和简化学习过程。如果您想证明这一点,请使用 Python 编写著名的 Hello World! 程序:

   1. 启动 Python 解释器。在 UNIX 系统(包括 Mac OS X)中,启动解释器通常包括在命令提示行键入 python;在 Microsoft® Windows® 系统中,启动 Python 命令 shell。
   2. 在 Python 提示行中,在三个大于号 (>>>) 标志后输入 print 'Hello World!',然后按 Enter。
   3. 完成:没有第三步了。清单 1 显示了此命令的输出。


清单 1. 用 Python 编写的 "Hello World" 的输出
[*]rb% python
[*]Python 2.4 (#1, Mar 29 2005, 12:05:39)
[*] on darwin
[*]Type "help", "copyright", "credits" or "license" for more information.
[*]>>> print 'Hello World!'
[*]Hello World!

复制代码
您可以看到,我使用的是运行于 Apple OS X 系统上的 Python V2.4。但是,不管操作系统是什么,基本原理都是一样的,而且在本例中,所用的是 Python 的哪一个实际版本也无所谓。我虽然不了解您,但是此 Hello World! 练习比我学过的 C、C++ 甚至 Java™ 语言的对应练习容易多了。这种简单性就是使用 Python 解释器的主要优点之一。开发人员可以快速试验一个想法、研究一种对象属性或不同算法,而无需编译、执行和测试任何代码。

Python 类型层次结构

从其他语言过渡到 Python 编程语言时需要学习的最重要的课程之一是,Python 中的每样东西都是对象。这一点可能并没有什么特别之处,尤其是对于熟悉面向对象的语言(如 C++、Java 或 C#)的人来说。然而,Python 的面向对象原理与其他语言不同,主要表现在两个方面:第一,Python 中的所有数据值都被封装在相关对象类中。第二,Python 程序中的所有东西都是可以从程序访问的对象,即使是您编写的代码也不例外。

大多数流行的编程语言都有多个内置的数据类型,在这一方面 Python 也一样。例如,C 编程语言具有整型和浮点类型。由于谱系相同,Java 语言和 C# 具有内置类型也不足为奇。这意味着在 C 程序中,可以编写 int i = 100 来创建和初始化整型变量。在 Java 和 C# 中,此方法也是可能的,而且使用它们的自动装箱功能,在需要时这两种语言还可以把这种简单的内置类型转换为 Integer 对象。

另一方面,Python 不包含像 int 这样的简单类型 —— 只有对象类型。如果 Python 中需要整数值,将整数赋值给相应变量(如 i = 100 )即可。在后台,Python 将创建一个整数对象,并将对新对象的引用赋值给变量。问题的关键是:Python 是一种动态类型化语言,所以无需声明变量类型。事实上在单个程序中,变量的类型是可以改变(多次)的。

一种直观演示动态类型化工作方式的简单方法是,设想单个名为 PyObject 的基类,让 Python 中的所有其他对象类型都继承它。在这一模型中,您创建的所有变量都将引用在总的类层次结构中创建的对象。如果您还让 PyObject 类记录曾创建并分配给变量的子类的实际类型或名称,则 Python 程序可正确确定程序执行过程中需要采取的步骤。

上一段描述 Python 的面向对象的模型图像是对 Python 的实际工作方式很好的模拟。除此之外,Python 还可以使用类型函数来简化对变量类型的确定。(本例还介绍如何使用带有 # 字符的内联注释。)

清单 2. 演示 Python 简单类型
[*]>>> i = 100 # Create an int object whose value is 100
[*]>>> type(i)
[*]<type 'int'>
[*]>>> f = 100.0
[*]>>> type(f)
[*]<type 'float'>

复制代码
可以将 PyObject 类之下的所有 Python 类划分为 Python 运行时解释器可以使用的四个主要类别:

    * 简单类型 —— 基本构建块,如 int 和 float。
    * 容器类型 —— 保存其他对象。
    * 代码类型 —— 封装 Python 程序的元素。
    * 内部类型 —— 程序执行期间使用的类型。

到本系列结束时,我会把所有不同类别都介绍给大家。但是在这第一篇文章中,我重点介绍简单类型。

简单类型

Python 有五个内置的简单类型:bool、int、long、float 和 complex。这些类型是不可变的,就是说整数对象一旦创建,其值便不可更改。相反,系统将创建新的简单类型对象并将其赋值给变量。通过 Python id 函数,可以查看基本 PyObject 标识的变更方式:

清单 3. 使用 Python id 函数
[*]>>> i = 100
[*]>>> id(i)
[*]8403284
[*]>>> i = 101
[*]>>> id(i)
[*]8403296

复制代码
此方法看似容易丢失对象,会导致内存泄漏。但是,Python 像 C# 和 Java 一样,使用了垃圾回收功能,以释放用于保存不再引用的对象的内存,如上例中用于保存 100 的整数对象。

布尔类型

Python 中最简单的内置类型是 bool 类型,该类型包括的对象仅可能为 True 或 False:

清单 4. bool 类型
[*]>>> b = True
[*]>>> type(b)
[*]<type 'bool'>
[*]>>> id(b)
[*]1041552

复制代码
因为只有两个可能值,所以布尔类型是惟一的。Python 解释器提供这仅有的(也是必需的)两个 bool 对象:True 和 False。在任何时候,在 Python 程序需要这些对象时,变量只能相应地引用其中一个值。清单 5 显示 bb 变量如何具有同一个 id,不管您直接赋予它 b 变量的值还是直接赋予它 True 对象。

清单 5. bb 变量的值
[*]>>> b = True
[*]>>> id(b)
[*]1041552
[*]>>> bb = b
[*]>>> id(bb)
[*]1041552
[*]>>> bb = True
[*]>>> id(bb)
[*]1041552

复制代码
布尔对象名称的大小写是至关重要的,因为 true(和 false)是未定义的:

清单 6. 未定义的 true 和 false
[*]>>> b = true
[*]Traceback (most recent call last):
[*]File "<stdin>", line 1, in ?
[*]NameError: name 'true' is not defined

复制代码
在这一点上,bool 类型可能看起来不是很有用。不过顾名思义,布尔表达式是依赖于名称的,如下所示:

清单 7. 布尔表达式
[*]>>> b = 100 < 101
[*]>>> print b
[*]True

复制代码
很多程序利用布尔表达式,Python 提供一整套布尔比较和逻辑运算,详细信息请分别参见表 1 和表 2。

表 1. Python 中的布尔比较运算符


运算符描述示例
<小于i < 100
<=小于等于i <= 100
>大于i > 100
>=大于等于i >= 100
==相等i == 100
!=不相等(另外使用 <>)i != 100
补充一点,表 1 中列出的运算符优先级都一样,除非将表达式置于括号中,否则按从左到右的顺序应用。

表 2. Python 中的逻辑运算符


运算符描述示例
not逻辑非not b
and逻辑与(i <= 100) and (b == True)
or逻辑或(i < 100) or (f > 100.1)
逻辑运算符的优先级低于单独的比较运算符,这一点意义重大,因为必须先计算比较运算符,然后才能计算逻辑运算符。逻辑运算符的实际优先级就是表 2 中罗列这些运算符的顺序。


在 Python 中,关于 or 和 and 逻辑运算符有意思的是,它们都是快捷运算符。简言之,如果给定表达式 x or y,则仅当 x 为 False 时才会计算 y。同样地,如果给定表达式 x and y,则仅当 x 为 True 时,才会计算 y。此功能可以增强表达式求值的性能(尤其是针对长的或复杂的表达式),然而对于习惯于从其他语言学来的不同规则的程序员而言,则容易犯错。

数值类型

Python 中其他四个简单的内置类型都是数值类型:int、long、float 和 complex。在程序中,数值类型很常见,不管使用的是什么语言。Python 对算术运算提供完整支持,包括加法、减法、乘法和除法(参见表 3)。

表 3. Python 中的算术运算


运算符描述示例
*乘i * 100
/除i / 100
//整除i // 100
%取余f % 100
+加i + 100
-减i - 100

乘法和除法运算符(表 3 中列出的前四个)具有高于加法和减法的优先级。如前所述,您可以通过使用括号分组子表达式,将其分离出来以提高优先级。

Python 与 Java 语言不同,Java 语言通常定义允许的数值类型的范围,而 Python 在这一点上更像 C,因为它的类型范围是依赖于平台的。您可以使用 int 和 long 两种类型来保存整数值,它们的不同点在于 int 是一种 32 位的整数值。因而,它被限制为只能保存从 -232 到 232 - 1 之间的值(在多数平台上)。与此相反,长整数类型的精度不受限,仅计算机内存对它有影响。要通知 Python 应该按照长类型处理整数,只需将 L 附加到数字的末尾,如 100L。在 Python 中,浮点值始终是按双精度处理的;因此 Python 的 float 类型对应于 C 类语言中的双精度。

与数值类型相关的其他两个重点是常量(如上例中的 100,只是明确表达的数字)和位运算。程序员一般在十进制系统(以 10 为基数)中工作。但是,有时其他系统也相当有用,尤其是我们知道计算机是基于二进制的。Python 可以提供对八进制(以 8 为基数)和十六进制(以 16 为基数)数字的支持。要通知 Python 应该按八进制数字常量处理数字,只需将零附加在前面。将一个零加上一个 x 附加在数字的前面是告诉 Python 按十六进制数值常量处理数字,如以下代码所示:

清单 8. 通知 Python 按十六进制数值常量处理数字

当您具有容易的方式来表达数值常量时,尤其是十六进制,就可以容易地构建对应于特定测试用例的标志,这是一种常见的编程技术。例如,一个 32 位的整数可以存储 32 个标志值。使用位测试,可以容易地测试标志变量上的特定标志。Python 中位运算的完整列表如表 4 所示。

表 4. Python 中的位运算


运算符描述示例
~按位求补~b
<<向左位移b << 1
>>向右位移b >> 1
&按位和b & 0x01
^按位异或b ^ 0x01
|按位或b | 0x01
至此,您可能想知道不同数值类型在单个表达式中混合出现的时候怎么办。简单的答复是,Python 会根据需要将表达式中的所有操作数转换为最复杂的操作数的类型。复杂度的顺序是:int、long、float 和 complex(非双关),下面是一个简单的示例:

清单 9. Python 将所有操作数转换为最复杂的操作数

尽管 Python 会与您预期的一样转换操作数,但是语言并不基于运算符转换操作数,如 1/3 示例中所示,其计算结果为整数。如果要强制取得浮点结果,则必须确保操作数中至少有一个为浮点类型。

complex 类型

最后一种类型 complex 可能是大多数程序员难以识别的,因为它不是其他编程语言中常见的内置数据类型。而对于工程师和科学家来说,复数却是个司空见惯的概念。从形式上讲,复数 具有实部和虚部两个部分,都由 Python 中的 float 类型来表示。虚数 是 -1 的平方根的倍数,用 i 或 j 表示 —— 取决于您被培养为科学家还是工程师。在 Python 中,复数的虚部被表示为 j:

清单 10. 复数的虚部
[*]>>> c = 3.0 + 1.2j
[*]>>> print c
[*](3+1.2j)
[*]>>> print c.real, c.imag
[*]3.0 1.2

复制代码
本例是一个实部为 3.0 和虚部为 1.2 的复数。注意,通过使用复杂对象的 real 和 imag 属性,即可访问复数的不同部分。

它们真是对象吗?

到此为止,我已经介绍了 Python 只处理对象类型,然而示例中好像并没有什么对象。最后还有一个问题,构造函数在哪里?对于简单的内置数据类型,Python 替您做了大量的工作。不过,构造函数还在那里(其名称与相关数据类型的名称相同),如果您愿意,可以直接使用它们,如下所示:

清单 11. Python 构造函数
[*]>>> b = bool(True)
[*]>>> i = int(100)
[*]>>> l = long(100)
[*]>>> f = float(100.1)
[*]>>> c = complex(3.0, 1.2)
[*]>>> print b, i, l, f, c
[*]True 100 100 100.1 (3+1.2j)

复制代码












winston 发表于 2012-6-9 21:06:15

第 2 部分: 探索 Python 类型的层次结构 —— 了解对象和容器

Python 编程语言是一种简单但功能强大的语言。本文将探索该语言的对象属性,开头部分介绍了一些简单的内置数据类型。此外,本文还介绍了 Python 元组类,并用它演示容器类型的概念。

在 Python 语言中,所有事物都是程序可以访问的对象, 其中包括保存整数的简单类型,以及您编写的实际代码和这些代码在 Python 解释器中的表示。对于熟悉其他编程语言的人来说,此行为可能导致某些混乱。但是,在实践中,不会出现这种情况。Python 有一个良好定义的类型(或对象)层次结构。该层次结构在概念上可以划分为四种类别:简单类型、容器类型、代码类型 和内部类型。

简单类型

内置到 Python 编程语言中的简单数据类型包括:
    * bool
    * int
    * float
    * complex

支持简单数据类型不是 Python 独有的功能,因为多数现代编程语言都具有完整类型补充。例如 Java™ 语言甚至有一组更丰富的原始数据类型:
    * byte
    * short
    * int
    * long
    * float
    * double
    * char
    * boolean

但是,在 Python 中,简单数据类型并不是原始数据类型,而是完善的对象,它们有自已的方法和类。另外,这些简单的内置类型是不可改变的,这意味着:创建对象之后,您无法更改对象的值。如果需要新值,则必须创建新的对象。Python 简单数据类型的不可改变特性与其他多数流行语言(如 Java 语言)处理简单原始类型的方式不同。但是,当您对这些简单数据类型的对象属性有了更多的了解之后,就很容易理解这种差异。

所以,整数如何能够拥有一些方法?它仅仅是一个数字吗?不是的,至少在 Python 中答案是否定的。您自已可以对它进行检验:仅借助内置的 help 方法,就可以向 Python 解释器咨询关于 int 对象的信息(参见清单 1 )。

清单 1. Python 解释器: 用于整数对象的 Help
[*]rb% python
[*]Python 2.4 (#1, Mar 29 2005, 12:05:39)
[*] on darwin
[*]Type "help", "copyright", "credits" or "license" for more information.
[*]>>> help(int)
[*]Help on class int in module __builtin__:
[*]class int(object)
[*]|int(x[, base]) -> integer
[*]|
[*]|Convert a string or number to an integer, if possible.A floating point
[*]|argument will be truncated towards zero (this does not include a string
[*]|representation of a floating point number!)When converting a string, use
[*]|the optional base.It is an error to supply a base when converting a
[*]|non-string. If the argument is outside the integer range a long object
[*]|will be returned instead.
[*]|
[*]|Methods defined here:
[*]|
[*]|__abs__(...)
[*]|      x.__abs__() <==> abs(x)
[*]|
[*]|__add__(...)
[*]|      x.__add__(y) <==> x+y
[*]...

复制代码
这具体说明了什么?只有一个事情,那就是可以方便地从 Python 解释器中得到帮助,但是从后面部分可以获得更多帮助。第一行告诉您正在查看 int 类的帮助页面,它是一个内置的数据类型。如果您对面向对象的编程的概念不太熟悉,那么可以将类 想像成只是一个用于构建特殊事物并与之交互的蓝图。好比房子的设计蓝图,不仅显示如何构建房子,还显示房子完工之后,如何更好地使用房子。例如,设计图会显示不同房间的位置、在房间之间的移动方式以及出入房子的通道情况。

第一行的下面是对实际 int 类的详细说明。在这一点上,您可能不熟悉如何在 Python 中创建类,因为显示的语法类似于外语。没关系,我将在另一篇文章中对此进行全面介绍。现在,您只需要知道:int 对象是从 object 类中继承而来,它是 Python 中许多内容的一个基类。

后面的几行介绍 int 类的构造函数。构造函数 只是创建特定类实例(或对象) 的特殊方法。构造函数方法好比建筑承包人,它利用房子的设计图建房子。在 Python 中,构造函数的名称与其创建的类的名称相同。类可以有不同的构造函数方法,这些方法是通过类名称后的圆括号内附带的不同属性进行区分。类可以有不同构造函数方法的较好的一个例子就是 int 类, 实际上,您可以用多种方法调用它,具体采用哪种方法取决于圆括号中放置的参数(参见清单 2)。

清单 2. Python 解释器:int 类构造函数
[*]>>> int()
[*]0
[*]>>> int(100)          # Create an integer with the value of 100
[*]>>> int("100", 10)    # Create an integer with the value of 100 in base 10
[*]100
[*]100
[*]>>> int("100", 8)   # Create an integer with the value of 100 in base 8
[*]64

复制代码
这四个构造函数调用创建了四个不同的整数。第一个构造函数创建了一个整数对象,其值为 0,在没有值提供给 int 类构造函数的情况下,该值是所使用的默认值。第二个构造函数根据规定创建了一个值为 100 的整数。第三个构造函数采用了字符串“100”并创建了以 10 为基数的整数值(常见的十进制系统)。最后一个构造函数也采用了字符串“100”—— 但是它使用基数 8 来创建整数值,通常称为 八进制。不过,该值在输出时会被转换成十进制数值,这就是该数字显示为 64 的原因。

您可能想知道如果省略了构造函数调用中的圆括号将会发生什么。在这种情况下,您可以向该变量分配一个实际的类名称,有效地为原先的类创建一个别名(参见清单 3)。

清单 3. Python 解释器:int 类型
[*]>>> it = int         #Create an alias to the integer class
[*]>>> it(100)
[*]100
[*]>>> type(it)         #We created a new type
[*]<type 'type'>
[*]>>> type(it(100))    #Our new type just makes integers
[*]<type 'int'>

复制代码
真是太棒了!您立即可以创建一个由内置 int 类定义的新数据类型。但请注意不好的一面,不要滥用这一新功能。优秀的程序员除了使代码具有良好性能外,还应努力使代码清淅。这类编码技巧的确有其使用价值,但它们并不常见。

使用 Python 解释器可以使新的 Python 程序员简化学习过程,少走弯路。如果您想详细了解 Python 内的 help 工具,只需在 Python 解释器中的命令提示符下键入 help() ,就可以访问交互式的帮助工具(参见清单 4)。

清单 4. Python 解释器:帮助解释器
[*]>>> help()
[*]Welcome to Python 2.4!This is the online help utility.
[*]If this is your first time using Python, you should definitely check out
[*]the tutorial on the Internet at http://www.python.org/doc/tut/.
[*]Enter the name of any module, keyword, or topic to get help on writing
[*]Python programs and using Python modules.To quit this help utility and
[*]return to the interpreter, just type "quit".
[*]To get a list of available modules, keywords, or topics, type "modules",
[*]"keywords", or "topics".Each module also comes with a one-line summary
[*]of what it does; to list the modules whose summaries contain a given word
[*]such as "spam", type "modules spam".
[*]help>

复制代码
您可能已经对此有所了解,但在 help> 提示符处输入 int 可以显示那些为以前的 int 类显示的类描述。

容器类型

到目前为止,已经谈论了许多 Python 语言中使用的简单类型。但是多数程序并不简单,它们涉及通常由简单类型组成的复杂数据。因此,现在的问题就成了“如何在 Python 中处理复杂数据?”

如果您熟悉面向对象的语言,如 Java 或 C#,那么您可能认为该问题的答案很简单:只需创建一个新类来处理复杂的数据即可。该方法也适用于 Python,原因是 Python 支持通过类创建新类型。但是,在多数情况下,Python 还可以提供更为简单的方法。当您的程序需要一次处理多个对象时,就可以利用 Python 容器类:
    * tuple
    * string
    * unicode
    * list
    * set
    * frozenset
    * dictionary

这些容器类型提供了两种功能。前六个类型是有序的,最后一个类型 dictionary 则是一个映射。有序类型与映射类型的区别较为简单。有序类型 仅仅是指对象的顺序。所有的有序类型(除 set 和 frozenset 类型外)都支持访问给定顺序的对象。相比之下,映射容器 则用于保存那些对顺序不是很敏感的对象;通过提供可以找到关系值的密钥,就可以从容器中提取值。

容器类型间的另一个不同点来自于它们所持有的数据的特性,下面四种容器类型的顺序是不可变的:
    * tuple
    * string
    * unicode
    * frozenset

这意味着在您创建了这些容器类型之一后,所存储的数据就不可更改。如果出于某种原因需要更改数据,则需要创建一个新容器来保存新的数据。

后三种容器类型(list、set 和 dictionary)都是可变容器,因此,它们可以根据需要更改保存的任何数据(但在 dictionary 中所使用的密钥是不可变的,就像您房间的钥匙)。虽然可变容器非常灵活,但它们的动态特性会对性能造成影响。例如,tuple 类型,尽管它是不可变的,灵活性较差,但在同一环境中使用时,它们通常比 list 类型快得多。

这些容器类提供了强大的功能,它们通常是多数 Python 程序的核心。本文的其余部分讨论了 tuple 类型,它用于引入许多与创建和使用 Python 中的容器类型有关的基本概念。其余的类型将在以后的文章中讨论。

元组

tuple 类型像一个口袋,在出门前可以把所需的任何东西一股脑地放在里面。您可以将钥匙、驾驶证、便笺簿和钢笔放在口袋里,您的口袋是存放各种东西的收集箱。Python 的 tuple 类型与口袋类似,它可以存放不同类型的对象。您只需向变量分配一个用逗号分隔的对象序列,就可以创建一个 tuple(参见清单 5)。

清单 5. Python 解释器:创建一个 tuple
[*]>>> t = (0,1,2,3,4,5,6,7,8,9)
[*]>>> type(t)
[*]<type 'tuple'>
[*]>>> t
[*](0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
[*]>>> tt = 0,1,2,3,4,5,6,7,8,9
[*]>>> type(tt)
[*]<type 'tuple'>
[*]>>> tt
[*](0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
[*]>>> tc=tuple((0,1,2,3,4,5,6,7,8,9))
[*]>>> tc
[*](0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
[*]>>> et = ()   # An empty tuple
[*]>>> et
[*]()
[*]>>> st = (1,)   # A single item tuple
[*]>>> st
[*](1,)

复制代码
该示例代码显示了如何以多种方式创建 tuple。第一种方法是创建一个包含从 0 到 9 整数序列的 tuple。第二种方法与第一种相同,但这次省去了括号。在创建一个 tuple 时,括号通常是可选的,但有时是必需的,这取决于上下文。结果,您会习惯性地使用括号来减少混淆。最后一个 tuple tc 使用了一个实际的类构造函数来创建 tuple。这里重要的一点是,构造函数构成中仅有一个变量,因此您必须在括号中包括对象序列。最后两个构造函数调用演示了如何通过在括号内不放任何东西来创建空的 tuple (et),以及如何通过将一个逗号放在序列中仅有的一个项目后面来创建 tuple (st)。

使用口袋装东西的一个主要原因是为了方便生活。但要求在需要这些东西的时候能够迅速地从口袋中取出它们。Python 中的多数容器类型(其中包括 tuple)允许您使用方括号操作符从集合中方便地访问数据项。但 Python 比其他语言更具灵活性:您可以使用通常称为分段 的方法选择一个项目或多个有序项目(参见清单 6)。

清单 6. Python 解释器:从 tuple 访问项目
[*]>>> t = (0,1,2,3,4,5,6,7,8,9)
[*]>>> t
[*]2
[*]>>> type(t)
[*]<type 'int'>
[*]>>> t, t, t
[*](0, 1, 9)
[*]>>> t            # Slice out five elements from the tuple
[*](2, 3, 4, 5, 6)
[*]>>> type(t)
[*]<type 'tuple'>
[*]>>> t          # Slice out three elements from the tuple
[*](2, 4, 6)

复制代码
在创建简单的 tuple 之后,前面的示例显示如何选择一个数据项 —— 在本示例中是整数 2。这时,请注意 Python 使用了零排序,其中集合中的项目从零开始编号。如果您熟悉使用 Java 语言、C# 或其他从 C 语言派生的语言进行编程,那么您应该非常熟悉此行为。否则,该概念也是非常简单的。用于访问数据项的索引只声明集合中越过第一个数据项有多远,或者称为序列,您需要去获得所需的内容。因此,要获得第三个数据项(在本示例中为整数 2),您需要从第一个数据项起越过两个数据项。在访问第三个数据项时,Python 知道它是一个整数对象。您还可以方便地从集合中提取多个数据项。在本示例中,您创建了一个新的 tuple,其值为从最初的 tuple 开始第一、第二和第十个值。

其余的示例显示了如何使用 Python 的分段功能从序列中一次选择多个数据项。术语分段 是指从序列中对数据项进行分段的方法。分段的工作方式是声明开始索引、结束索引和一个可选的步骤大小,全部都用分号分隔。因此,t 将 tuple 中的第三到第七个数据项分段,而 t 则对每两个数据项进行分段,从 tuple 中的第三个数据项开始一直到第七个数据项。

我目前创建的 tuple 对象是同类的,它们仅包含整数对象。所幸的是,tuple 要比显示的示例复杂得多,因为 tuple 实际上是一个异构容器(参见清单 7)。

清单 7. Python 解释器:异构的 tuple
[*]>>> t = (0,1,"two",3.0, "four", (5, 6))
[*]>>> t
[*](0, 1, 'two', 3.0, 'four', (5, 6))
[*]>>> t
[*](1, 'two', 3.0)
[*]>>> type(t)
[*]<type 'str'>
[*]>>> type(t)
[*]<type 'float'>
[*]>>> type(t)
[*]<type 'tuple'>
[*]>>> t = (0,1)
[*]Traceback (most recent call last):
[*]File "<stdin>", line 1, in ?
[*]TypeError: object does not support item assignment

复制代码
您会看到,创建可以拥有各种类型数据项(其中包括另一 tuple)的 tuple 是多么方便。并且可以使用方括号操作符以相同的方式访问所有数据项,它支持将不同类型的有序数据项分段。然而,tuple 是不可变的。因此,当我尝试更改第五个元素时,发现不允许对数据项分配。打一个简单的比方,在您将某些东西放入口袋后,改变所取东西的惟一方式是取一个新口袋,并将所有数据项放进去。

如果需要在现有 tuple 中创建一个包含数据项子集的新 tuple,最简单的方法是使用相关的片段,并根据需要同时添加子集(参见清单 8)。

清单 8. Python 解释器:使用 tuple
[*]>>> tn = t + t# Add two tuples
[*]>>> tn
[*](1, 'two', 3.0, 'four', (5, 6))
[*]>>> tn = t + t + (7,8,9,"ten")
[*]>>> tn
[*](1, 'two', 3.0, 'four', (5, 6), 7, 8, 9, 'ten')
[*]>>> t2 = tn[:]            # Duplicate an entire tuple, a full slice
[*]>>> t2
[*](1, 'two', 3.0, 'four', (5, 6), 7, 8, 9, 'ten')
[*]>>> len(tn)               # Find out how many items are in the tuple
[*]9
[*]>>> tn            # Access a nested tuple
[*]5

复制代码
您还可以将现有 tuple 的片段与新 tuple 的片段合并在一起。使用片段语法,无需指定开始或结束索引,就可以制作现有 tuple 的副本。最后两个示例也非常有趣。内置的 len 方法告诉您 tuple 中数据项的数量。从嵌套的 tuple 访问数据项也非常简单:选择嵌套的 tuple,然后从中访问有趣的数据项。

您还可以从称为打包 的过程的一组现有变量中创建一个tuple。反之亦然,其中,tuple 中的值被指派给变量。这之后的过程称为解包,它是用于许多情形的功能十分强大的技术,其中包括希望从一个函数中返回多个值。在解包 tuple 时,仅有的问题是必须为 tuple 中的每个数据项提供一个变量(参见清单 9)。

清单 9. Python 解释器:打包和解包 tuple
[*]>>> i = 1
[*]>>> s = "two"
[*]>>> f = 3.0
[*]>>> t = (i, s, f)         # Pack the variables into a tuple
[*]>>> t
[*](1, 'two', 3.0)
[*]>>> ii, ss, ff = t      # Unpack the tuple into the named variables
[*]>>> ii
[*]1
[*]>>> ii, ff = t            # Not enough variables to unpack three element tuple
[*]Traceback (most recent call last):
[*]File "<stdin>", line 1, in ?
[*]ValueError: too many values to unpack

复制代码
简化概念

尽管看上去十分复杂,但 Python 的对象属性实际上简化了 Python 语言新手常常面临的一些更为复杂的概念。在了解如何使用对象之后,所有东西都是对象这一概念意味着您已经进一步理解了一些新概念。如 Python 的容器类型。使困难的任务变得简单化是使用 Python 得到的常见好处之一;另一个例子是内置的帮助工具,只需在 Python 提示符处输入 help(),就可以在 Python 解释器中看到该工具。由于生活不是用一些简单的概念描述的,所以 Python 提供了一组丰富的容器(即集合)对象。在本文中,我介绍了其中的最简单的对象 —— tuple。要正确使用 tuple,就需要熟悉它的工作方式。但是,由于许多其他容器类型具有类似的功能,其中包括分段以及打包或解包,了解 tuple 的工作原理意味着您已经开始完全理解 Python 中的其他容器类型。




winston 发表于 2012-6-9 21:06:33

第 3 部分: 探索 Python 类型的层次结构 —— 使用字符串

字符串

在 Python 中创建字符串对象非常容易。只要将所需的文本放入一对引号中,就完成了一个新字符串的创建(参见清单 1)。如果稍加思考的话,您可能会感到有些困惑。毕竟,有两类可以使用的引号:单引号 (') 和双引号 (")。幸运的是,Python 再一次使这种问题迎刃而解。您可以使用任意一类引号来表示 Python 中的字符串,只要引号一致就行。如果字符串是以单引号开始,那么必须以单引号结束,反之亦然。如果不遵循这一规则,则会出现 SyntaxError 异常。

清单 1. 在 Python 中创建字符串
[*]>>> sr="Discover Python"
[*]>>> type(sr)
[*]<type 'str'>
[*]>>> sr='Discover Python'
[*]>>> type(sr)
[*]<type 'str'>
[*]>>> sr="Discover Python: It's Wonderful!"
[*]>>> sr='Discover Python"
[*]File "<stdin>", line 1
[*]    sr='Discover Python"
[*]                        ^
[*]SyntaxError: EOL while scanning single-quoted string
[*]>>> sr="Discover Python: \
[*]... It's Wonderful!"
[*]>>> print sr
[*]Discover Python: It's Wonderful!

复制代码
从清单 1 中可以看出,除了字符串用适当的引号括起来之外,另外还有两个重要方面。第一,在创建字符串时,您可以混合使用单引号和双引号,只要字符串在开始位置和结束位置使用同一类型的引号。这种灵活性允许 Python 容易地保留常规的文本数据,这些常规的文本数据可能需要使用单引号来表示简写的动词形式或所属关系,以及使用双引号来表示引述文本。

第二,如果字符串用一行表示太长,您可以使用 Python 连续字符:反斜线 (\) 来对字符串进行折行。从内部机制看,在创建字符串时换行符会被忽略,在打印字符串时可以看出这一点。您可以结合使用这两个功能,来创建包含较长段落的字符串,如清单 2 所示。

清单 2. 创建长字符串
[*]>>> passage = 'When using the Python programming language, one must proceed \
[*]... with caution. This is because Python is so easy to use and can be so \
[*]... much fun. Failure to follow this warning may lead to shouts of \
[*]... "WooHoo" or "Yowza".'
[*]>>> print passage
[*]When using the Python programming language, one must proceed with caution.
[*]This is because Python is so easy to use, and can be so much fun.
[*]Failure to follow this warning may lead to shouts of "WooHoo" or "Yowza".

复制代码
编者注:上面的示例已折行处理,这样使页面布局更合理。事实上,它本来显示为一个较长的行。

注意,当打印 passage 字符串时,所有格式将被删除,只保留一个非常 长的字符串。通常,您可以使用控制符来表示字符串中的简单格式。例如,要表示一个新行开始,您可以使用换行控制符 (\n);要表示插入一个制表符(预设空格数),可以使用制表符控制符 (\t),如清单 3 所示。

清单 3. 在字符串中使用控制符
[*]>>> passage='\tWhen using the Python programming language, one must proceed\n\
[*]... \twith caution. This is because Python is so easy to use, and\n\
[*]... \tcan be so much fun. Failure to follow this warning may lead\n\
[*]... \tto shouts of "WooHoo" or "Yowza".'
[*]>>> print passage
[*]      When using the Python programming language, one must proceed
[*]      with caution. This is because Python is so easy to use, and
[*]      can be so much fun. Failure to follow this warning may lead
[*]      to shouts of "WooHoo" or "Yowza".
[*]>>> passage=r'\tWhen using the Python programming language, one must proceed\n\
[*]... \twith caution. This is because Python is so easy to use, and\n\
[*]... \tcan be so much fun. Failure to follow this warning may lead\n\
[*]... \tto shouts of "WooHoo" or "Yowza".'
[*]>>> print passage
[*]\tWhen using the Python programming language, one must proceed\n\
[*]\twith caution. This is because Python is so easy to use, and\n\
[*]\tcan be so much fun. Failure to follow this warning may lead\n\
[*]\tto shouts of "WooHoo" or "Yowza".

复制代码
清单 3 中的第一段按照您预期的方式使用了控制符。该段已具备良好的格式,阅读非常方便。第二个示例虽然也进行了格式化处理,但它引用的是所谓的原始字符串,即没有应用控制符的字符串。您始终可以认出原始字符串,因为该字符串的起始引号的前面有一个 r 字符,它是 raw 的缩写。

我不了解您讲的有什么可取之处,虽然这种方法可行,但创建一个段落字符串似乎非常因难。当然一定有更好的方法。与往常一样,Python 提供了一种非常简单的方法用于创建长字符串,该方法可保留创建字符串时所使用的格式。这种方法是使用三个双引号(或三个单引号)来开始和结束长字符串。在该字符串中,您可以使用任意多的单引号和双引号(参见清单 4)。

清单 4. 使用三个引号的字符串
[*]>>> passage = """
[*]...         When using the Python programming language, one must proceed
[*]...         with caution. This is because Python is so easy to use, and
[*]...         can be so much fun. Failure to follow this warning may lead
[*]...         to shouts of "WooHoo" or "Yowza".
[*]... """
[*]>>> print passage
[*]
[*]      When using the Python programming language, one must proceed
[*]      with caution. This is because Python is so easy to use, and
[*]      can be so much fun. Failure to follow this warning may lead
[*]      to shouts of "WooHoo" or "Yowza".

复制代码
将字符串作为一个对象

如果阅读了本系列前两篇文章中的任何一篇文章,那么在您的脑海中会立即浮现出这样一句话:在 Python 中,所有事物都是对象。到目前为止,我还没有涉及到关于 Python 中的字符串的对象特性的问题,但是,与往常一样,Python 中的字符串就是对象。事实上,字符串对象是 str 类的一个实例。正如您在 探索 Python,第 2 部分 中看到的,Python 解释器包括一个内置帮助工具(如清单 5 所示),它可以提供关于 str 类的信息。

清单 5. 获取关于字符串的帮助信息
[*]>>> help(str)
[*]
[*]Help on class str in module __builtin__:
[*]
[*]class str(basestring)
[*]|str(object) -> string
[*]|
[*]|Return a nice string representation of the object.
[*]|If the argument is a string, the return value is the same object.
[*]|
[*]|Method resolution order:
[*]|      str
[*]|      basestring
[*]|      object
[*]|
[*]|Methods defined here:
[*]|
[*]|__add__(...)
[*]|      x.__add__(y) <==> x+y
[*]|
[*]...

复制代码
使用单引号、双引号和三引号语法创建的字符串仍然是字符串对象。但是您也可以使用 str 类构造函数显式地创建字符串对象,如清单 6 所示。该构造函数可以接受简单的内置数值类型或字符数据作为参数。两种方法都可以将输入的内容更改为新的字符串对象。

清单 6. 创建字符串
[*]>>> str("Discover python")
[*]'Discover python'
[*]>>> str(12345)
[*]'12345'
[*]>>> str(123.45)
[*]'123.45'
[*]>>> "Wow," + " that " + "was awesome."
[*]'Wow, that was awesome.'
[*]>>> "Wow,"" that ""was Awesome"
[*]'Wow, that was Awesome'
[*]>>> "Wow! "*5
[*]'Wow! Wow! Wow! Wow! Wow! '
[*]>>>sr = str("Hello ")
[*]>>>id(sr)
[*]5560608
[*]>>>sr += "World"
[*]>>>sr
[*]'Hello World'
[*]>>>id(sr)
[*]3708752

复制代码
清单 6 中的例子也展示了关于 Python 字符串的几个其他重要方面。第一,通过将其他字符串添加在一起,可以创建新的字符串,具体方法可以使用 + 运算符,或者干脆使用适当的引号将字符串连在一起。第二,如果需要重复短字符串来创建长字符串,可以使用 * 运算符,将字符串重复一定的次数。我在本文开头说过,在 Python 中,字符串是不变的字符序列, 上例中的最后几行说明了这一点,我首先创建一个字符串,然后通过添加其他字符串对它进行修改。从对 id 方法两次调用的输出中可以看出,创建的新字符串对象中保存的是向原字符串中添加文本的结果。

str 类包含大量的用于操作字符串的有用方法。这里不做一一介绍,您可以使用帮助解释器获得有关信息。现在让我们了解一下四个有用的函数,并演示其他 str 类方法的工具。清单 7 演示了 upper、lower、split 和 join 方法。

清单 7. 字符串方法
[*]>>> sr = "Discover Python!"
[*]>>> sr.upper()
[*]'DISCOVER PYTHON!'
[*]>>> sr.lower()
[*]'discover python!'
[*]>>> sr = "This is a test!"
[*]>>> sr.split()
[*]['This', 'is', 'a', 'test!']
[*]>>> sr = '0:1:2:3:4:5:6:7:8:9'
[*]>>> sr.split(':')
[*]['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
[*]>>> sr=":"
[*]>>> tp = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
[*]>>> sr.join(tp)
[*]'0:1:2:3:4:5:6:7:8:9'

复制代码
前两个方法 upper 和 lower 很容易理解。它们只是分别将字符串都转换成大写字母或小写字母。split 方法很有用,因为它可以将一个字符串分成几个较小的字符串序列,方法是将令牌字符(或给定字符序列中的任何字符)用作断开位置的指示器。所以,第一个 split 方法示例使用默认的令牌将字符串“This is a test”拆分开,此令牌可以是任何空白字符(这个序列包括空格、制表符和换行符)。第二个 split 方法演示如何使用不同的令牌字符(本例中使用的是冒号)将一个字符串分成一系列字符串。最后的一个例子显示如何使用 join 方法,该方法的作用与 split 方法相反, 可以使多个短字符串序列形成一个长字符串。在本例中,使用冒号将 tuple 包含的由单个字符构成的字符串序列连接在一起。

将字符串用作字符的容器

在本文的开头部分,我着重强调了 Python 中的字符串是不变的字符序列。本系列的第 2 部分 探索 Python,第 2 部分 介绍了 tuple,它也是一个不变的序列。tuple 通过以下方式支持访问序列中的元素:使用索引符号,使用片段分离序列中的元素,以及使用特定的片段或将不同的片段添加在一起来创建新的元组。根据这一情况,您可能想知道是否可以将同一技巧应用于 Python 字符串。如清单 8 所示,答案显然是“可以”。

清单 8. 字符串方法
[*]>>> sr="0123456789"
[*]>>> sr
[*]'0'
[*]>>> sr + sr
[*]'10'
[*]>>> sr   # Give me elements four through seven, inclusive
[*]'4567'
[*]>>> sr[:-1]   # Give me all elements but the last one
[*]'012345678'
[*]>>> sr    # Slice more than you can chew, no problem
[*]'123456789'
[*]>>> sr[:-20]    # Go before the start?
[*]''
[*]>>> sr   # Go past the end?
[*]''
[*]>>> sr + sr + sr + sr
[*]'0123456789'
[*]>>> sr
[*]Traceback (most recent call last):
[*]File "<stdin>", line 1, in ?
[*]IndexError: string index out of range
[*]>>> len(sr)   # Sequences have common methods, like get my length
[*]10

复制代码
在 Python 中,将字符串作为字符序列进行处理是非常简单的。您可以获得单个元素,将不同的元素添加在一起,切出几个元素,甚至将不同的片段添加在一起。进行切片的一个非常有用的特性是,在开始之前或结束之后进行较多切片不会抛出异常,只是相应地以默认方式开始或结束该序列。相反,如果您试图使用允许范围之外的索引来访问单个元素,则会得到一个异常。这种行为说明了为什么 len 方法是如此重要。

字符串:功能强大的工具

在本文中,我介绍了 Python 字符串,它是一种不变的字符序列。在 Python 中,您可以使用多个方法很容易地创建字符串,其中包括使用单引号、双引号或更灵活的方式,即使用一组三个引号。假设 Python 中的每个事物都是一个对象,您可以使用底层的 str 类方法来获得附加功能或直接使用字符串的序列功能。

winston 发表于 2012-6-9 21:06:50

第 4 部分: 探索 Python 类型的层次结构 —— 使用列表

Python 提供了一系列有用的功能,其中 list 类是最重要的功能之一。本文介绍 list 类,并演示了众多方法中的一些方法,了解如何使用这些方法简化困难的编程任务。

Python list

在介绍 Python tuple 时,我使用了类比的方法,将其比做一个袋子,您可以在袋子中存放不同的东西。Python list 与此非常类似,因此,它的功能与袋子的功能也非常类似。但有一点是不同的,即您可以使用方括号创建 list,如清单 1 所示。

清单 1. 在 Python 中创建一个 list
[*]>>> l =
[*]>>> l
[*]
[*]>>> type(l)
[*]<type 'list'>
[*]>>> el = []    # Create an empty list
[*]>>> len(el)
[*]0
[*]>>> sl =     # Create a single item list
[*]>>> len(sl)
[*]1
[*]>>> sl =     # Create
[*]a single item list, as with a tuple
[*]>>> len(sl)
[*]1

复制代码
本例展示如何创建包含从 0 到 9(包括 0 和 9)的简单 list,以及如何创建一个空列表和一个包含单个条目的列表。如果您还记得的话,创建单个条目的 tuple 还需要在单个条目后面跟一个逗号。这是区分单个条目 tuple 与方法调用的必要条件,这一点将在以后的文章中详细讨论。而对于 list,则是不必要的,尽管也允许使用单个逗号。

与往常一样,要获取有关 Python 主题的更多信息,您可以使用内置的帮助解释器,例如,清单 2 展示了如何开始 list 类的帮助描述。

清单 2. 获取有关 list 的帮助
[*]>>> help(list)
[*]Help on class list in module __builtin__:
[*]class list(object)
[*]|list() -> new list
[*]|list(sequence) -> new list initialized from sequence's items
[*]|
[*]|Methods defined here:
[*]|
[*]|__add__(...)
[*]|      x.__add__(y) <==> x+y
[*]|
[*]|__contains__(...)
[*]|      x.__contains__(y) <==> y in x
[*]|
[*]...

复制代码
如果仔细观察清单 2 中对 list 类的描述,您会看到其中提供了两个不同的构造函数:一个没有参数,另一个接受一个序列类作为参数。因此,使用构造函数及方括号简化符号,可以创建 list。这就提供了很大的灵活性,原因是您可以方便地将现有的序列,如 tuple 或 string 转换为 list,如清单 3 所示。不过,请注意,传递的参数必须是序列 —— 并且不只是对象序列 —— 否则将会出现错误。对于任何序列类型,您都可以使用 len 方法容易地查找序列中条目的数量。

清单 3. 直接创建 list 对象
[*]>>> l = list()
[*]>>> type(l)
[*]<type 'list'>
[*]>>> len(l)
[*]0
[*]>>> l
[*][]
[*]>>> l = list((0, 1, 2, 3, 4, 5, 6, 7,
[*]8, 9))    # Create a list from a tuple
[*]>>> l
[*]
[*]>>> len(l)
[*]10
[*]>>> l = list()    # Create a list from a list
[*]>>> l
[*]
[*]>>> len(l)
[*]10
[*]>>> l = list(0, 1, 2, 3, 4, 5, 6, 7,
[*]8, 9)      # Error: Must pass in a sequence
[*]Traceback (most recent call last):
[*]File "<stdin>", line 1, in ?
[*]TypeError: list() takes at most 1 argument (10 given)
[*]>>> l = list("0123456789") # Create a list from a string
[*]>>> l
[*]['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
[*]>>> type(l)
[*]<type 'list'>
[*]>>> len(l)
[*]10

复制代码
正如您看到的,创建 list 是很容易的,如果还没有尝试过,现在可以试一试。您不仅能够将序列直接传递给构造函数,还可以将拥有元组或字符串的变量传递给 list 构造函数。

很明显,序列较为有用的主要原因是它可以非常方便地访问序列中的条目。如果还记得对 tuple 的讨论,便知道可以在序列中一次访问一个条目或者通过将条目切片来访问条目。Python list 也可以使用相同的技术,如清单 4 所示。

清单 4. 从 list 访问条目
[*]>>> l = list()
[*]>>> l         # Get the first item in the list
[*]0
[*]>>> type(l)
[*]<type 'int'>
[*]>>> l         # Get the sixth item in the list
[*]5
[*]>>> l       # Get the second through fifth items
[*]
[*]>>> type(l)
[*]<type 'list'>
[*]>>> l      # Get every second item
[*]
[*]>>> l, l, l
[*](0, 1, 2)

复制代码
在以前的文章中已经了解到,切片 是一个非常有用的概念,其一般形式为 l,其中 start 和 end 分别是开始和结束索引,step 是在切片时要跨过的条目数量。此外,还可以对结束索引使用负值,即从序列的结尾往回计数。另一个有用的功能是以一种很合适的方式处理错误(如超过序列的长度)。如前一个例子所示,您还可以选择忽略切片中使用的三个值中的一个或多个值。例如,我在切片 l 中没有使用结束索引。

可变的序列

在本文的开头,我提到过 list 和 tuple 之间的主要区别在于 list 是一个可变的序列,这就意味着您不但可以方便地访问 list 中的条目,而且可以方便地修改它们。但这会引起一个并发症状:您只能修改序列中的条目。若要向序列中添加条目(而不仅仅是修改条目),可使用 append 方法,如清单 5 所示。

清单 5. 修改 list
[*]>>> l = []
[*]>>> l = 0      # The list is empty
[*]Traceback (most recent call last):
[*]File "<stdin>", line 1, in ?
[*]IndexError: list assignment index out of range
[*]>>> l.append(0)
[*]>>> l
[*]
[*]>>> l = 1
[*]>>> l
[*]

复制代码
正如前一个例子所演示的,尝试修改不存在的 list 条目会导致出现错误。这一点意义重大,并演示了 Python 方法生成错误的情况。当问题较为严重时,将会产生一个错误,如果问题较小并且可以很容易地处理,则忽略它。

异构的可变序列

您可能想了解更为复杂的修改。通过综合切片知识以及如何修改 list 的知识,您应该已经获得了非常重要的见识:可以通过多种方式修改列表。就像 tuple 一样,list 也可以持有不同类型的数据(或不同类型的对象),这就是我所说的异构的可变序列。这两种功能在清单 6 中进行了更完整的描述。

清单 6. 异构的可变 list
[*]>>> l=
[*]>>> l
[*]
[*]>>> l = 2
[*]>>> type(l)
[*]<type 'int'>
[*]>>> l = "two"      # Change the type of an element
[*]>>> ,ype(l)
[*]<type 'str'>
[*]>>> l
[*]
[*]>>> l = l * 2
[*]>>> l
[*], 3, 4, 5, 6, 7, 8, 9]
[*]>>> del(l)         # Remove single element
[*]>>> l
[*]
[*]>>> l = []       # Remove a slice
[*]>>> l
[*]

复制代码
修改 list 中的条目相当容易:您可以适当地设置条目的值,甚至设置成另一种不同的类型,如 string 或另一 list。您还可以使用重复运算符,可以将该运算符识别为乘法运算符,以便从小片段中构建更大的列表。

前面的例子向您展示了如何向 list 中添加元素,以及如何修改 list 中的条目。前一个例子还演示了如何从 list 中删除对象。删除条目的第一个方法是使用 del 方法。使用此方法可以删除一个条目或一个条目范围。您还可以使用灵活而强大的切片方法从 list 中删除切片。

数组

在前一个例子中您可以看到,list 可以包含另一个 list 作为条目。如果扩展此例子,您可能想知道每个条目由一个 list 替换将会发生什么样的事情。结果是一个数组,或者从更加数学方面来讲是一个矩阵。清单 7 展示了如何使用 list 保持二维 (2-D) 或三维 (3-D) 数组。

清单 7. list 作为一个数组
[*]>>> al = [, , ]
[*]>>> al
[*][, , ]
[*]>>> al          # First element in 2D array
[*]0
[*]>>> al          # Last element in 2D array
[*]8
[*]>>> al
[*]5
[*]>>> al = [[, ], [, ]]
[*]>>> al
[*][[, ], [, ]]
[*]>>> al
[*]1
[*]>>> len(al)         # Length of outer dimension
[*]2
[*]>>> len(al)      # Length of middle dimension
[*]2
[*]>>> len(al)   # Length of inner dimension
[*]2

复制代码
其他列表操作

list 对象具有许多可以应用于现有列表的有用方法。例如,您可以反转 list 中的所有条目或排序 list。不过,要记住这些操作的一个重点在于,它们是就地 操作,这意味着它们会修改调用它们所针对的 list。因此,如果您尝试创建新列表,并将其设置为对这些方法之一调用所产生的结果,则会得到一个空列表。

list 除可以用于模拟数组外,还可以用于模拟其他数据结构。例如,append 和 pop 方法对 list 函数的操作要么是先进先出 (FIFO) 数据结构(也称为队列),要么是后进先出 (LIFO) 数据结构(也称为堆栈)。通过允许您将条目设置为从 list 中弹出(删除并返回),pop 方法支持这些功能。如果弹出 list 的第一项,则是一个队列;反之,如果弹出 list 的最后一项,则是一个堆栈,如清单 8 所示。

清单 8. 操纵 list
[*]>>> l=
[*]>>> id(l) # This is the object id for our current list
[*]4525432
[*]>>> l.reverse()       # Reverse the list
[*]>>> l
[*]
[*]>>> id(l) # The id is the same, modified list in place.
[*]4525432
[*]>>> l.sort()          # Sort the list in numerical order
[*]>>> l
[*]
[*]>>> id(l) # Modified the existing list
[*]4525432
[*]>>> l.index(5)      # Same as l
[*]5
[*]>>> l.count(0)      # How
[*]many times does '0' occur in the list
[*]1
[*]>>> l.pop()         # Take off the last item (Stack)
[*]9
[*]>>> l
[*]
[*]>>> l.pop(5)          # Take out the fifth element
[*]5
[*]>>> l
[*]
[*]>>> l.pop(0)          # Take the first item off the list (Queue)
[*]0
[*]>>> l
[*]

复制代码


winston 发表于 2012-6-9 21:08:28

第 5 部分: 用 Python 编程 —— 控制流

本文开始讲述如何用 Python 编程,着重点是流控制,这是编写程序的最简方法之一。在该编程模型中,会操纵来自用户界面、传感器或文件等的数据,根据数据的值或产生的表达式不同,采取的操作也不同。Python 提供几种流控制机制。本文将讨论 if 语句、while 循环和 for 循环。

程序流

上面已经介绍了 Python 程序中常用的基本数据类型,包括:
    * 内置的数值数据类型
    * Tuple 容器类型
    * String 容器类型
    * List 容器类型

上文中也展示了一些简单的 Python 例子,这些例子管理保存有这四种类型的数据的变量。尽管我没有指出,但是我自然还是假设您像读一本书一样地读并解释代码。(至少英语中)自然的顺序是从页面或程序的顶端开始,然后从左往右读每一行。当达到行尾后,又是下一行的开始(或者叫做左端),依此类推,沿着页面(这里是指程序)往下走。

Python 解释器在其最简单的级别,以类似的方式操作,即从程序的顶端开始,然后一行一行地顺序执行程序语句。例如,清单 1 展示了几个简单的语句。当把它们键入 Python 解释器中(或者将它们保存在一个文件中,并作为一个 Python 程序来执行)时,读取语句的顺序是从左到右。 当读到一个行结束符(比如换行符)时,Python 解释器就前进到下一行并继续,直到没有了代码行。

清单 1. 一个简单的 Python 程序
[*]>>> i = 1
[*]>>> type(i)
[*]<type 'int'>
[*]>>> l =
[*]>>> l * i
[*]

复制代码
在本例中,语句以简单的顺序一个接一个。但是情况并不总是线性的。考虑一个个人的例子。您今天早上醒来,听了交通或天气报告(或者两者都听了)。根据交通报告,您可能选择了一条不同的上班路线;或者类似地,根据天气报告,您为周末计划了不同的活动。您的对策并不简单;根据您所获得的信息,生活的自然顺序迂回而曲折。

Python 像大多数编程语言一样,通过使用流控制语句,也可以以这种方式操作。在 Python 中,有 3 种基本的流控制语句:
    * if 语句,它基于测试表达式的结果执行一个特定的语句块。
    * while 循环,它当一个测试表达式为 true 时执行一个语句块。
    * for 循环,它对一个语句块执行一定次数。

这个列表相当简单,并且您可能从其他编程语言认识了这些流控制语句。但是您可能在想,语句块 是什么意思呢。在清单 1 中,您看到了几个简单的语句,包括一个变量初始化、一个方法调用(type 方法)和一个乘法操作。这些语句执行一个简单的操作,因此把它们叫做简单语句。

Python 也具有复合语句,即相关语句形成的语句组,其中包括简单和(可能)附加的复杂语句。例如,根据表达式的值(对个人来说,可能是对“今天的天气晴朗吗”之类问题的答案),一个复合语句可能执行不同的操作或者对一个操作重复多次。这一描述似乎有些类似于前一段的流控制描述。当然应该类似,因为流控制语句就是复合语句。

一个复合语句包括一个流控制指令,后跟一个冒号(:),然后再是一个程序语句块。语句块由一个或多个简单语句和复合语句组成。清单 2 中提供了一个简单的伪代码例子。

清单 2. 一个伪代码例子展示了简单语句和复杂语句
[*]simple statement one
[*]compound statement one:
[*]    simple statement two
[*]    simple statement three
[*]    compound statement two:
[*]      simple statement four
[*]simple statement five

复制代码
该语法看起来既熟悉又陌生,并且两种感觉来自相同的事情:缩进。在列大纲或步骤时,您可能会使用不同级别的缩进来分隔每一项,使得列出来的东西更加清晰可读。Python 遵循这一模型,使用缩进来分隔代码块与程序的其余部分。其他编程语言使用特殊的字符来区分代码块,比如基于 C 的语言中的花括号({ 和 })。这些其他语言也鼓励程序员使用缩进,以改善程序的可读性。

另一方面,Python 需要缩进以指示代码块。如果没有正确地缩进,Python 解释器会抛出异常。可以使用制表符来标记缩进,但是一般推荐使用空格。(为了一致性,我总是使用 4 个空格来缩进代码块。)理由很简单:空格字符只有一种解释方式。另一方面,制表符可以有不同的解释方式,根据所使用的平台或工具,可以解释为 2 个、4 个、6 个甚至 8 个空格。

增强程序可读性

缩进要求可能是 Python 的一个基本指导原则 —— Python 程序应该易于读和理解 —— 的最佳例子。但是这就跟工具一样,顽固分子也可能会编写晦涩的 Python 代码。例如,螺丝起子是用来起螺丝的,但是有时您也可能用来打开油漆盖子。

两个其他特性有助于编写易读的 Python 程序,并且这两者都遵循前面所用的书的比喻。首先,书中的行不会延伸到页面外面,都有固定的长度。其次,书中的行不是以特殊符号(比如分号)结束。这两个特性都贯穿于编写 Python 程序的过程中。

如果某个程序行太长,可以在文件中的下一物理行继续这一行。没有硬性规定一个代码行应该多长。但是一般限制为 80 个字符,这容易适合大多数显示器的一个打印页面。有几种方式来扩展超过一行的代码语句:

    * 三引号字符串可以扩展到多个行。
    * 括号中的表达式可以扩展到多个行。
    * 可以使用继续字符(\)来在多行分割语句。

在 Python 中,不需要使用特殊字符(或符号)来指示语句的结束。这与有些语言不同。例如,基于 C 的语言使用分号(;)来指示代码行的结束。然而,有时候需要在一行放多个程序语句,例如初始化变量时。在这样的情况下,可以使用分号来分隔单个语句。

清单 3 中演示了这两种技术。

清单 3. 演示 Python 的可读性技术
[*]>>> i1 = 10 ; i2 = 20 ; i3 = 30
[*]>>>
[*]>>> b = ((i1 < 20) and
[*]...      (i2 < 30) and
[*]...      (i3 < 40))
[*]>>> b
[*]True
[*]>>>
[*]>>> b = (i1 < 20) and \
[*]...   (i2 < 30) and \
[*]...   (i3 < 40)
[*]>>>
[*]>>> b
[*]True

复制代码
注意清单 3 中扩展到多个行的程序语句是如何缩进以改善可读性的。在本例中,缩进不是强制性的,就跟一个复合语句一样。但是正如您所见,缩进改善了程序的外观,因而强烈推荐进行缩进。

if 语句

最简单的流控制语句是 if 语句,它的基本语法在清单 4 中的伪代码中演示了。if 语句在一个布尔表达式计算为 True 时执行一个程序语句块。if 语句支持一个可选的 else 子句,指示当布尔表达式计算为 False 时应该处理的程序语句块。

清单 4. if 语句的基本语法
[*]if(expression one):
[*]    # Action to take if expression one evaluates True
[*]else:
[*]    # Action to take if all expression one evaluates False

复制代码
如果您使用过其他编程语言,那么该语法看起来可能既熟悉又陌生。相似之处在于 if 语句的一般格式、名称、用于确定如何分支语句执行流的表达式的计算,以及用于处理当表达式计算为 False 时的情况的 else 子句。但是有两个方面是完全特定于 Python 的:带有冒号字符的 if 和 else 语句的终止,以及 if 和 else 块中语句的缩进。正如所提到的,这两个特征是 Python 中流控制语句所必需的。

在清单 5 中,一个简单的 if/else 条件测试一个给定的数字是奇数还是偶数,并打印出结果。

清单 5. 一个简单的 if 语句例子
[*]>>> i = 8
[*]>>> if(i % 2):
[*]...   print "Odd Number"
[*]... else:
[*]...   print "Even Number"
[*]...
[*]Even Number

复制代码
一个似乎有些混乱的地方是 if 语句后面每一行前面的三个点(...)。当键入 if 语句和终止的冒号,并按键盘上的回车键时,Python 解释器就知道您输入了一个复合语句。因此,它就将提示符从三个大于符号(>>>)改为三个点(...)。因为 Python 需要缩进以错开当表达式计算为 True 或 False 时应该执行的语句块,所以两个 print 语句都缩进了 4 个空格。

if 语句(以及本文后面讨论的 elif 子句和 while 循环)中的表达式可以很复杂。它可以包括多个使用 Python 中支持的不同关系运算符的子表达式。而子表达式又可使用 and、or 和 not 逻辑运算符组合起来。本系列的第一篇文章“探索 Python,第 1 部分:Python 的内置数值类型”,包含更多关于布尔表达式和 Python 中不同关系和逻辑运算符的信息。

至此,已经看到了 if 语句可以如何用于根据一个特定布尔表达式的值,来执行两个程序语句块中的其中一个。然而在有些情况下,可能需要更多的选择。幸运的是,Python 提供了 if 语句的一个简单扩展。提供的解决方案非常简单:给 else 子句添加一个额外的 if 语句。结果是一个 else if 语句,简写为 elif,如清单 6 所示。

清单 6. 使用 elif 语句
[*]>>> i = -8
[*]>>> if(i > 0):
[*]...   print "Positive Integer"
[*]... elif(i < 0):
[*]...   print "Negative Integer"
[*]... else:
[*]...   print "Zero"
[*]...
[*]Negative Integer

复制代码
本例只包含一个 elif 语句,而实际中可根据程序需要包含任意多个。尽管它不是最优的解决方案,但是多个 elif 语句可以用于模拟其他一些语言中的 switch case 语句。

while 循环

Python 中的第二种流控制语句是 while 循环,它在一个表达式计算为 True 时执行一个程序语句块。while 循环与 if 语句一样,支持一个可选的 else 子句,其中包含一个当表达式计算为 False 时执行的程序语句块。但是对于 while 循环,这意味着在循环终止后,else 子句中的代码被执行一次(参见清单 7 中的伪代码)。

清单 7. while 循环的伪代码
[*]while (expression):
[*]    # statements to execute while loop expression is True
[*]else:
[*]    # statements to execute when loop expression is False

复制代码
理解了 if 语句之后,while 循环理解起来就相当简单了。但是一定要知道,循环一直要执行到表达式计算为 False。这意味着循环体中执行的程序语句必须要改变表达式的值,否则循环将无法结束。如清单 8 所示。

清单 8. while 循环的一个简单例子
[*]>>> i = 0 ; x = 10
[*]>>> while(x > 0):
[*]...   i+=1 ; x -= 1
[*]... else:
[*]...   print i, x
[*]...
[*]10 0

复制代码
该例演示了几件事情。首先,它在一行中组合了变量初始化和变量修改:在本例中是 i 和 x 变量。其次,分别使用缩写形式的运算符 += 和 -= 来递增 i 的值和递减 x 的值。在本例中,循环开始时 x 的值为 10。每通过一次循环,x 的值就递减 1。最后,x 的值为 0,此时循环退出,并执行 else 子句中的代码,打印出两个变量的值。

while 循环(与本文后面介绍的 for 循环一样)支持三种附加语句:
    * continue
    * break
    * pass

continue 和 break 语句分别用于在 while 循环中继续下一次循环或中断循环。这两个语句通常放在 if 语句体中,以便由一个特殊的条件触发 continue 或 break 操作。break 语句的一个特殊特性是,它完全中断循环,并跳转到循环下面的任一个 else 子句。

pass 语句什么都不做。它用作一个占位符,即在需要一个语句,但是程序逻辑不需要操作时使用。清单 9 中演示了这三种语句。

清单 9. 使用 continue、break 和 pass 语句
[*]>>> i = 1
[*]>>> while(i < 1000):
[*]...   i *= 5
[*]...   if(i % 25):
[*]...         continue
[*]...   if not (i % 125):
[*]...         break
[*]...   if not (i % 1000):
[*]...         pass
[*]... else:
[*]...   print i
[*]...
[*]>>> print i
[*]125

复制代码
这个虚构的例子一直循环到变量 i 大于或等于 1,000。在循环中,将 i 乘以 5,然后测试 i 是否被 25 整除。记住,您只在表达式为 True 时执行 if 语句体。该表达式在当变量 i 不能被 25 整除时计算为 True。(在 Python 表达式中,非零数被计算为布尔值 True。)

循环体中的下一个语句是第二个 if 语句,它测试变量 i 是否能被 125 整除,但是该表达式前面加了一个 not 运算符。因此,当变量 i 的值能被 125 整除时执行第二个 if 语句体。此时,break 语句导致程序执行中断 while 循环,跳转到 else 子句。

最后一个 if 语句永远不会执行,只是用于演示如何在程序中编写 pass 语句。在后续文章中,将会介绍 pass 语句更相关的一些情况。

通过跟踪程序的逻辑流可以看到,第一次通过循环后,变量 i 的值变为 5。第一个 if 语句计算为 True,因为 5 不能被 25 整除。这就会第二次进入 while 循环,这次变量 i 变成了 25。现在第一个 if 语句计算为 False,因为 25 能被 25 整除。第二个和第三个 if 语句也计算为 False,意味着第三次进入循环。这次变量 i 变成了 125,并且第一个 if 语句计算为 False。

但是第二个 if 语句计算为 True,因为变量 i 能被 125 整除(并且 not 运算符将结果 0 转换成布尔值 True)。这导致执行 break 语句,中断循环。else 子句永远不被执行,所以直到显式使用 print 语句之前不会输出任何东西。

for 循环

Python 中的 for 循环很特殊,与 Python 编程语言中内置的容器数据类型紧密相关。当您在现实生活中有一个容器对象(比如书包)时,您通常想要看它所包含的东西。在编写 Python 程序时也是这样的。当需要对某件事情做一定的次数时(就像针对容器中的每一项一样),可使用 for 循环。清单 10 中的伪代码格式演示了 for 循环。

清单 10. for 循环的伪代码
[*]for item in container:
[*]    # action to repeat for each item in the container
[*]else:
[*]    # action to take once we have finished the loop.

复制代码
由于 Python 容器类型的丰富特性,for 循环非常强大。本质上,for 循环涉及到一个迭代器(iterator),用于在集合中逐项移动。本系列的下一篇文章将更加详细地介绍 for 循环,以及如何正确地将它与不同容器类型一起使用。

控制流

本文介绍了三种 Python 程序语句:if 语句、while 循环和 for 循环。这三种语句通过选择执行哪些语句,或者通过多次执行一组语句,让您可以改变程序流。在后续文章中将大量用到这些语句。复合语句的特性引入了 Python 程序中的适当缩进特性,这使得 Python 程序易于读和理解。

winston 发表于 2012-6-9 21:08:50

第 6 部分: 用 Python 编程 —— 兴趣

本文讲述了 Python for 循环。for 循环用于迭代 Python 集合中的项目,集合包括前面讨论的 Python tuple、string 和 list 容器类型。通过使用 range(或 xrange)方法,for 循环还可用于访问某个container 类型中的元素。另外,还可以使用 range 方法在 for 循环内对一组语句执行特定次数。

for 循环

本文第 5 部分:用 Python 编程” 讨论了 if 语句和 while 循环,讨论了复合语句以及适当缩进 Python 语句来指示相关 Python 代码块。该文的结尾介绍了 Python for 循环。但就其使用和功能来说,for 循环更值得关注,所以本文单独讲述该循环。

for 循环有一个简单的语法,使您可以从容器对象中提取单个项目并对其进行某些操作。简单地说,使用 for 循环,可以迭代中对象集合的项目。对象集合可以是任何 Python 容器类型,包括前面文章中讨论的 tuple、string 和 list 类型。但是容器 metaphor 的功能比这三种类型更强大。metaphor 包括其他序列类型,如 dictionary 和 set,将来的文章中将对它们进行讨论。

但是请稍等!还有更多信息:for 循环可以用于迭代支持迭代 metaphor 的任何对象,这使 for 循环非常有用。

清单 1 中显示了 for 循环的基本语法,还演示了如何在 for 循环中使用 continue 和 break 语句。

清单 1. for 循环的伪代码
[*]for item in container:
[*]
[*]    if conditionA:      # Skip this item
[*]      continue
[*]
[*]    elif conditionB:      # Done with loop
[*]      break
[*]
[*]    # action to repeat for each item in the container
[*]
[*]else:
[*]
[*]    # action to take once we have finished the loop.

复制代码
本系列中的第二篇文章 “探索 Python,第 2 部分:探索 Python 类型的层次结构” 介绍了 Python tuple。如文中所述,tuple 类型是不可变的异构容器。这主要是说 tuple 可以存放不同类型的对象,但是它一旦创建,就无法更改。清单 2 演示了如何使用 for 循环迭代 tuple 的元素。

清单 2. for 循环和 tuple
[*]>>> t = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
[*]>>> count = 0
[*]>>> for num in t:
[*]...   count += num
[*]... else:
[*]...   print count
[*]...
[*]45
[*]>>> count = 0
[*]>>> for num in t:
[*]...   if num % 2:
[*]...         continue
[*]...   count += num
[*]... else:
[*]...   print count
[*]...
[*]20

复制代码
本例首先创建了名为 t 的 tuple,存放整数 0 至 9(包含 9)。第一个 for 循环迭代此 tuple,在 count 变量中累计 tuple 中数值的和。一旦代码已经迭代了 tuple 中的所有元素,它将进入 for 循环的 else 子句,打印 count 变量的值。

清单 2 中显示的第二个 for 循环也迭代 tuple 中的所有元素。但是,它仅累计容器中能够被 2 整除的那些项的值(请记住如果表达式为非零,if 语句将确定为真,num 不能被 2 整除时使用 % 运算符会返回非零值)。此限制通过使用适当的 if 语句和 continue 语句来完成。如前面的文章中所述,continue 语句使包含它的循环开始下一次迭代。实现相同结果的另一种方法是测试 tuple 中的当前项是否是偶数(使用 if not num % 2:),如果为真,那么将当前项添加到运行总和中。一旦代码完成 tuple 中的迭代,将调用 else 子句,打印总和。

本系列中的第三篇文章 “探索 Python:第 3 部分:探索 Python 类型的层次结构” 讨论了 Python string。string 是不可变的同构容器,这意味着它仅能存放字符且一旦建立将无法修改。清单 3 演示了如何使用 Python string 作为 for 循环的容器。

清单 3. for 循环和 string
[*]>>> st = "Python Is A Great Programming Language!"
[*]>>> for c in st:
[*]...   print c,
[*]...
[*]P y t h o n   I s   A   G r e a t   P r o g r a m m i n g   L a n g u a g e !
[*]>>> count = 0
[*]>>> for c in st:
[*]...   if c in "aeiou":
[*]...         count += 1
[*]... else:
[*]...   print count
[*]...
[*]10
[*]>>> count = 0
[*]>>> for c in st.lower():
[*]...   if c in "aeiou":
[*]...         count += 1
[*]... else:
[*]...   print count
[*]...
[*]12

复制代码
本例提供了三个不同的 for 循环,它们都迭代同一 string。第一个 for 循环迭代 string “Python Is A Great Programming Language!” 并一次打印 string 中的一个字符。在此例中,print 语句变量 c 后加了一个逗号。这使 print 语句打印字符值时后面跟着空格字符,而不是换行字符。如果没有后面的逗号,字符将全部打印在单独的行中,会很难读。

下两个 for 循环迭代该字符串并计算其包含多少个元音字母(“a”、“e”、“i”、“o” 或 “u”)。第二个 for 循环在迭代原始 string 时仅查找小写元音字母。第三个 for 循环迭代通过调用 string 对象的 lower 方法返回的临时 string。lower 方法将 string 中的所有字符转换为小写。因此,第三个 for 循环可找到另外两个元音字母。

本系列中的第四篇文章 “探索 Python,第 4 部分:探索 Python 类型的层次结构” 介绍了 Python list。list 是异构可变容器,这意味着它可以存放不同类型的对象且创建后可以修改。清单 4 演示了如何使用 list 和 for 循环。

清单 4. for 循环和 list
[*]>>> mylist = ]
[*]>>> for item in mylist:
[*]...   print item, "\t", type(item))
[*]...
[*]1       <type 'int'>
[*]1.0   <type 'float'>
[*]1j      <type 'complex'>
[*]1       <type 'str'>
[*](1,)    <type 'tuple'>
[*]   <type 'list'>

复制代码
既然 list 是很灵活的 Python 容器类型(您将在本系列其余的文章中多次看到它),本例看起来可能过于简单了。但是,这是一部分要点:使用 for 循环使处理容器中的每个项目非常简单,甚至处理包含各种不同对象的 list 也是如此。本例迭代 Python list 中的所有项目,并在单独的行中打印每一项及其相对应的 Python 类型。

迭代和可变容器

Python list 是一个可变序列,提供了一种令人好奇的可能性:for 循环主体可以修改其正在迭代的 list。正如您可能认为的,这样并不好,如果进行此操作,Python 解释器将无法很好地工作,如清单 5 所示。

清单 5. 在 for 循环中修改容器
[*]>>> mylist =
[*]>>> for item in mylist:
[*]...   if item % 2:
[*]...         mylist.insert(0, 100)
[*]...
[*]^CTraceback (most recent call last):
[*]File "<stdin>", line 3, in ?
[*]KeyboardInterrupt
[*]>>> print mylist
[*]# Many lines deleted for clarity
[*]>>> mylist =
[*]>>> for item in mylist[:]:
[*]...   if item % 2:
[*]...         mylist.insert(0, 100)
[*]...
[*]>>> print mylist
[*]

复制代码
本例中的第一个 for 循环只要在原始 list 中发现奇数,它就在 list 的开始插入数值 100。当然,这是一种演示此问题的不同寻常的方式,但却非常好。一旦在三个点的 Python 提示后按 Enter 键,Python 解释器就处于无限循环的混乱中。要停止这种混乱,必须通过按 Ctrl-C(其在 Python 输出中显示为 ^C)来中断进程,然后会出现 KeyboardInterrupt 异常。如果打印出修改的 list,将看到 mylist 现在包含大量的值为 100 的元素(新元素的准确数量取决于您中断循环的速度)。

本例中的第二个 for 循环演示了如何避免此问题。使用切片运算符创建原始 list 的副本。现在 for 循环将迭代该副本,而对原始 list 进行修改。最终的结果是修改后的原始 list,它现在以五个值为 100 的新元素开始。

for 循环和序列索引

如果您用过其他编程语言,Python for 循环可能看起来有点儿古怪。您可能认为它更像 foreach 循环。基于 C 的编程语言具有 for 循环,但它的设计目的是对一系列操作执行特定次数。Python for 循环可以通过使用内置的 range 和 xrange 方法来模拟该行为。清单 6 中演示了这两种方法。

清单 6. range 和 xrange 方法
[*]>>> r = range(10)
[*]>>> print r
[*]
[*]>>> type(r)
[*]<type 'list'>
[*]>>> xr = xrange(10)
[*]>>> print xr
[*]xrange(10)
[*]>>> type(xr)
[*]<type 'xrange'>

复制代码
本例首先演示了 range 方法,它创建一个包含一系列整数的新 list。调用 range 方法的一般形式是提供单个值,用作整数 list 的上限。零为起始值。因此,调用 range(10) 将创建包含整数 0 至 9(包含 9)的 list。range 方法接受起始索引以及步长。所以,调用 range(11,20) 将创建从 11 至 19(包含 19)的整数 list,而调用 range(12, 89, 2) 将创建从 12 至 88 的偶数 list。

由于 xrange 方法也创建整数 list(其使用相同参数),所以它与 range 方法非常相似。但是,xrange 方法仅在需要时才在 list 中创建整数。例如,在清单 6 中,尝试打印出新创建的 xrange 时除了 xrange 的名称,不会显示任何数据。当需要迭代大量整数时,xrange 方法更适用,因为它不会创建极大的 list,那样会消耗大量计算机内存。

清单 7 演示了如何在 for 循环内使用 range 方法来创建整数 1 至 10(包含 10)的乘法表。

清单 7. 创建乘法表
[*]>>> for row in range(1, 11):
[*]...   for col in range(1, 11):
[*]...         print "%3d " % (row * col),
[*]...   print
[*]...
[*]1    2    3    4    5    6    7    8    9   10
[*]2    4    6    8   10   12   14   16   18   20
[*]3    6    9   12   15   18   21   24   27   30
[*]4    8   12   16   20   24   28   32   36   40
[*]5   10   15   20   25   30   35   40   45   50
[*]6   12   18   24   30   36   42   48   54   60
[*]7   14   21   28   35   42   49   56   63   70
[*]8   16   24   32   40   48   56   64   72   80
[*]9   18   27   36   45   54   63   72   81   90
[*]10   20   30   40   50   60   70   80   90100

复制代码
本例使用两个 for 循环,外面的 for 循环关注乘法表中的每一行,嵌套的 for 循环关注每行内的列。每个循环都迭代包含整数 1 至 10(包含 10)的 list。最里面的 print 语句使用了一个名为 字符串格式化 的新概念来创建格式设置精美的表。字符串格式化是一种非常有用的技术,用于以格式设置精美的布局创建由不同数据类型组成的 string。现在详细信息并不重要,将来的文章中将讲述这些内容(了解 C 编程语言的 printf 方法的任何人都会很熟悉这些内容)。在本例中,字符串格式化指定将从整数创建新 string 且需要保留三个字符来存放该整数(如果该整数小于三个字符,将在左边用空格填补,从而使数据排列整齐)。第二个 print 语句用于打印新行,从而使乘法表中的下一行被打印在新的行中。

range 方法还可用于迭代容器,通过使用适当的索引访问序列中的每一项。要进行此操作,需要包含容器的允许范围索引值的整数 list,这可以通过使用 range 方法和 len 方法来轻松实现,如清单 8 所示。

清单 8. 在 for 循环内索引容器
[*]>>> st = "Python Is A Great Programming Language!"
[*]>>> for index in range(len(st)):
[*]...   print st,
[*]...
[*]P y t h o n   I s   A   G r e a t   P r o g r a m m i n g   L a n g u a g e !
[*]>>> for item in st.split(' '):
[*]...   print item, len(item)
[*]...
[*]Python 6
[*]Is 2
[*]A 1
[*]Great 5
[*]Programming 11
[*]Language! 9

复制代码
这个最后的示例演示了如何使用 len 方法作为 range 方法的参数,创建可用于单独访问 string 中每个字符的整数 list。第二个 for 循环还显示了如何将 string 分割为子字符串的 list(使用空格字符来指示子字符串的边界)。for 循环迭代子字符串 list,打印每个子字符串及其长度。

winston 发表于 2012-6-9 21:12:09



第 7 部分: 探索 Python 类型层次结构 —— 如何成功地使用 dictionary

本文研究 Python 类型层次结构并介绍 dictionary 容器类型。与前面文章中讨论的 Python tuple、string 和 list 容器类型不同,dictionary 类型是一个无序的容器,依赖于键-值映射。因此,要根据键值访问 dictionary 中的元素,而不是根据它们在序列中的位置。dictionary 类型的独特特性看起来可能不同寻常,但是如果使用得当,它们可以提供强大的能力。

dictionary

我们都曾经使用过语言词典来查找不认识的单词的定义。语言词典针对给定的单词(比如 python)提供一组标准的信息。这种系统将定义和其他信息与实际的单词关联(映射)起来。使用单词作为键定位器来寻找感兴趣的信息。这种概念延伸到 Python 编程语言中,就成了特殊的容器类型,称为 dictionary。

dictionary 数据类型在许多语言中都存在。它有时候称为关联 数组(因为数据与一个键值相关联),或者作为散列表。但是在 Python 中,dictionary 是一个很好的对象,因此即使是编程新手也很容易在自己的程序中使用它。按照正式的说法,Python 中的 dictionary 是一种异构的、易变的映射容器数据类型。

创建 dictionary

本系列中前面的文章介绍了 Python 编程语言中的一些容器数据类型,包括 tuple、string 和 list(参见 参考资料)。这些容器的相似之处是它们都是基于序列的。这意味着要根据元素在序列中的位置访问这些集合中的元素。所以,给定一个名为 a 的序列,就可以使用数字索引(比如 a )或片段(比如 a)来访问元素。Python 中的 dictionary 容器类型与这三种容器类型的不同之处在于,它是一个无序的集合。不是按照索引号,而是使用键值来访问集合中的元素。这意味着构造 dictionary 容器比 tuple、string 或 list 要复杂一些,因为必须同时提供键和相应的值,如清单 1 所示。

清单 1. 在 Python 中创建 dictionary,第 1 部分
[*]>>> d = {0: 'zero', 1: 'one', 2 : 'two', 3 : 'three', 4 : 'four', 5: 'five'}
[*]>>> d
[*]{0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five'}
[*]>>> len(d)
[*]>>> type(d)          # Base object is the dict class
[*]<type 'dict'>
[*]>>> d = {}         # Create an empty dictionary
[*]>>> len(d)
[*]>>> d = {1 : 'one'}# Create a single item dictionary
[*]>>> d
[*]{1: 'one'}
[*]>>> len(d)
[*]>>> d = {'one' : 1}# The key value can be non-numeric
[*]>>> d
[*]{'one': 1}
[*]>>> d = {'one': }
[*]>>> d
[*]{'one': }

复制代码
如这个例子所示,在 Python 中创建 dictionary 要使用花括号和以冒号分隔的键-值组合。如果没有提供键-值组合,那么就会创建一个空的 dictionary。使用一个键-值组合,就会创建具有一个元素的 dictionary,以此类推,直至您需要的任何规模。与任何容器类型一样,可以使用内置的 len 方法查明集合中元素的数量。

前面的示例还演示了关于 dictionary 容器的另一个重要问题。键并不限制为整数;它可以是任何不易变的数据类型,包括 integer、float、tuple 或 string。因为 list 是易变的,所以它不能作为 dictionary 中的键。但是 dictionary 中的值可以是任何数据类型的。

最后,这个示例说明了 Python 中 dictionary 的底层数据类型是 dict 对象。要进一步了解如何使用 Python 中的 dictionary,可以使用内置的帮助解释器来了解 dict 类,如清单 2 所示。

清单 2. 获得关于 dictionary 的帮助

>>> help(dict)on class dict in module __builtin__:
   dict(object)
|dict() -> new empty dictionary.
|dict(mapping) -> new dictionary initialized from a mapping object's
|      (key, value) pairs.
|dict(seq) -> new dictionary initialized as if via:
|      d = {}
|      for k, v in seq:
|          d = v
|dict(**kwargs) -> new dictionary initialized with the name=value pairs
|      in the keyword argument list.For example:dict(one=1, two=2)
|
|Methods defined here:
|
|__cmp__(...)
|      x.__cmp__(y) <==> cmp(x,y)
|
|__contains__(...)
|      x.__contains__(y) <==> y in x
|
|__delitem__(...)
|      x.__delitem__(y) <==> del x
...关于 dict 类的帮助指出,可以使用构造函数直接创建 dictionary,而不使用花括号。既然与其他容器数据类型相比,在创建 dictionary 时必须提供更多的数据,那么这些创建方法比较复杂也就不足为奇了。但是,在实践中使用 dictionary 并不难,如清单 3 所示。

清单 3. 在 Python 中创建 dictionary,第 2 部分
[*]>>> l =
[*]>>> d = dict(l)(most recent call last):
[*]File "<stdin>", line 1, in ?: can't convert dictionary
[*]update sequence element #0 to a sequence
[*]
[*]>>> l = [(0, 'zero'), (1, 'one'), (2, 'two'), (3, 'three')]
[*]>>> d = dict(l)
[*]>>> d
[*]{0: 'zero', 1: 'one', 2: 'two', 3: 'three'}
[*]>>> l = [, , , ]
[*]>>> d
[*]{0: 'zero', 1: 'one', 2: 'two', 3: 'three'}
[*]>>> d = dict(l)
[*]>>> d
[*]{0: 'zero', 1: 'one', 2: 'two', 3: 'three'}
[*]>>> d= dict(zero=0, one=1, two=2, three=3)
[*]>>> d
[*]{'zero': 0, 'three': 3, 'two': 2, 'one': 1}
[*]>>> d = dict(0=zero, 1=one, 2=two, 3=three): keyword can't be an expression

复制代码
可以看到,创建 dictionary 需要键值和数据值。第一次从 list 创建 dictionary 的尝试失败了,这是因为没有匹配的键-数据值对。第二个和第三个示例演示了如何正确地创建 dictionary:在第一种情况下,使用一个 list,其中的每个元素都是一个 tuple;在第二种情况下,也使用一个 list,但是其中的每个元素是另一个 list。在这两种情况下,内层容器都用于获得键到数据值的映射。

直接创建 dict 容器的另一个方法是直接提供键到数据值的映射。这种技术允许显式地定义键和与其对应的值。这个方法其实用处不大,因为可以使用花括号完成相同的任务。另外,如前面的例子所示,在采用这种方式时对于键不能使用数字,否则会导致抛出一个异常。

访问和修改 dictionary

创建了 dictionary 之后,需要访问其中包含的数据。访问方式与访问任何 Python 容器数据类型中的数据相似,如清单 4 所示。

清单 4. 访问 dictionary 中的元素
[*]>>> d= dict(zero=0, one=1, two=2, three=3)
[*]>>> d
[*]{'zero': 0, 'three': 3, 'two': 2, 'one': 1}
[*]>>> d['zero']
[*]>>> d['three']
[*]>>> d = {0: 'zero', 1: 'one', 2 : 'two', 3 : 'three', 4 : 'four', 5: 'five'}
[*]>>> d
[*]'zero'
[*]>>> d
[*]'four'
[*]>>> d(most recent call last):
[*]File "<stdin>", line 1, in ?: 6
[*]>>> d[:-1](most recent call last):
[*]File "<stdin>", line 1, in ?: unhashable type

复制代码
可以看到,从 dictionary 中获取数据值的过程几乎与从任何容器类型中获取数据完全一样。在容器名后面的方括号中放上键值。当然,dictionary 可以具有非数字的键值,如果您以前没有使用过这种数据类型,那么适应这一点需要些时间。因为在 dictionary 中次序是不重要的(dictionary 中数据的次序是任意的),所以可以对其他容器数据类型使用的片段功能,对于 dictionary 是不可用的。试图使用片段或者试图从不存在的键访问数据就会抛出异常,指出相关的错误。

Python 中的 dictionary 容器也是易变的数据类型,这意味着在创建它之后可以修改它。如清单 5 所示,可以添加新的键到数据值的映射,可以修改现有的映射,还可以删除映射。

清单 5. 修改 dictionary
[*]>>> d = {0: 'zero', 1: 'one', 2: 'two', 3: 'three'}
[*]>>> d
[*]'zero'
[*]>>> d = 'Zero'
[*]>>> d
[*]{0: 'Zero', 1: 'one', 2: 'two', 3: 'three'}
[*]>>> d = 'four'
[*]>>> d = 'five'
[*]>>> d
[*]{0: 'Zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five'}
[*]>>> del d
[*]>>> d
[*]{1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five'}
[*]>>> d = 'zero'
[*]>>> d
[*]{0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five'}

复制代码
清单 5 演示了几个重点。首先,修改数据值是很简单的:将新的值分配给适当的键。其次,添加新的键到数据值的映射也很简单:将相关数据分配给新的键值。Python 自动进行所有处理。不需要调用 append 这样的特殊方法。对于 dictionary 容器,次序是不重要的,所以这应该好理解,因为不是在 dictionary 后面附加映射,而是将它添加到容器中。最后,删除映射的办法是使用 del 操作符以及应该从容器中删除的键。

在清单 5 中有一个情况看起来有点儿怪,键值是按照数字次序显示的,而且这个次序与插入映射的次序相同。不要误解 —— 情况不总是这样的。Python dictionary 中映射的次序是任意的,对于不同的 Python 安装可能会有变化,甚至多次使用同一 Python 解释器运行相同代码也会有变化。如果在一个 dictionary 中使用不同类型的键和数据值,那么就很容易看出这一点,如清单 6 所示。

清单 6. 异构的容器
[*]>>> d = {0: 'zero', 'one': 1}
[*]>>> d
[*]{0: 'zero', 'one': 1}
[*]>>> d
[*]'zero'
[*]>>> type(d)
[*]<type 'str'>
[*]>>> d['one']
[*]>>> type(d['one'])
[*]<type 'int'>
[*]>>> d['two'] =
[*]>>> d
[*]{0: 'zero', 'two': , 'one': 1}
[*]>>> d = (0, 1, 2, 3)
[*]>>> d
[*]{0: 'zero', 3: (0, 1, 2, 3), 'two': , 'one': 1}
[*]>>> d = 'a tuple'
[*]>>> d
[*]{0: 'zero', 3: 'a tuple', 'two': , 'one': 1}

复制代码
如这个例子所示,可以在一个 dictionary 中使用不同数据类型的键和数据值。还可以通过修改 dictionary 添加新的类型。最后,产生的 dictionary 的次序并不与插入数据的次序匹配。本质上,dictionary 中元素的次序是由 Python dictionary 数据类型的实际实现控制的。新的 Python 解释器很容易改变这一次序,所以一定不要依赖于元素在 dictionary 中的特定次序。

用 dictionary 进行编程

作为正式的 Python 数据类型,dictionary 支持其他较简单数据类型所支持的大多数操作。这些操作包括一般的关系操作符,比如 <、> 和 ==,如清单 7 所示。

清单 7. 一般关系操作符
[*]>>> d1 = {0: 'zero'}
[*]>>> d2 = {'zero':0}
[*]>>> d1 < d2
[*]>>> d2 = d1
[*]>>> d1 < d2
[*]>>> d1 == d2
[*]>>> id(d1)
[*]>>> id(d2)
[*]>>> d2 = d1.copy()
[*]>>> d1 == d2
[*]>>> id(d1)
[*]>>> id(d2)

复制代码
前面的示例创建两个 dictionary 并使用它们测试 < 关系操作符。尽管很少以这种方式比较两个 dictionary;但是如果需要,可以这样做。

然后,这个示例将赋值给变量 d1 的 dictionary 赋值给另一个变量 d2。注意,内置的 id() 方法对于 d1 和 d2 返回相同的标识符值,这说明这不是复制操作。要想复制 dictionary ,可以使用 copy() 方法。从这个示例中的最后几行可以看出,副本与原来的 dictionary 完全相同,但是容纳这个 dictionary 的变量具有不同的标识符。

在 Python 程序中使用 dictionary 时,很可能希望检查 dictionary 中是否包含特定的键或值。如清单 8 所示,这些检查很容易执行。

清单 8. 条件测试和 dictionary
[*]>>> d = {0: 'zero', 3: 'a tuple', 'two': , 'one': 1}
[*]>>> d.keys()
[*]
[*]>>> if 0 in d.keys():
[*]...   print 'True'
[*]...
[*]>>> if 'one' in d:
[*]...   print 'True'
[*]...
[*]>>> if 'four' in d:
[*]...   print 'Dictionary contains four'
[*]... elif 'two' in d:
[*]...   print 'Dictionary contains two'
[*]... contains two

复制代码
测试 dictionary 中键或数据值的成员关系是很简单的。dictionary 容器数据类型提供几个内置方法,包括 keys() 方法和 values() 方法(这里没有演示)。这些方法返回一个列表,其中分别包含进行调用的 dictionary 中的键或数据值。

因此,要判断某个值是否是 dictionary 中的键,应该使用 in 操作符检查这个值是否在调用 keys() 方法所返回的键值列表中。可以使用相似的操作检查某个值是否在调用 values() 方法所返回的数据值列表中。但是,可以使用 dictionary 名作为简写表示法。这是有意义的,因为一般希望知道某个数据值(而不是键值)是否在 dictionary 中。

在 “Discover Python, Part 6” 中,您看到了使用 for 循环遍历容器中的元素是多么容易。同样的技术也适用于 Python dictionary,如清单 9 所示。

清单 9. 迭代和 dictionary
[*]>>> d = {0: 'zero', 3: 'a tuple', 'two': , 'one': 1}
[*]>>> for k in d.iterkeys():
[*]...   print d
[*]... tuple
[*]
[*]>>> for v in d.itervalues():
[*]...   print v
[*]... tuple
[*]
[*]>>> for k, v in d.iteritems():
[*]...   print 'd[',k,'] = ',v
[*]... [ 0 ] =zero[ 3 ] =a tuple[ two ] =[ one ] =1

复制代码
这个示例演示了遍历 dictionary 的三种方式:使用从 iterkeys()、itervalues() 或 iteritems() 方法返回的 Python 迭代器。(顺便说一下,可以通过在 dictionary 上直接调用适当方法,比如 d.iterkeys(),从而检查这些方法是否返回一个迭代器而不是容器数据类型。)iterkeys() 方法允许遍历 dictionary 的键,而 itervalues() 方法允许遍历 dictionary 包含的数据值。另一方面,iteritems() 方法允许同时遍历键到数据值的映射。

dictionary:另一种强大的 Python 容器

本文讨论了 Python dictionary 数据类型。dictionary 是一种异构的、易变的容器,依赖键到数据值的映射(而不是特定的数字次序)来访问容器中的元素。访问、添加和删除 dictionary 中的元素都很简单,而且 dictionary 很容易用于复合语句,比如 if 语句或 for 循环。可以在 dictionary 中存储所有不同类型的数据,可以按照名称或其他复合键值(比如 tuple)访问这些数据,所以 Python dictionary 使开发人员能够编写简洁而又强大的编程语句。








winston 发表于 2012-6-9 21:13:44

第 8 部分: 用 Python 的输入输出功能读取和写入数据

在这篇文章中,将学习如何处理文件。首先,回顾一种使用 Python 输出数据的简单方式,然后学习文件对象,Python 程序用它从文件读取数据和把数据写入文件。将演示打开文件的不同模式,最后将显示如何读取和写入二进制文件。

读取、写入和 Python

在上文中,学习了基本的 Python 数据类型和一些容器数据类型,例如 tuple、string 和 list。其他文章讨论了 Python 语言的条件和循环特性,以及它们如何与容器数据类型进行协作来简化编程任务。编写程序的最后一个基本步骤就是从文件读取数据和把数据写入文件。阅读完这篇文章之后,可以在自己的 to-do 列表中加上检验这个技能学习效果的任务。

简单输出

贯穿整个系列,一直用 print 语句写入(输出)数据,它默认把表达式作为 string 写到屏幕上(或控制台窗口上)。清单 1 演示了这一点。清单 1 重复了第一个 Python 程序 “Hello, World!”,但是做了一些小的调整。

清单 1. 简单输出
[*]>>> print "Hello World!"
[*]Hello World!
[*]>>> print "The total value is = $", 40.0*45.50
[*]The total value is = $ 1820.0
[*]>>> print "The total value = $%6.2f" % (40.0*45.50)
[*]The total value = $1820.00
[*]>>> myfile = file("testit.txt", 'w')
[*]>>> print >> myfile, "Hello World!"
[*]>>> print >> myfile, "The total value = $%6.2f" % (40.0*45.50)
[*]>>> myfile.close()

复制代码
正如这个示例演示的,用 print 语句写入数据很容易。首先,示例输出一个简单的 string。然后创建并输出复合的 string,这个字符串是用 string 格式化技术创建的。

但是,在这之后,事情发生了变化,与代码以前的版本不同。接下来的一行创建 file 对象,传递进名称 "testit.txt" 和 'w' 字符(写入文件)。然后使用修改过的 print 语句 —— 两个大于号后边跟着容纳 file 对象的变量 —— 写入相同的 string。但是这一次,数据不是在屏幕上显示。很自然的问题是:数据去哪儿了?而且,这个 file 对象是什么?

第一个问题很容易回答。请查找 testit.txt 文件,并像下面那样显示它的内容。
[*]% more testit.txt
[*]Hello World!
[*]The total value = $1820.00

复制代码
可以看到,数据被准确地写入文件,就像以前写到屏幕上一样。

现在,请注意清单 1 中的最后一行,它调用 file 对象的 close 方法。在 Python 程序中这很重要,因为在默认情况下,文件输入和输出是缓冲的;在调用 print 语句时,数据实际未被写入;相反,数据是成批写入的。让 Python 把数据写入文件的最简单方式就是显式地调用 close 方法。

文件对象

file 是与计算机上的文件进行交互的基本机制。可以用 file 对象读取数据、写入数据或把数据添加到文件,以及处理二进制或文本数据。

学习 file 对象的最简单方法就是阅读帮助,如清单 2 所示。

清单 2. 得到 file 对象的帮助
[*]>>> help(file)
[*]Help on class file in module __builtin__:
[*]class file(object)
[*]|file(name[, mode[, buffering]]) -> file object
[*]|
[*]|Open a file.The mode can be 'r', 'w' or 'a' for reading (default),
[*]|writing or appending.The file will be created if it doesn't exist
[*]|when opened for writing or appending; it will be truncated when
[*]|opened for writing.Add a 'b' to the mode for binary files.
[*]|Add a '+' to the mode to allow simultaneous reading and writing.
[*]|If the buffering argument is given, 0 means unbuffered, 1 means line
[*]|buffered, and larger numbers specify the buffer size.
[*]|Add a 'U' to mode to open the file for input with universal newline
[*]|support.Any line ending in the input file will be seen as a '\n'
[*]|in Python.Also, a file so opened gains the attribute 'newlines';
[*]|the value for this attribute is one of None (no newline read yet),
[*]|'\r', '\n', '\r\n' or a tuple containing all the newline types seen.
[*]|
[*]|'U' cannot be combined with 'w' or '+' mode.
[*]|
[*]|Note:open() is an alias for file().
[*]|
[*]|Methods defined here:
[*]...

复制代码
正如帮助工具指出的,使用 file 对象很简单。用 file 构造函数或 open 方法创建 file 对象,open 是 file 构造函数的别名。第二个参数是可选的,它指定文件的使用方式:
    * 'r' (默认值)表示从文件读取数据。
    * 'w' 表示要向文件写入数据,并截断以前的内容。
    * 'a' 表示要向文件写入数据,但是添加到当前内容尾部。
    * 'r+' 表示对文件进行读写操作(删除以前的所有数据)。
    * 'r+a' 表示对文件进行读写操作(添加到当前内容尾部)。
    * 'b' 表示要读写二进制数据。

这篇文章的第一个代码清单向文件写入数据。现在,清单 3 显示如何把这个数据读入 Python 程序,并解析文件的内容。

清单 3. 从文件读取数据
[*]>>> myfile = open("testit.txt")
[*]>>> myfile.read()
[*]'Hello World!\nThe total value = $1820.00\n'
[*]>>> str = myfile.read()
[*]>>> print str
[*]>>> myfile.seek(0)
[*]>>> str = myfile.read()
[*]>>> print str
[*]Hello World!
[*]The total value = $1820.00
[*]>>> str.split()
[*]['Hello', 'World!', 'The', 'total', 'value', '=', '$1820.00']
[*]>>> str.split('\n')
[*]['Hello World!', 'The total value = $1820.00', '']
[*]>>> for line in str.split('\n'):
[*]...   print line
[*]...
[*]Hello World!
[*]The total value = $1820.00
[*]>>> myfile.close()

复制代码
要读取数据,首先要创建合适的 file 对象 —— 在这个示例中,文件对象打开 testit.txt 文件,并用 read 方法读取内容。这个方法把整个文件读入一个 string,然后在程序中把这个字符串输出到控制台。在对 read 方法的第二个调用中,试图把值分配给 str 变量,结果返回一个空的 string。这是因为第一个 read 操作读入了整个文件。当试图再次读取内容时,已经到了文件末尾,所以什么也读不到。

这个问题的解决方案也很简单:让 file 对象返回文件的开头。回到开头要通过 seek 方法进行,它接受一个参数,表示要从文件中的什么位置开始读取或写入(例如,0 代表文件开头)。seek 方法支持更复杂的操作,但是可能会有危险。对于目前来说,我们还坚持采用简单方式。

现在回到了文件的开始之处,可以把文件内容读入 string 变量并对 string 做适当地解析。请注意,在文件中,行之间用新行(或行结束)字符区分。如果试着在 string 上调用 split 方法,它会在空白字符(例如空格)处进行拆分。为了让方法根据新行字符拆分各行,必须显式地指定新行字符。然后可以拆分 string 并在 for 循环中对文件的行进行迭代。

看起来仅仅从文件中读取和处理一行内容都有许多工作要做。Python 要让简单的事情变容易,所以您可能想知道这个任务有没有快捷方式可用。如清单 4 所示,答案是 yes。

清单 4. 读取和解析行
[*]>>> myfile = open("testit.txt")
[*]>>> for line in myfile.readlines():
[*]...   print line
[*]...
[*]Hello World!
[*]The total value = $1820.00
[*]>>> myfile.close()
[*]>>> for line in open("testit.txt").readlines():
[*]...   print line
[*]...
[*]Hello World!
[*]The total value = $1820.00
[*]>>> for line in open("testit.txt"):
[*]...   print line
[*]...
[*]Hello World!
[*]The total value = $1820.00

复制代码
清单 4 演示了读取和解析文本文件行的三种技术。首先,打开文件并把它分配给变量。然后调用 readlines 方法,把整个文件读入内存并把内容拆分成 string 列表。for 循环在 string 列表上进行迭代,一次输出一行。

第二个 for 循环通过使用 file 对象的隐式变量(也就是说,变量不是显式创建的),对这个过程稍做了点儿简化。打开文件和读取文件内容一次完成,生成的效果与第一个显式示例相同。最后一个示例进一步做了简化,并演示了直接在 file 对象上进行迭代的能力(请注意,这是 Python 的一个新特性,所以在您的计算机上可能无法工作)。在这个示例中,创建隐式 file 对象,然后 Python 做余下的工作,允许对文件中的全部行进行迭代。

但是,有些时候,在从文件读取数据时,可能想要更好的控制级别。在这种情况下,应当使用 readline 方法,如清单 5 所示。

清单 5. 读取数据
[*]>>> myfile = open("testit.txt")
[*]>>> myfile.readline()
[*]'Hello World!\n'
[*]>>> myfile.readline()
[*]'The total value = $1820.00\n'
[*]>>> myfile.readline()
[*]''
[*]>>> myfile.seek(0)
[*]>>> myfile.readline()
[*]'Hello World!\n'
[*]>>> myfile.tell()
[*]13L
[*]>>> myfile.readline()
[*]'The total value = $1820.00\n'
[*]>>> myfile.tell()
[*]40L
[*]>>> myfile.readline()
[*]''
[*]>>> myfile.tell()
[*]40L
[*]>>> myfile.seek(0)
[*]>>> myfile.read(17)
[*]'Hello World!\nThe '
[*]>>> myfile.seek(0)
[*]>>> myfile.readlines(23)
[*]['Hello World!\n', 'The total value = $1820.00\n']
[*]>>> myfile.close()

复制代码
这个示例演示了如何在文件中移动,一次读取一行,或者显式地用 seek 方法移动文件位置指示器。首先,用 readline 方法在文件行中移动。当到达文件末尾时,readline 方法返回一个空的 string。在过了文件末尾之后,如果还用这种方式继续读取,并不会造成错误,只会返回空的 string。

然后返回文件开始的地方,并读取另一行。 tell 方法显示出在文件中的当前位置(应当在第一行文本之后) —— 在这个示例中,在第 13 个字符位置。通过使用这个知识,可以向 read 或readline 方法传递一个参数,控制读取的字符数。对于 read 方法,这个参数(在这个示例中是 17)是要从文件中读取的字符数。但是 readline 方法在读入指定数量的字符后,还会继续读取,直到行尾。在这个示例中,它读取第一行和第二行文本。

写入数据

迄今为止的示例都侧重于读取数据,而不是写入数据。但是如清单 6 所示,一旦了解了使用 file 对象的基础知识,写入也很容易。

清单 6. 写入数据
[*]>>> mydata = ['Hello World!', 'The total value = $1820.00']
[*]>>> myfile = open('testit.txt', 'w')
[*]>>> for line in mydata:
[*]...   myfile.write(line + '\n')
[*]...
[*]>>> myfile.close()
[*]>>> myfile = open("testit.txt")
[*]>>> myfile.read()
[*]'Hello World!\nThe total value = $1820.00\n'
[*]>>> myfile.close()
[*]>>> myfile = open("testit.txt", "r+")
[*]>>> for line in mydata:
[*]...   myfile.write(line + '\n')
[*]...
[*]>>> myfile.seek(0)
[*]>>> myfile.read()
[*]'Hello World!\nThe total value = $1820.00\n'
[*]>>> myfile.close()
[*]>>> myfile = open("testit.txt", "r+a")
[*]>>> myfile.read()
[*]'Hello World!\nThe total value = $1820.00\n'
[*]>>> for line in mydata:
[*]...   myfile.write(line + '\n')
[*]...
[*]>>> myfile.seek(0)
[*]>>> myfile.read()
[*]'Hello World!\nThe total value = $1820.00\nHello World!\nThe total value = $1820.00\n'
[*]>>> myfile.close()

复制代码
要把数据写入文件,必须先创建 file 对象。但是,在这情况下,必须用 'w' 模式标记指定要写入文件。在这个示例中,把 mydata list 的内容写入文件,关闭文件,然后重新打开文件,这样就可以读取内容了。

但是,通常情况下,想要同时读取文件和写入文件,所以这个示例的下一部分用 'r+' 模式重新打开文件。因为能够写入文件,而不是添加,所以文件会被截断。首先,把 mydata list 的内容写入文件,然后把文件指针重新定位到文件开头,并读入内容。然后这个示例关闭文件,并用读取和添加模式 "r+a" 重新打开文件。正如示例代码所示,文件内容现在是两个写入操作的结果(文本是重复的)。

处理二进制数据

前面所有的示例都处理文本数据或字符数据:写入和读取字符 string。但是,在某些情况下,例如在处理整数或压缩文件时,需要能够读取和写入二进制数据。在创建 file 对象时,通过把 'b' 添加到文件模式中,可以很容易地用 Python 处理二进制数据,如清单 7 所示。

清单 7. 处理二进制数据
[*]>>> myfile = open("testit.txt", "wb")
[*]>>> for c in range(50, 70):
[*]...   myfile.write(chr(c))
[*]...
[*]>>> myfile.close()
[*]>>> myfile = open("testit.txt")
[*]>>> myfile.read()
[*]'23456789:;<=>?@ABCDE'
[*]>>> myfile.close()

复制代码
在这个示例中,创建一个合适的 file 对象,然后用从 50 到 69 的 ASCII 值写入二进制字符。使用 chr 方法,把 range 方法调用创建的整数转变成字符。在写完所有数据之后,关闭文件并重新打开文件进行读取,还是使用二进制模式标记。读取文件可以证明没有把整数写入文件,相反,写的是字符值。

在读取和写入二进制数据时,必须小心,因为不同的平台用不同的方式保存二进制数据。如果必须处理二进制数据,最好是使用来自 Python 库的合适对象(或者来自第三方开发人员的对象)。

读取和写入:最有趣的地方

这篇文章讨论了在 Python 程序中如何从文件读取数据和写入数据到文件中。总体来说,过程很简单:创建合适的 file 对象,然后按照需要读取和写入。但是,在使用写入模式创建 file 文件,向文件写入数据时,必须注意文件的截断。如果需要向文件中添加数据,应当在创建 file 对象时,使用添加模式。

winston 发表于 2012-6-9 21:14:31

第 9 部分: 化零为整 —— 函数、文件与模块

前文中已为 Python 编程新手介绍了几方面的主题,包括变量、容器对象和复合语句。本文以这些概念为基础,构造一个完整的 Python 程序。引入了 Python 函数和模块,并展示了构建一个 Python 程序、将其存储在文件中以及通过命令行运行该程序的方法。

返璞归真

许多流行的玩具都以这样一个概念为基础:简单的积木。这些简单的积木可通过多种方式组合在一起构造出全新的作品 —— 有时甚至完全令人出乎意料。这一概念同样适用于现实生活中的建筑领域,将基本原材料组合在一起,形成有用的建筑物。平凡无奇的材料、技术和工具简化了新建筑物的建造过程,同样也简化了对新踏入此领域的人员的培训。

相同的基本概念也适用于计算机程序开发技术,包括以 Python 编程语言编写的程序。本文介绍了使用 Python 创建基本构件 (building block) 的方法,可用于解决更为复杂的问题。这些基本构件可能小而简单,也可能庞大而复杂。无论采用哪种形式,我们这场游戏的目的就是定义基本构件,然后使用它们来创建专属于您的杰作。

函数:封装逻辑

在本系列的前几篇文章中,您通常不得不重复输入所有代码,即便它与上一行代码完全相同。此要求的惟一特例就是变量的使用:一旦初始化了变量的内容,之后就可以随时重用。显而易见,这一用法的普及对我们大有好处。

描述杰出程序员的最流行的箴言之一就是他们很懒惰。这并不表示杰出的程序员不努力工作 —— 而是说他们喜欢灵活的工作方法,除非绝对必要,否则从不反复做任何相同的事情。这也就意味着在您需要编写代码之前,首先考虑如何实现重用。Python 中有多种可实现重用的途径,但最简单的技术莫过于使用函数,也称为方法 或子例程。

与绝大多数现代编程语言类似,Python 支持使用方法将一组语句封装在一起,从而可在必要时重复使用。清单 1 给出了一段简单的伪代码,为您展示如何在 Python 中编写方法。

清单 1. 定义函数的伪代码
[*]def myFunction(optional input data):
[*]    initialize any local data
[*]    actual statements that do the work
[*]    optionally return any results

复制代码
如您所见,在 Python 中,函数的基本组成部分是包装器代码,指明将被重用的一系列 Python 语句。函数可接受输入参数,输入参数在紧接着函数名(在本例中为 myFunction)之后的圆括号内提供。函数还可返回值(更为正式的说法是:对象),包括像 tuple 这样的 Python 容器。

在真正着手构建函数之前,让我们先来看看关于伪代码的一些简单却重要的要点:
    * 请注意函数名中所用的字符大小写:大多数字符都是小写的,但在使用多个单词连接成一个函数名时,后接的各单词首字母应大写(例如,myFunction 中的 F)。这就是所谓的驼峰式大小写风格 (camel casing),是 Python 和其他编程语言中广泛采用的一种技术,可使函数的名称更易阅读。
    * 函数定义中的程序语句采用了缩进式排版,函数体由 Python 语句块构成,它们也必须像循环或条件语句那样缩进。
    * 函数定义的第一行也称为方法签名 (method signature),以 def 开头(def 是 define 这个单词的缩写)。
    * 方法签名以冒号结尾,表示下面的代码行是函数体。

至此,您或许已认可了使用方法的好处。那么让我们投入进去,开始编写函数吧。“Discover Python, Part 6: Programming in Python, For the fun of it” 中使用了一个 for 循环来创建乘法表。清单 2 展示了同样的概念,但本例中创建的是一个函数,用于封装乘法表计算背后的逻辑。

清单 2. 第一个函数
[*]>>> def timesTable():
[*]...   for row in range(1, 6):
[*]...         for col in range(1, 6):
[*]...             print "%3d " % (row * col),
[*]...         print
[*]...
[*]>>> timesTable()
[*]1    2    3    4    5
[*]2    4    6    8   10
[*]3    6    9   12   15
[*]4    8   12   16   20
[*]5   10   15   20   25
[*]>>> t = timesTable
[*]>>> type(t)
[*]<type 'function'>
[*]>>> t
[*]<function timesTable at 0x64c30>
[*]>>> t()
[*]1    2    3    4    5
[*]2    4    6    8   10
[*]3    6    9   12   15
[*]4    8   12   16   20
[*]5   10   15   20   25

复制代码
timesTable 函数定义起来非常简单,它不接受任何输入参数,也不返回任何结果。函数体几乎与 “Discover Python, Part 6” 中的语句完全相同(但该文章中的乘法表为从 1 到 10)。为了调用 方法,并使其发挥作用,只需输入函数名后接圆括号即可。本例中还输出了乘法表。

在 Python 中,函数是一类对象,与整型变量和容器对象相同。因而,您可以将函数指派给一个变量(切记,在 Python 中变量是动态类型化的)。在清单 2 中,我们将 timesTable 函数指派给变量 t。接下来的两行代码表示变量 t 确实指向函数。最后,我们使用变量 t 调用 timesTable 函数。

函数:动态更改逻辑

清单 2 中的 timesTable 函数不复杂,但也不是特别有用。更有用的示例允许您指定用于生成乘法表的行数和列数 —— 换言之,允许您在调用函数时动态地更改函数的操作方式。在函数定义中使用两个输入参数即可实现这一功能,如清单 3 所示。

清单 3. 更好的乘法表函数
[*]>>> def timesTable2(nrows=5, ncols=5):
[*]...   for row in range(1, nrows + 1):
[*]...         for cols in range(1, ncols + 1):
[*]...             print "%3d " % (row * cols),
[*]...         print
[*]...
[*]>>> timesTable2(4, 6)
[*]1    2    3    4    5    6
[*]2    4    6    8   10   12
[*]3    6    9   12   15   18
[*]4    8   12   16   20   24
[*]>>> timesTable2()
[*]1    2    3    4    5
[*]2    4    6    8   10
[*]3    6    9   12   15
[*]4    8   12   16   20
[*]5   10   15   20   25
[*]>>> timesTable2(ncols=3)
[*]1    2    3
[*]2    4    6
[*]3    6    9
[*]4    8   12
[*]5   10   15

复制代码
两个乘法表函数的定义非常相近,但清单 3 中的函数有用得多(通过清单 3 中的 3 次调用即可看出这一点)。为函数添加此附加功能的方法非常简单:提供名为 nrows 和 ncols 的两个输入参数,允许在调用函数时更改乘法表的大小。这两个参数随后会被提供给生成乘法表的两个 for 循环。

关于 timesTable2 函数的另一要点就是两个输入参数有默认值。在函数签名中为参数提供默认值,方法是在参数名后添加等号和值,例如 nrows=5。默认参数使程序获得了更高的灵活性,因为在您调用函数时,可以包含两个输入参数,也可以仅包含一个输入参数,甚至可以一个参数都不包含。但这种方法可能会导致某些问题。如果您在函数调用期间未指定全部参数,则必须显式地写出您所指定的参数的名称,以使 Python 解释器能够正确地调用函数。最后一个函数调用正体现了这一点,它显式地调用了带有 ncols=3 的 timesTable2 函数,函数创建了一个 5 行(默认值)3 列(所提供的值)的乘法表。

函数:返回数据

使用方法时,人们最希望获得的结果并非总是乘法表。您可能希望完成一次计算,并将计算结果值返回给调用代码。有时要实现这两个目的,需要分别调用不返回任何数据的调用方法(子例程)和返回值的方法(函数)。但在 Python 中,您无需担心这些语义问题,因为通过使用 return 语句,几乎可以相同的方式实现这两个目的(参见清单 4)。

清单 4. 在函数中返回一个值
[*]>>> def stats(data):
[*]...   sum = 0.0
[*]...   for value in data:
[*]...         sum += value
[*]...   return (sum/len(data))
[*]...
[*]>>> stats()   # Find the mean value from a list
[*]3.0
[*]>>> stats((1, 2, 3, 4, 5))   # Find the mean value from a tuple
[*]3.0
[*]>>> stats()
[*]Traceback (most recent call last):
[*]File "<stdin>", line 1, in ?
[*]TypeError: stats() takes exactly 1 argument (0 given)
[*]>>> stats("12345")
[*]Traceback (most recent call last):
[*]File "<stdin>", line 1, in ?
[*]File "<stdin>", line 4, in stats
[*]TypeError: unsupported operand type(s) for +=: 'float' and 'str'

复制代码
这个简单的函数遍历 data(假设 data 为一个容纳有数字数据的 Python 容器),计算一组数据的平均值,然后返回值。函数定义接受一个输入参数。平均值通过 return 语句传回。当您调用带有包含数字 1 到 5 的 list 或 tuple 的函数时,返回值会显示在屏幕上。如果调用不带任何参数的函数、带非容器数据类型的函数或带内含非数字数据的容器的函数,就会导致出错。(在此类情况下抛出错误是很有意义的。更高级的处理方法应包含恰当的错误检查和处理,以应对这些情况,但这不在本文讨论范围内。)

此示例已经非常有用,但还可使它更强大,如清单 5 所示。在 Python 中,函数可返回任何有效的对象类型,包括容器类型在内。因此,您可以计算多个数量,并轻松地将多个结果返回给调用语句。

清单 5. 返回复合值
[*]>>> def stats(data):
[*]...   sum = 0.0
[*]...   for value in data:
[*]...         sum += value
[*]...   mean = sum/len(data)
[*]...   sum = 0.0
[*]...   for value in data:
[*]...         sum += (value - mean)**2
[*]...   variance = sum/(len(data) - 1)
[*]...   return (mean, variance)
[*]...
[*]>>> stats()
[*](3.0, 2.5)
[*]>>> (m, v) = stats()
[*]>>> print m, v
[*]5.0 7.5

复制代码
为了从一个函数中返回多个值,要将其括在一个括号中并以逗号分隔 —— 换句话说,创建并返回一个 tuple。新 stats 函数的函数体要略加修改,以计算数字序列的样本方差。最后,正如 stats 函数的两次调用所示,tuple 值可作为一个 tuple 存取,也可将其解包为各自的分量。

模块:简化代码重用

至此,您或许已相信了代码重用的价值。但即便是使用函数,您依然需要在打算使用函数时重新输入函数体。例如,当您打开一个新的 Python 解释器时,必须键入之前所创建的所有函数。幸运的是,您可以使用模块 将相关函数(和其他 Python 对象)封装在一起,将其保存在一个文件中,然后将这些已定义好的函数导入到新 Python 代码内,包含于 Python 解释器之中。

为介绍在 Python 中使用模块的方法,我们将重用清单 5 中的 stats 方法。有两个选择:您可以从与本文相关的压缩文件中提取名为 test.py 的文件,也可以在编辑器中键入函数,然后将文件保存为 test.py。完成上一步后,在您保存 test.py 的目录中启动一个新的 Python 解释器,然后输入如清单 6 所示的语句。

清单 6. 使用模块
[*]>>> import test
[*]>>> test.stats()
[*](5.0, 7.5)
[*]>>> from test import stats
[*]>>> (m, v) = stats()
[*]>>> print m, v
[*]5.0 7.5

复制代码
第一行 import test 打开文件 test.py 并处理文件中的各条语句。这里仅定义了 stats 函数,但若需要,您还可定义更多的函数。调用 stats 函数时,应以模块名 test 作为函数前缀。之所以使用这种复杂的名称,是出于作用域 方面的考虑,作用域表示一个程序内名称的有效范围。为告知 Python 您要调用的是哪个 stats 方法,就必须提供完整的名称。这一点非常重要,因为您可能拥有多个名称相同的对象。作用域规则可帮助 Python 判断您想使用的对象。

第三行 from test import stats 也打开了文件 test.py,但它隐式地将 stats 方法置入当前文件的作用域内,以使您能够直接调用 stats 函数(无需使用模块名)。明智地使用 from ... import ... 语法可使您的程序更简洁,但过度的使用也会导致混淆,甚至出现更糟糕的作用域冲突错误。不要滥用您的新武器!

模块库

使用 Python 编程语言的一个主要好处就是大型的内置式标准库,可作为 Python 模块访问。常用模块示例如下:
    * math 包含有用的数学函数。
    * sys 包含用于与 Python 解释器交互的数据和方法。
    * array 包含数组数据类型和相关函数。
    * datetime 包含有用的日期和时间处理函数。

由于这些都是内置模块,因此您可以通过帮助解释器来了解更多相关内容,如清单 7 所示。

清单 7. 获得关于 math 模块的帮助信息
[*]>>> help(math)
[*]Traceback (most recent call last):
[*]File "<stdin>", line 1, in ?
[*]NameError: name 'math' is not defined
[*]>>> import math   # Need to import math module in order to use it
[*]>>> help(math)
[*]Help on module math:
[*]NAME
[*]    math
[*]FILE
[*]    /System/Library/Frameworks/Python.framework/Versions/2.4/lib/
[*]python2.4/lib-dynload/math.so
[*]DESCRIPTION
[*]    This module is always available.It provides access to the
[*]    mathematical functions defined by the C standard.
[*]FUNCTIONS
[*]    acos(...)
[*]      acos(x)
[*]
[*]      Return the arc cosine (measured in radians) of x.
[*]
[*]    asin(...)
[*]      asin(x)
[*]
[*]      Return the arc sine (measured in radians) of x.
[*]...

复制代码
math 模块的帮助输出展示了所支持的大量数学函数,包括 sqrt 函数在内。您可以利用此函数将您的样本方差计算转换为样本标准差计算,如清单 8 所示。

清单 8. 使用多个模块
[*]>>> from math import sqrt
[*]>>> from test import stats
[*]>>> (m, v) = stats()
[*]>>> print m, sqrt(v)
[*]5.0 2.73861278753

复制代码
如您所见,您可以将多个模块导入到一个 Python 程序中。在大型、内置的模块库与更大量的公用库(其中许多都是开放源码的)的共同协助下,您很快也会成为一名懒惰 —— 也就是杰出 —— 的程序员。

可执行文件

导入一个模块时,Python 解释器会处理模块文件内的各行。实际上,您可以调用 Python 解释器使其仅处理包含于一个文件中的一个 Python 程序。在基于 UNIX® 的操作系统中,您可以轻松创建可执行的文件,如清单 9 所示。

清单 9. 一个完整的 Python 程序
[*]#!/usr/bin/env python
[*]def stats(data):
[*]    sum = 0.0
[*]    for value in data:
[*]      sum += value
[*]    mean = sum/len(data)
[*]    sum = 0
[*]    for value in data:
[*]      sum += (value - mean)**2
[*]    variance = sum/(len(data) - 1)
[*]    return(mean, variance)
[*](m, v) = stats()
[*]print "The mean and variance of the values " \
[*]"from 1 to 9 inclusive are ",m, v

复制代码
观察上例,您应该会产生几分好感,将 Python 程序置于文件内,并使其运行是如此简单。本例与 test.py 文件中的代码之间惟一的差异就是包含了第一行。在基于 UNIX 的操作系统中,本行会使 Python 解释器自动启动,并在终止前处理文件中的语句。本示例中的其他行定义了 stats 函数、调用了函数,并输出了结果。

要运行本文件中的语句,您需要启动一个 Python 解释器,并让它去读取和处理文件的内容。为实现这一目的,您必须首先将清单 9 中的示例输入到一个名为 mystats.py 的文件中,也可从与本文相关的压缩文件中提取文件。进入包含此文件的目录,然后按清单 10 中所示命令执行。注意对于 Microsoft® Windows® 操作系统而言,仅应使用第一条命令;其他命令是供 UNIX 系统(如 Linux® 或 Mac OS X)使用的。

清单 10. 执行 Python 程序
[*]rb% python mystats.py
[*]The mean and variance of the values from 1 to 9 inclusive are 5.0 7.5
[*]rb% chmod +x mystats.py
[*]rb% ./mystats.py
[*]The mean and variance of the values from 1 to 9 inclusive are 5.0 7.5

复制代码
清单 10 中的命令展示了运行一个包含于文件之中的 Python 程序的方法。第一条命令以文件名调用 Python 解释器,无论使用哪种系统安装 Python、Python 解释器位于哪个目录下,这种方法都有效。第二条命令 chmod 使包含 Python 程序的文件成为可执行文件。第三条命令告诉操作系统运行程序。这是通过使用 env 程序实现的,这是一种独立于操作系统的技术,用于定位和运行程序 —— 本例中是 Python 解释器。

重用与缩减

本文介绍了如何在 Python 中编写可重用代码。讨论了如何在 Python 程序中使用方法或可重用块。方法可接受输入参数,也可返回数据,包括容器数据类型在内。这种功能使方法成为一种可处理大量问题的强大途径。本文还介绍了模块,模块可使您将相关方法及数据合并入一个有组织的层次结构中,而此结构可方便地在其他 Python 程序中重用。最后还介绍了如何将所有这些内容组合在一起以创建一个功能完整、独立的 Python 程序。您已经看到,代码的重用也就意味着您的工作量缩减。对于程序员而言,懒惰是一种优势而非陋习。

winston 发表于 2012-10-19 10:13:19

刷新,冲掉垃圾广告标题。
页: [1] 2
查看完整版本: 探索Python:Python初学者入门,功能强大的面向对象脚本语言