第 2 章:数据类型和引用

编程语言和应用程序需要数据。我们定义应用程序来处理数据,并且我们需要有可以用来保存数据的容器。本章将重点介绍如何定义容器以及如何使用它们来处理应用程序数据。无论我们使用的数据来自键盘输入还是数据库,都需要一种方法在程序中临时存储它,以便对其进行操作和使用。完成对数据的处理后,这些临时容器可以被销毁,以便为新的结构腾出空间。

我们将首先介绍 Python 语言提供的不同数据类型,然后讨论如何在收集和存储数据后使用这些数据。我们将比较和对比我们拥有的不同类型的结构,并提供一些关于在处理不同类型的数据时使用哪些结构的示例。通过使用列表、字典和元组,可以完成许多任务,我们将尝试涵盖其中许多任务。一旦您学会了如何定义和使用这些结构,我们将讨论一下当应用程序不再需要它们时会发生什么。

让我们开始探索 Python 编程语言中的数据类型和结构的旅程……这些技能将在每个实际的 Jython 程序中使用。

Python 数据类型

正如我们所讨论的,需要在程序中存储和操作数据。为了做到这一点,我们还必须能够创建用于保存数据的容器,以便程序可以使用它。语言需要知道如何在存储数据后处理数据,我们可以通过在 Java 中为我们的容器分配数据类型来做到这一点。但是,在 Python 中,这不是必需的,因为解释器能够以动态的方式确定我们正在存储哪种类型的数据。

表 2-1 列出了每种数据类型,并简要描述了定义每种数据类型的特征。

表 2-1. Python 数据类型

数据类型 特征
None NULL 值对象。
int 普通整数(例如 32)。
long 长整数。带有 'L' 后缀的整数字面量,长度超过普通整数。
float 浮点数。包含小数点或指数符号的数字字面量。
complex 复数。表示为一个数字字面量加上实部和虚部的和。
Boolean 真或假值(也称为 1 和 0 的数值)。
Sequence 包括以下类型:字符串、Unicode 字符串、basestring、列表、元组。
Mapping 包括字典类型。
Set 无序的独特对象集合;包括以下类型:集合、冻结集合。
File 用于使用文件系统对象。
Iterator 允许迭代容器。有关更多详细信息,请参见迭代器部分。

有了所有这些信息和上面的示例,我们应该正式讨论如何在 Python 语言中声明变量。让我们看一些在以下代码行中定义变量的示例。

清单 2-1. 在 Jython 中定义变量:

# Defining a String
x = 'Hello World'
x = "Hello World Two"

# Defining an integer
y = 10

# Float
z = 8.75

# Complex
i = 1 + 8.07j

需要注意的重要一点是,Jython 中实际上没有类型。每个对象都是一个类的实例。要查找对象的类型,只需使用 type() 函数。

清单 2-2.:

# Return the type of an object using the type function
>>> i = 1 + 8.07j
>>> type(i)
<type 'complex'>
>>> a = 'Hello'
>>> type(a)
<type 'str'>

需要注意的一个很好的功能是多重赋值。通常需要将多个值分配给不同的变量。使用 Python 中的多重赋值,可以在一行中完成此操作。

清单 2-3. 多重赋值:

>>> x, y, z = 1, 2, 3
>>> print x
1
>>> print z
3

字符串和字符串方法

字符串是大多数编程语言中的一种特殊类型,因为它们经常用于操作数据。Python 中的字符串是字符序列,它是不可变的。不可变对象是在创建后无法更改的对象。相反的是可变对象,它可以在创建后被修改。这非常重要,因为它对字符串的整体理解有很大影响。但是,有一些字符串方法可以用来操作特定字符串的内容。我们实际上从未操作过内容,这些方法返回字符串的已操作副本。原始字符串保持不变。

在 Jython 2.5.0 发布之前,CPython 和 Jython 对字符串的处理略有不同。CPython 中有两种类型的字符串对象,它们被称为标准字符串和 Unicode 字符串。有很多文档专门针对两种字符串类型的差异,本参考只涵盖基础知识。值得注意的是,Python 包含一个名为 basestring 的抽象字符串类型,以便可以检查任何类型的字符串以确保它是一个字符串实例。

在 Jython 2.5.0 发布之前,只有一种字符串类型。Jython 中的字符串类型支持完整的双字节 Unicode 字符,并且字符串模块中包含的所有函数都支持 Unicode。如果指定了 u'' 字符串修饰符,Jython 会忽略它。自 2.5.0 发布以来,Jython 中的字符串与 CPython 中的字符串一样对待,因此相同的规则将适用于这两种实现。如果您有兴趣了解更多关于字符串编码的信息,有很多关于该主题的优秀参考资料。还值得注意的是,Jython 使用来自 Java 平台的字符方法。因此,isupper 和 islower 等属性(我们将在后面的部分中讨论)是基于 Java 方法的,尽管它们实际上与 CPython 对应方法的工作方式相同。

在本节的其余部分,我们将介绍所有可供我们使用的众多字符串函数。这些函数将适用于标准字符串和 Unicode 字符串。与 Python 和其他编程语言中的许多其他功能一样,有时完成一项任务有多种方法。在字符串和字符串操作的情况下,情况也是如此。但是,您会发现,在大多数情况下,尽管有多种方法可以完成任务,但 Python 专家添加了函数,使我们能够实现性能更高、更易于阅读的代码。

表 2-2 列出了自 2.5 版本以来内置于 Python 语言的所有字符串方法。由于 Python 是一种不断发展的语言,因此此列表在将来的版本中肯定会发生变化。通常,会添加语言的补充,或者增强现有功能。在表格之后,我们将给出这些方法的众多示例以及它们的使用方法。虽然我们无法提供每个方法如何工作的示例(那将是一本书),但它们的功能都相同,因此应该很容易上手。

表 2-2. 字符串方法

方法 功能描述
capitalize() 返回字符串的标题化副本
center (width[,fill]) 返回一个重新定位的字符串,具有指定的宽度,并提供可选的填充字符
count(sub[,start[,end]]) 计算子字符串在字符串中出现的次数
decode([encoding[,errors]]) 解码并返回 Unicode 字符串
encode([encoding[,errors]]) 返回字符串的编码版本
endswith(suffix[,start[,end]]) 返回一个布尔值,表示字符串是否以给定的模式结尾
expandtabs([tabsize]) 将字符串中的制表符转换为空格
find(sub[,start[,end]]) 返回给定子字符串第一次出现的起始位置的索引
index(sub[,start[,end]) 返回给定子字符串第一次出现的起始位置的索引。如果子字符串未找到,则引发 ValueError。
isalnum() 返回一个布尔值,表示字符串是否仅包含字母数字字符
isalpha() 返回一个布尔值,表示字符串是否包含所有字母字符
isdigit() 返回一个布尔值,表示字符串是否包含所有数字字符
islower() 返回一个布尔值,表示字符串是否包含所有小写字符
isspace() 返回一个布尔值,表示字符串是否仅包含空格
istitle() 返回一个布尔值,表示字符串中每个单词的第一个字符是否都大写
isupper() 返回一个布尔值,表示字符串中所有字符是否都大写
join(sequence) 返回序列的副本,使用原始字符串将每个元素连接在一起
ljust(width[,fillchar]) 返回一个指定宽度的字符串,以及原始字符串的副本位于最左侧。 (可选地用 fillchar 填充空空格)
lower() 返回原始字符串的副本,其中字符串中的所有字符都转换为小写
lstrip([chars]) 从左侧删除字符串中第一个找到的与给定字符匹配的字符。 还会从左侧删除空格。 当没有指定参数时,默认情况下会删除空格。
partition(separator) 返回从左侧开始使用提供的分隔符分隔的字符串
replace(old,new[,count]) 返回原始字符串的副本,将 old 中给出的字符串部分替换为 new 中给出的部分
rfind(sub[,start[,end]]) 从右到左搜索字符串,找到给定字符串的第一次出现,并返回 sub 找到的最高索引
rindex(sub[,start[,end]]) 从右到左搜索字符串,找到给定字符串的第一次出现,并返回 sub 找到的最高索引,或者引发异常
rjust(width[,fillchar]) 返回字符串的副本,向右对齐宽度
rpartition(separator) 返回字符串的副本,从右侧开始使用提供的分隔符对象进行分隔
rsplit([separator[,maxsplit]]) 返回字符串中的单词列表,并从右侧拆分字符串,使用给定的分隔符作为分隔符。 如果指定了 maxsplit,则最多执行 maxsplit 拆分(从右侧开始)。
rstrip([chars]) 返回字符串的副本,从右侧删除字符串中第一个找到的与给定字符匹配的字符。 当没有指定参数时,还会从右侧删除空格。
split([separator[,maxsplit]]) 返回字符串中的单词列表,并从左侧拆分字符串,使用给定的分隔符作为分隔符。
splitlines([keepends]) 将字符串拆分为行列表。 keepends 表示是否删除换行符分隔符。 返回字符串中的行列表。
startswith(prefix[,start[,end]]) 返回一个布尔值,表示字符串是否以给定的前缀开头
strip([chars]) 返回字符串的副本,从字符串中删除给定的字符。 如果没有指定参数,则删除空格。
swapcase() 返回字符串的副本,将字符串中每个字符的大小写转换。
title() 返回字符串的副本,每个单词的第一个字符都大写。
translate(table[,deletechars]) 返回字符串的副本,使用给定的字符转换表来转换字符串。 可选的 deletechars 参数中出现的所有字符都将被删除。
upper() 返回字符串的副本,其中字符串中的所有字符都转换为大写
zfill(width) 返回一个数字字符串,从左侧用零填充到指定的宽度。

现在让我们看一些示例,以便您了解如何使用字符串方法。 如前所述,它们中的大多数以类似的方式工作。

清单 2-4. 使用字符串方法:

our_string='python is the best language ever'

# Capitalize first character of a String
>>> our_string.capitalize()
'Python is the best language ever'

# Center string
>>> our_string.center(50)
'         python is the best language ever         '
>>> our_string.center(50,'-')
'---------python is the best language ever---------'

# Count substring within a string
>>> our_string.count('a')
2

# Count occurrences of substrings
>>> state = 'Mississippi'
>>> state.count('ss')
2

# Partition a string returning a 3-tuple including the portion of string
# prior to separator, the separator
# and the portion of string after the separator
>>> x = "Hello, my name is Josh"
>>> x.partition('n')
('Hello, my ', 'n', 'ame is Josh')

# Assuming the same x as above, split the string using 'l' as the separator
>>> x.split('l')
['He', '', 'o, my name is Josh']

# As you can see, the tuple returned does not contain the separator value
# Now if we add maxsplits value of 1, you can see that the right-most split
# is taken.  If we specify maxsplits value of 2, the two right-most splits
# are taken
>>> x.split('l',1)
['He', 'lo, my name is Josh']
>>> x.split('l',2)
['He', '', 'o, my name is Josh']

字符串格式化

使用 print 语句打印字符串时,您有很多选择。与 C 编程语言类似,Python 字符串格式化允许您在打印时使用多种不同的转换类型。

清单 2-5. 使用字符串格式化:

# The two syntaxes below work the same
>>> x = "Josh"
>>> print "My name is %s" % (x)
My name is Josh
>>> print "My name is %s" % x
My name is Josh

# An example using more than one argument
>>> name = 'Josh'
>>> language = 'Python'
>>> print "My name is %s and I speak %s" % (name, language)
My name is Josh and I speak Python

# And now for some fun, here's a different conversion type
# Mind you, I'm not sure where in the world the temperature would
# fluctuate so much!
>>> day1_temp = 65
>>> day2_temp = 68
>>> day3_temp = 84
>>> print "Given the temparatures %d, %d, and %d, the average would be %f" % (day1_temp, day2_temp, day3_temp, (day1_temp + day2_temp + day3_temp)/3)
Given the temperatures 65, 68, and 83, the average would be 72.333333

表 2-3 列出了转换类型。

表 2-3. 转换类型

   
d 带符号整数十进制
i 带符号整数
o 无符号八进制
u 无符号十进制
x 无符号十六进制(小写)
X 无符号十六进制(大写字母)
E 浮点指数格式(大写“E”)
e 浮点指数格式(小写“e”)
f 浮点十进制格式(小写)
F 浮点十进制格式(与“f”相同)
g 如果指数 < -4,则为浮点指数格式,否则为浮点数
G 如果指数 < -4,则为浮点指数格式(大写),否则为浮点数
c 单个字符
r 字符串(使用 repr() 转换任何 Python 对象)
s 字符串(使用 str() 转换任何 Python 对象)
% 无转换,如果指定两次,则结果为百分号 (%) 字符

清单 2-6.:

>>> x = 10
>>> y = 5.75
>>> print 'The expression %d * %f results in %f' % (x, y, x*y)
The expression 10 * 5.750000 results in 57.500000

# Example of using percentage
>>> test1 = 87
>>> test2 = 89
>>> test3 = 92
>>> "The gradepoint average of three students is %d%%" % (avg)
'The gradepoint average of three students is 89%'

列表、字典、集合和元组

列表、字典、集合和元组都提供类似的功能和可用性,但它们在语言中都有自己的利基。我们将介绍每个的几个示例,因为它们在某些情况下都起着重要作用。与字符串不同,本节中讨论的所有容器(元组除外)都是可变对象,因此它们可以在创建后进行操作。

由于这些容器非常重要,我们将在本章末尾进行一个练习,让您有机会自己尝试使用它们。

列表

也许 Python 编程语言中最常用的结构是列表。大多数其他编程语言都提供类似的容器来存储和操作应用程序中的数据。Python 列表比静态类型语言中提供的类似结构具有优势。Python 语言的动态特性有助于列表结构利用其包含不同类型值的强大功能。这意味着列表可以用于存储任何 Python 数据类型,并且这些类型可以在单个列表中混合使用。在其他语言中,这种类型的结构通常被定义为类型化对象,这会将结构锁定为仅使用一种数据类型。

Python 列表的创建和使用与语言的其余部分一样简单易用。只需将一组空方括号分配给变量即可创建一个空列表。我们也可以使用内置的 list() 函数来创建一个列表。列表可以在应用程序运行时构建和修改,它们没有声明为静态长度。它们易于通过循环遍历,索引也可以用于列表中特定项目的定位放置或删除。我们将首先展示一些定义列表的示例,然后介绍 Python 语言为我们提供的用于处理列表的每种方法。

清单 2-7. 定义列表:

# Define an empty list
my_list = []
my_list = list()  # rarely used

# Single Item List
>>> my_list = [1]
>>> my_list           # note that there is no need to use print to display a variable in the interpreter
[1]

# Define a list of string values
my_string_list = ['Hello', 'Jython' ,'Lists']

# Define a list containing mulitple data types
multi_list = [1, 2, 'three', 4, 'five', 'six']

# Define a list containing a list
combo_list = [1, my_string_list, multi_list]

# Define a list containing a list inline
>>> my_new_list = ['new_item1', 'new_item2', [1, 2, 3, 4], 'new_item3']
>>> print my_new_list
['new_item1', 'new_item2', [1, 2, 3, 4], 'new_item3']

如前所述,为了从列表中获取值,我们可以使用索引。与 Java 语言中的数组类似,使用 list[index] 符号将允许我们访问一个项目。如果我们希望从列表中获取一个范围或一组值,我们可以提供一个起始索引和/或一个结束索引。这种技术也称为切片。更重要的是,我们还可以通过提供一个步长索引来返回列表中的一组值以及一个步长模式。要记住的一点是,在通过索引访问列表时,列表中的第一个元素包含在索引 0 中。请注意,在切片列表时,始终会返回一个新列表。创建列表的浅拷贝的一种方法是使用切片符号,而不指定上限或下限。下限默认为零,上限默认为列表的长度。

请注意,浅拷贝会构造一个新的复合对象(列表或其他包含对象的),然后将引用插入到原始对象中。深拷贝会构造一个新的复合对象,然后根据原始对象中找到的对象插入副本。

清单 2-8. 访问列表:

# Obtain elements in the list
>>> my_string_list[0]
'Hello'
>>> my_string_list[2]
'Lists'

# Negative indexes start with the last element in the list and work back towards the
first

# item
>>> my_string_list[-1]
'Lists'
>>> my_string_list[-2]
'Jython'

# Using slicing (Note that slice includes element at starting index and excludes the
end)
>>> my_string_list[0:2]
['Hello', 'Jython']

# Create a shallow copy of a list using slice
>>> my_string_list_copy = my_string_list[:]
>>> my_string_list_copy
['Hello', 'Jython', 'Lists']

# Return every other element in a list
>>> new_list=[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# Using a third parameter in the slice will cause a stepping action to take place

# In this example we step by one
>>> new_list[0:10:1]
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

# And here we step by two
>>> new_list[0:10:2]
[2, 6, 10, 14, 18]

# Leaving a positional index blank will also work as the default is 0 for the start,
and the length of the string for the end.
>>> new_list[::2]
[2, 6, 10, 14, 18]

修改列表的方式与访问类似,您可以使用索引来插入或删除特定位置的项目。还有许多其他方法可以插入或删除列表中的元素。Python 提供了所有这些不同的选项,因为它们为您的操作提供了不同的功能。

清单 2-9.:

# Modify an element in a list.  In this case we'll modify the element in the 9th
position
>>> new_list[9] = 25
>>> new_list
[2, 4, 6, 8, 10, 12, 14, 16, 18, 25]

您可以使用 append() 方法将项目添加到列表的末尾。extend() 方法允许您将整个列表或序列的副本添加到列表的末尾。最后,insert() 方法允许您使用位置索引将项目或另一个列表放置到现有列表的特定位置。如果将另一个列表插入到现有列表中,则不会将其与原始列表合并,而是作为包含在原始列表中的单独项目。

同样,我们有很多选项可以从列表中删除项目。del 语句(如第 1 章所述)可用于使用索引符号删除整个列表或列表中的值。您还可以使用 pop() 或 remove() 方法从列表中删除单个值。pop() 方法将从列表的末尾删除单个值,并且还会同时返回该值。如果向 pop() 函数提供索引,则它将删除并返回该索引处的 value。remove() 方法可用于查找和删除列表中的特定值。换句话说,remove() 将删除列表中的第一个匹配元素。如果列表中的多个值与传递给 remove() 函数的值匹配,则将删除第一个值。关于 remove() 函数的另一个注意事项是,删除的值不会被返回。让我们看一下这些修改列表的示例。

清单 2-10. 修改列表:

# Adding values to a list using the append method
>>> new_list=['a','b','c','d','e','f','g']
>>> new_list.append('h')
>>> print new_list
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

# Add another list to the existing list
>>> new_list2=['h','i','j','k','l','m','n','o','p']
>>> new_list.extend(new_list2)
>>> print new_list
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h','h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']

# Insert a value into a particular location via the index.
# In this example, we add a 'c' into the third position in the list
# (Remember that list indicies start with 0, so the second index is actually the third
# position)
>>> new_list.insert(2,'c')
>>> print new_list
['a', 'b', 'c', 'c', 'd', 'e', 'f', 'g', 'h', 'h','i', 'j', 'k', 'l', 'm', 'n', 'o',
'p']

# Insert a list into a particular postion via the index
>>> another_list = ['a', 'b', 'c']
>>> another_list.insert(2, new_list)
>>> another_list
['a', 'b', [2, 4, 8, 10, 12, 14, 16, 18, 25], 'c']

# Use the slice notation to overwrite part of a list or sequence
>>> new_listA=[100,200,300,400]
>>> new_listB=[500,600,700,800]
>>> new_listA[0:2]=new_listB
>>> print new_listA
[500, 600, 700, 800, 300, 400]

# Assign a list to another list using the empty slice notation
>>> one = ['a', 'b', 'c', 'd']
>>> two = ['e', 'f']
>>> one
['a', 'b', 'c', 'd']
>>> two
['e', 'f']

# Obtain an empty slice from a list by using the same start and end position.

# Any start and end position will work, as long as they are the same number.
>>> one[2:2]
[]

# In itself, this is not very interesting – you could have made an empty list

# very easily. The useful thing about this is that you can assign to this empty slice

# Now, assign the 'two' list to an empty slice for the 'one' list which essentially

# inserts the 'two' list into the 'one' list
>>> one[2:2] = two
# the empty list between elements 1 and 2 of list 'one' is replaced by the list 'two'
>>> one
['a', 'b', 'c', 'd', 'e', 'f']

# Use the del statement to remove a value or range of values from a list
# Note that all other elements are shifted to fill the empty space
>>> new_list3=['a','b','c','d','e','f']
>>> del new_list3[2]
>>> new_list3
['a', 'b', 'd', 'e', 'f']
>>> del new_list3[1:3]
>>> new_list3
['a', 'e', 'f']

# Use the del statement to delete a list
>>> new_list3=[1,2,3,4,5]
>>> print new_list3
[1, 2, 3, 4, 5]
>>> del new_list3
>>> print new_list3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'new_list3' is not defined

# Remove values from a list using pop and remove functions
>>> print new_list
['a', 'b', 'c', 'c', 'd', 'e', 'f', 'g', 'h','h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']

# pop the element at index 2
>>> new_list.pop(2)
'c'
>>> print new_list
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h','h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']

# Remove the first occurrence of the letter 'h' from the list
>>> new_list.remove('h')
>>> print new_list
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']

# Useful example of using pop() function
>>> x = 5
>>> times_list = [1,2,3,4,5]
>>> while times_list:
...     print x * times_list.pop(0)
...
5
10
15
20
25

现在我们已经知道如何向列表添加和删除项目,是时候学习如何操作列表中的数据了。Python 提供了许多不同的方法,可以帮助我们管理列表。请参见表 2-4,了解这些函数的列表及其功能。

表 2-4. Python 列表方法

方法 执行的任务
index 返回列表中第一个与给定值匹配的值的索引。
count 返回列表中等于给定值的项目的数量。
sort 对列表中包含的项目进行排序,并返回列表
reverse 反转列表中包含的项目的顺序,并返回列表

让我们来看一些关于如何在列表上使用这些函数的示例。

清单 2-11. 利用列表函数:

# Returning the index for any given value
>>> new_list=[1,2,3,4,5,6,7,8,9,10]
>>> new_list.index(4)
3

#  Change the value of the element at index 4
>>> new_list[4] = 30
>>> new_list
[1, 2, 3, 4, 30, 6, 7, 8, 9, 10]

# Ok, let's change it back
>>> new_list[4] = 5
>>> new_list
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Add a duplicate value into the list and then return the index

# Note that index returns the index of the first matching value it encounters
>>> new_list.append(6)
>>> new_list
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 6]
>>> new_list.index(6)
5

# Using count() function to return the number of items which  equal a given value
>>> new_list.count(2)
1
>>> new_list.count(6)
2

# Sort the values in the list
>>> new_list.sort()
>>> new_list
[1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10]

# Reverse the order of the value in the list
>>> new_list.reverse()
>>> new_list
[10, 9, 8, 7, 6, 6, 5, 4, 3, 2, 1]

遍历和搜索列表

在列表中移动非常简单。一旦填充了列表,我们通常希望遍历它并对其中包含的每个元素执行某些操作。您可以使用任何 Python 循环结构来遍历列表中的每个元素。虽然有很多选项可用,但 for 循环特别有效。这是因为 Python for 循环使用的语法很简单。本节将向您展示如何使用不同的 Python 循环结构来遍历列表。您将看到它们各自的优缺点。

让我们首先看一下使用 for 循环遍历列表的语法。这是迄今为止遍历列表中包含的每个值的 easiest 模式之一。for 循环一次遍历一个元素,允许开发人员在需要时对每个元素执行某些操作。

清单 2-12. 使用“for”循环遍历列表:

>>> ourList=[1,2,3,4,5,6,7,8,9,10]
>>> for elem in ourList:
...    print elem
...
1
2
3
4
5
6
7
8
9
10

从这个简单的示例中可以看出,遍历列表并分别处理每个项目非常容易。for 循环语法需要一个变量,该变量将在每次循环传递时分配给列表中的每个元素。

也可以将切片与 for 循环结合使用。在这种情况下,我们将简单地使用列表切片来检索我们想要查看的确切元素。例如,请查看以下代码,它遍历我们列表中的前 5 个元素。

清单 2-13.:

>>> for elem in ourList[:5]:
...     print elem
...
1
2
3
4
5

如您所见,通过简单地利用 Python 提供的内置功能,这很容易做到。

列表推导式

正如我们在上一节中所见,我们可以使用切片来创建列表的副本。另一种更强大的方法是使用列表推导。列表有一些高级功能可以帮助开发人员简化工作。其中一项功能被称为列表推导。虽然这个概念乍一看可能令人生畏,但它提供了一个很好的替代方案,可以手动创建许多单独的列表。列表推导接受一个给定的列表,然后遍历它并对列表中的每个对象应用给定的表达式。

清单 2-14. 简单列表推导:

# Multiply each number in a list by 2 using a list comprehension

# Note that list comprehension returns a new list
>>> num_list = [1, 2, 3, 4]
>>> [num * 2 for num in num_list]
[2, 4, 6, 8]

# We could assign a list comprehension to a variable
>>> num_list2 = [num * 2 for num in num_list]
>>> num_list2
[2, 4, 6, 8]

如您所见,这允许人们快速获取一个列表并通过使用提供的表达式对其进行更改。当然,与许多其他 Python 方法一样,列表推导返回列表的修改副本。列表推导会生成一个新列表,而原始列表保持不变。

让我们看一下列表推导的语法。它们基本上由某种表达式、一个 for 语句以及可选的更多 for 或 if 语句组成。列表推导的基本功能是遍历列表的项目,然后对列表的每个成员应用一些表达式。从语法上讲,列表推导的写法如下

遍历列表,并可选地对每个元素执行表达式,然后返回包含结果元素的新列表,或者根据可选子句评估每个元素。

[list-element (optional expression) for list-element in list (optional clause)]

清单 2-15. 在列表推导中使用 If 子句:

# The following example returns each element
# in the list that is greater than the number 4
>>> nums = [2, 4, 6, 8]
>>> [num for num in nums if num > 4]
[6, 8]

让我们看一些更具体的例子。一旦您看到列表推导的实际应用,您一定会理解它们并看到它们有多么有用。

清单 2-16. Python 列表推导:

# Create a list of ages and add one to each of those ages using a list comprehension
>>> ages=[20,25,28,30]
>>> [age+1 for age in ages]
[21, 26, 29, 31]

# Create a list of names and convert the first letter of each name to uppercase as it should be
>>> names=['jim','frank','vic','leo','josh']
>>> [name.title() for name in names]
['Jim', 'Frank', 'Vic', 'Leo', 'Josh']

# Create a list of numbers and return the square of each EVEN number
>>> numList=[1,2,3,4,5,6,7,8,9,10,11,12]
>>> [num*num for num in numList if num % 2 == 0]
[4, 16, 36, 64, 100, 144]

# Use a list comprehension with a range
>>> [x*5 for x in range(1,20)]
[5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]

# Use a for clause to perform calculations against elements of two different lists
>>> list1 = [5, 10, 15]
>>> list2 = [2, 4, 6]
>>> [e1 + e2 for e1 in list1 for e2 in list2]
[7, 9, 11, 12, 14, 16, 17, 19, 21]

列表推导可以使代码更加简洁,并允许人们非常轻松地将表达式或函数应用于列表元素。让我们快速看一下用 Java 编写的执行与列表推导相同类型工作的示例。很明显,列表推导更加简洁。

清单 2-17. Java 代码用于获取年龄列表并将每个年龄增加一年:

int[] ages = {20, 25, 28, 30};
int[] ages2 = new int[ages.length];

// Use a  Java for loop to go through each element in the array
for (int x = 0; x <= ages.length; x++) {
    ages2[x] = ages[x] + 1;
}

元组

元组很像列表;但是,它们是不可变的。一旦定义了元组,就不能更改它。它们包含与列表相同的索引,但同样,它们在定义后不能更改。因此,元组中的索引可用于检索特定值,而不是用于分配或修改。虽然元组可能看起来类似于列表,但它们有很大不同,因为元组通常包含异构元素,而列表通常包含以某种方式相关的元素。例如,元组的常见用例是将参数传递给函数、方法等。

由于元组是序列类型的一个成员,因此它们可以使用所有序列类型可用的相同方法和操作。

清单 2-18. 元组示例:

# Creating an empty tuple
>>> myTuple = ()
# Creating tuples and using them
>>> myTuple2 = (1, 'two',3, 'four')
>>> myTuple2
(1, 'two', 3, 'four')

# To create a single-item tuple, include a trailing comma
>>> myteam = 'Bears',
>>> myteam
('Bears',)

如前所述,元组对于传递给函数、方法、类等非常有用。通常,拥有一个不可变对象来传递多个值会很好。一种这样的情况是使用元组在地理信息系统或其他类似应用程序中传递坐标。它们在需要不可变对象的情况下也很有用。由于它们是不可变的,因此它们的大小在定义后不会增长,因此元组在内存分配是一个问题时也起着重要作用。

字典

Python 字典是一个键值存储容器。字典与 Python 中的典型列表有很大不同,因为字典中没有为任何给定元素自动填充索引。当您使用列表时,您无需担心为放置在其中的任何值分配索引。字典允许开发人员为放置在构造中的每个元素分配索引或“键”。因此,字典中的每个条目都需要两个值,即键和元素。

字典的妙处在于它允许开发人员选择键值的类型。因此,如果有人希望使用字符串或任何其他可散列对象(如 int 或 float 值)作为键,那么这完全有可能。字典还具有多种方法和操作,可以应用于它们以使其更易于使用。表 2-5 列出了字典方法和函数。

清单 2-19. 基本字典示例:

# Create an empty dictionary and a populated dictionary
>>> myDict={}
>>> myDict.values()
[]

# Assign key-value pairs to dictionary
>>> myDict['one'] = 'first'
>>> myDict['two'] = 'second'
>>> myDict
{'two': 'second', 'one': 'first'}

表 2-5. 字典方法和函数

方法或函数 描述
len(dictionary) 返回给定字典中项目数量的函数。
dictionary [key] 返回字典中与给定键关联的项目。
dictionary[key] = value 将字典中关联的项目设置为给定值。
del dictionary[key] 从字典中删除给定的键值对。
dictionary.clear() 从字典中删除所有项目的方法。
dictionary.copy() 创建字典的浅拷贝的方法。
has_key(key) 返回一个布尔值,表示字典是否包含给定的键。(已弃用,建议使用 in)
key in d 返回一个布尔值,表示给定的键是否在字典中找到
key not in d 返回一个布尔值,表示给定的键是否未在字典中找到
items() 返回一个元组列表,包括字典中键值对的副本。
keys() 返回字典中键的列表。
update([dictionary2]) 使用给定字典中的键值对更新字典。现有键将被覆盖。
fromkeys(sequence[,value]) 使用给定序列中的键创建一个新字典。值将设置为给定的值。
values() 将字典中的值作为列表返回。
get(key[, b]) 返回与给定键关联的值。如果键不存在,则返回 b。
setdefault(key[, b]) 返回与给定键关联的值。如果键不存在,则将键值设置为 b (mydict[key] = b)
pop(key[, b]) 返回并删除与给定键关联的键值对。如果键不存在,则返回 b。
popItem() 从字典中弹出一个任意的键值对
iteritems() 返回字典中键值对的迭代器。
iterkeys() 返回字典中键的迭代器。
itervalues() 返回字典中值的迭代器。

现在我们将看看一些字典示例。本参考不会向您展示使用每个字典方法和函数的示例,但它应该为您提供一个足够好的基础理解,了解它们是如何工作的。

清单 2-20. 使用 Python 字典:

# Create an empty dictionary and a populated dictionary
>>> mydict = {}
# Try to find a key in the dictionary
>>> 'firstkey' in mydict
False

# Add key/value pair to dictionary
>>> mydict['firstkey'] = 'firstval'
>>> 'firstkey' in mydict
True

# List the values in the dictionary
>>> mydict.values()
['firstval']

# List the keys in the dictionary
>>> mydict.keys()
['firstkey']

# Display the length of the dictionary (how many  key/value pairs are in it)
>>> len(mydict)
1

# Print the contents of the dictionary
>>> mydict
{'firstkey': 'firstval'}
>>>
# Replace the original dictionary with a dictionary containing string-based keys

# The following dictionary represents a hockey team line
>>> myDict =
{'r_wing':'Josh','l_wing':'Frank','center':'Jim','l_defense':'Leo','r_defense':'Vic'}
>>> myDict.values()
['Josh', 'Vic', 'Jim', 'Frank', 'Leo']
>>> myDict.get('r_wing')
'Josh'
>>> myDict['r_wing']
'Josh'

# Try to obtain the value for a key that does not exist
>>> myDict['goalie']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'goalie'

# Try to obtain a value for a key that does not exist using get()
>>> myDict.get('goalie')
# Now use a default message that will be displayed if the key does not exist
>>> myDict.get('goalie','Invalid Position')
'Invalid Position'

# Iterate over the items in the dictionary
>>> for player in myDict.iterItems():
...     print player
...
('r_wing', 'Josh')
('r_defense', 'Vic')
('center', 'Jim')
('l_wing', 'Frank')
('l_defense', 'Leo')

# Assign keys and values to separate objects and then print
>>> for key,value in myDict.iteritems():
...     print key, value
...
r_wing Josh
r_defense Vic
center Jim
l_wing Frank
l_defense Leo

集合

集合是无序的唯一元素集合。使集合不同于其他序列类型的是它们不包含索引或重复项。它们也不像字典,因为没有与元素关联的键值。它们是唯一元素的任意集合。集合不能包含可变对象,但集合本身可以是可变的。需要注意的是,集合默认情况下不可用,您必须从 Sets 模块导入 set 才能使用。

清单 2-21. 集合示例:

# In order to use a Set, we must first import it
>>> from sets import Set
# To create a set use the following syntax
>>> myset = Set([1,2,3,4,5])
>>> myset
Set([5, 3, 2, 1, 4])

# Add a value to the set – See Table 2-7 for more details
>>> myset.add(6)
>>> myset
Set([6, 5, 3, 2, 1, 4])

# Try to add a duplicate
>>> myset.add(4)
>>> myset
Set([6, 5, 3, 2, 1, 4])

有两种类型的集合,即 set 和 frozenset。两者之间的区别从名称本身就可以很容易地看出。常规集合是可变的集合对象,而冻结集合是不可变的。请记住,不可变对象一旦创建就不能更改,而可变对象可以在创建后更改。与序列和映射类型一样,集合也有一系列方法和操作可以应用于它们。许多操作和方法都适用于可变和不可变集合。但是,其中一些方法仅适用于可变集合类型。在表 2-6 和 2-7 中,我们将看看不同的方法和操作。

表 2-6. 集合类型方法和函数

方法或操作 描述
len(set) 返回给定集合中元素的数量
copy() 返回集合的新浅拷贝
difference(set2) 返回一个新集合,其中包含调用集合中存在但 set2 中不存在的所有元素
intersection(set2) 返回一个新集合,其中包含调用集合和 set2 共有的所有元素
issubbset(set2) 返回一个布尔值,表示调用集合中的所有元素是否也在 set2 中
issuperset(set2) 返回一个布尔值,表示 set2 中的所有元素是否都包含在调用集合中。
symmetric_difference(set2) 返回一个新的集合,包含调用集合或 set2 中的元素,但不包含两者共有的元素 (set1 ^ set2)。
x in set 测试 x 是否包含在集合中,返回布尔值。
x not in set 测试 x 是否不包含在集合中,返回布尔值。
union(set2) 返回一个新的集合,包含调用集合和 set2 中都包含的元素。

清单 2-22. 使用集合类型方法和函数:

# Create two sets
>>> s1 = Set(['jython','cpython','ironpython'])
>>> s2 = Set(['jython','ironpython','pypy'])
# Make a copy of a set
>>> s3 = s1.copy()
>>> s3
Set(['cpython', 'jython', 'ironpython'])

# Obtain a new set containing all elements that are in s1 but not s2
>>> s1.difference(s2)
Set(['cpython'])

# Obtain a new set containing all elements from each set
>>> s1.union(s2)
Set(['cpython', 'pypy', 'jython', 'ironpython'])

# Obtain a new set containing elements from either set that are not contained in both
>>> s1.symmetric_difference(s2)
Set(['cpython', 'pypy'])

表 2-7. 可变集合类型方法

方法或操作 描述
add(item) 如果项目不在集合中,则将项目添加到集合中。
clear() 删除集合中的所有项目。
difference_update(set2) 返回删除了 set2 中包含的所有元素的集合。
discard(element) 如果存在,则从集合中删除指定的元素。
intersection_update(set2) 返回仅保留也存在于 set2 中的元素的集合。
pop() 从集合中返回一个任意元素。
remove(element) 如果存在,则从集合中删除元素;如果不存在,则引发 KeyError。
symmetric_difference_update(set2) 用包含调用集合或 set2 中的元素(但不包含两者共有的元素)的集合替换调用集合,并返回该集合。
update(set2) 返回包含 set2 中所有元素的集合。

清单 2-23. 更多使用集合:

# Create three sets
>>> s1 = Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
>>> s2 = Set([5, 10, 15, 20])
>>> s3 = Set([2, 4, 6, 8, 10])
# Remove arbitrary element from s2
>>> s2.pop()
20
>>> s2
Set([5, 15, 10])

# Discard the element that equals 3 from s1 (if exists)
>>> s1.discard(3)
>>> s1
Set([6, 5, 7, 8, 2, 9, 10, 1, 4])

# Update s1 to include only those elements contained in both s1 and s2
>>> s1.intersection_update(s2)
>>> s1
Set([5, 10])
>>> s2
Set([5, 15, 10])

# Remove all elements in s2
>>> s2.clear()
>>> s2
Set([])

# Updates set s1 to include all elements in s3
>>> s1.update(s3)
>>> s1
Set([6, 5, 8, 2, 10, 4])

范围

范围是一个特殊的函数,允许您在数字范围内迭代或列出特定范围的数字。它在执行数学迭代时特别有用,但也可以用于简单的迭代。

使用范围函数的格式包括一个可选的起始数字、一个结束数字和一个可选的步长数字。如果指定,起始数字告诉范围从哪里开始,而结束数字指定范围应该在哪里结束。起始索引是包含的,而结束索引是不包含的。可选的步长数字告诉范围在范围输出中包含的每个数字之间应该放置多少个数字。步长数字加到前一个数字,如果该数字超过了终点,则范围停止。

范围格式

range([start], stop, [step])

清单 2-24. 使用范围函数:

#Simple range starting with zero, note that the end point is not included in the
range
>>>range(0,10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(50, 65)
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64]
>>>range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Include a step of two in the range
>>>range(0,10,2)
[0, 2, 4, 6, 8]

# Including a negative step performs the same functionality...the step is added to
the previously

# number in the range
>>> range(100,0,-10)
[100, 90, 80, 70, 60, 50, 40, 30, 20, 10]

此函数最常见的用途之一是在 for 循环中。以下示例显示了在 for 循环上下文中使用范围函数的几种方法。

清单 2-25. 在 for 循环中使用范围函数:

>>> for i in range(10):
...     print i
...
0
1
2
3
4
5
6
7
8
9

# Multiplication Example
>>> x = 1
>>> for i in range(2, 10, 2):
...     x = x + (i * x)
...     print x
...
3
15
105
945

如您所见,范围可用于迭代几乎任何数字集,无论是向上还是向下,正数还是负数。范围也是创建数字列表的好方法。为此,只需将范围传递给 list(),如以下示例所示。

清单 2-26. 从范围创建列表:

>>> my_number_list = list(range(10))
>>> my_number_list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

如您所见,范围不仅对迭代目的有用,而且也是创建数字列表的好方法。

Jython 特定的集合

有一些 Jython 特定的集合对象可供使用。大多数这些集合对象用于将数据传递到 Java 类等等,但它们在 Jython 实现中添加了额外的功能,这将帮助来自 Java 世界的 Python 新手。尽管如此,在某些情况下,许多这些额外的集合对象非常有用。

在 Jython 2.2 版本中,引入了 Java 集合集成。这使得 Jython 和 Java 集合类型之间可以进行双向交互。例如,可以将 Java ArrayList 导入 Jython,然后像使用语言本身的一部分一样使用它。在 2.2 之前,Java 集合对象可以充当 Jython 对象,但 Jython 对象不能充当 Java 对象。例如,可以在 Jython 中使用 Java ArrayList 并使用 add()、remove() 和 get() 等方法。您将在下面的示例中看到,使用 ArrayList 的 add() 方法将向列表添加一个元素,并返回一个布尔值以表示添加的成功或失败。remove() 方法的作用类似,只是它删除元素而不是添加元素。

清单 2-27. 在 Jython 中使用面向 Java 的集合的示例:

# Import and use a Java ArrayList
>>> import java.util.ArrayList as ArrayList
>>> arr = ArrayList()
#  Add method will add an element to the list and return a boolean to signify
successsful addition
>>> arr.add(1)
True
>>> arr.add(2)
True
>>> print arr
[1, 2]

在集成 Java 集合之前,Jython 也实现了 jarray 对象,它基本上允许在 Jython 中构建 Java 数组。为了使用 jarray,只需在 Jython 中定义一个序列类型,并将其与序列中包含的对象类型一起传递给 jarray 对象。jarray 对于创建 Java 数组然后将它们传递给 Java 对象非常有用,但对于在 Jython 对象中工作却不太有用。此外,jarray 中的所有值必须是相同类型。如果尝试将包含多种类型的序列传递给 jarray,则会收到某种类型的 TypeError。有关与 jarray 一起使用的字符类型代码列表,请参见表 2-8。

表 2-8. 与 Jarray 一起使用的字符类型代码

字符 Java 等效项
z boolean
b byte
c char
d Double
f Float
h Short
i Int
l Long

清单 2-28. Jarray 用法:

>>> my_seq = (1,2,3,4,5)
>>> from jarray import array
>>> array(my_seq,'i')
array('i', [1, 2, 3, 4, 5])
>>> myStr = "Hello Jython"
>>> array(myStr,'c')
array('c', 'Hello Jython')

jarray 的另一个有用功能是,我们可以使用 zeros() 方法创建空数组。zeros() 方法的工作方式类似于我们已经演示过的 array() 方法。为了创建一个空数组,只需将数组的长度以及类型传递给 zeros() 方法。让我们快速看一下示例。

清单 2-29. 创建空布尔数组:

>>> arr = zeros(10,'z')
>>> arr
array('z', [False, False, False, False, False, False, False, False, False, False])

清单 2-30. 创建空整数数组:

>>> arr2 = zeros(6, 'i')
>>> arr2
array('i', [0, 0, 0, 0, 0, 0])

在某些情况下,在使用 Java 对象时,您需要调用需要 Java 数组作为参数的 Java 方法。使用 jarray 对象允许在需要时简单地创建 Java 数组。

如果需要,可以使用 jarray 构建任意 Java 类的 Java 数组。

>>> from java.lang import StackTraceElement
>>> array([],StackTraceElement)
array(java.lang.StackTraceElement)

由于 Java 中的数组始终与关联的类相关联,因此这也允许使用二维或更高维数组。

>>> from java.lang import Class
>>> intArrayClass = Class.forName('[I') # Name of an array of Java int
>>> a = array([[1,2,3],[6,7,8,9]],intArrayClass)
>>> a[1][3]
9

原始数组的名称可以在 Java 文档中找到,用于 Class.getName() 方法。

文件

文件对象用于读取和写入磁盘上的文件数据。文件对象用于获取对磁盘上文件的引用,并将其打开以进行读取、写入、追加或许多不同的任务。如果我们只使用 open(filename[, mode]) 函数,我们可以返回一个文件对象并将其分配给一个变量以进行处理。如果文件尚不存在于磁盘上,则会自动创建它。mode 参数用于告诉我们希望对文件执行哪种类型的处理。此参数是可选的,如果省略,则文件以只读模式打开。参见表 2-9。

表 2-9. 文件类型的操作模式

模式 描述
‘r’ 只读
‘w’ 写入(注意:这会覆盖文件中的任何其他内容,因此请谨慎使用)
‘a’ 追加
‘r+’ 读写
‘rb’ 二进制文件读取
‘wb’ 二进制文件写入
‘r+b’ 二进制文件读写

清单 2-31.:

# Open a file and assign it to variable f
>>> f = open('newfile.txt','w')

有很多方法可以在文件对象上使用,用于操作文件内容。我们可以对文件调用 read([size]) 以读取其内容。Size 是这里的一个可选参数,它用于告诉从文件中读取多少内容。如果省略,则读取整个文件内容。readline() 方法可用于从文件中读取一行。readlines([size]) 用于返回一个列表,其中包含文件中包含的所有数据行。同样,有一个可选的 size 参数可用于告诉从文件中读取多少字节。如果我们希望将内容放入文件中,write(string) 方法就是这样做的。write() 方法将字符串写入文件。

写入文件时,通常需要知道要写入文件的准确位置。有一组方法可以帮助我们使用整数来表示文件中的字节,从而在文件中定位。可以对文件调用 tell() 方法以获取文件对象的当前位置。返回的整数是字节数,是从文件开头开始的偏移量。seek(offset, from) 方法可用于更改文件中的位置。offset 是您要转到的位置的字节数,from 表示您要从中计算偏移量的文件位置。如果 from 等于 0,则偏移量将从文件开头计算。同样,如果它等于 1,则它将从当前文件位置计算,而 2 将从文件末尾计算。如果省略 from,则默认值为 0。

最后,在我们的程序中有效地分配和释放资源非常重要,否则我们会遇到内存开销和泄漏。由于垃圾回收机制的不同,CPython 和 Jython 之间资源的处理方式略有不同。在 CPython 中,不必过分担心释放资源,因为它们在超出作用域时会自动释放。JVM 不会立即进行垃圾回收,因此正确释放资源更为重要。当我们完成对文件的操作时,应该调用 close() 方法。使用文件的正确方法是在每次操作时都打开、处理,然后关闭。但是,还有更有效的方法来执行此类任务。在第 7 章中,我们将讨论使用上下文管理器以更有效的方式执行相同功能。

清单 2-32. Python 中的文件操作:

# Create a file, write to it, and then read its content
>>> f = open('newfile.txt','r+')
>>> f.write('This is some new text for our file\n')
>>> f.write('This should be another line in our file\n')
#  No lines will be read because we are at the end of the written content
>>> f.read()
''
>>> f.readlines()
[]
>>> f.tell()
75L

# Move our position back to the beginning of the file
>>> f.seek(0)
>>> f.read()
'This is some new text for our file\nThis should be another line in our file\n'
>>> f.seek(0)
>>> f.readlines()
['This is some new text for our file\n', 'This should be another line in our file\n']

# Closing the file to de-allocate
>>> f.close()

迭代器

迭代器是在 Python 2.2 版本中引入的。它允许对 Python 容器进行迭代。所有可迭代容器都内置支持迭代器类型。例如,序列对象是可迭代的,因为它们允许对序列中的每个元素进行迭代。如果你尝试在一个不支持迭代的对象上返回迭代器,你很可能会收到一个 AttributeError,它会告诉你 __iter__ 尚未定义为该对象的属性。需要注意的是,使用双下划线的 Python 方法名称是特殊方法。例如,在 Python 中,可以使用 __init__() 方法初始化类……就像 Java 构造函数一样。有关类和特殊类方法的更多详细信息,请参阅第 7 章。

迭代器允许轻松访问序列和其他可迭代容器。正如你在前面的部分中所见,一些容器(如字典)内置了专门的迭代方法。迭代器对象需要支持构成迭代器协议的两个主要方法。这些方法在表 2-10 中定义。要返回容器上的迭代器,只需将 container.__iter__() 赋值给某个变量。该变量将成为该对象的迭代器。这使得人们能够将迭代器传递给函数等。然后,迭代器本身就像一个保持其状态的不断变化的变量。我们可以使用迭代器而不影响原始对象。如果使用 next() 调用,它将继续返回列表中的下一个项目,直到所有项目都被检索。一旦发生这种情况,就会发出 StopIteration 异常。这里需要注意的重要一点是,当我们返回迭代器并将其分配给变量时,实际上是在创建列表的副本。该变量每次在它上面调用 next() 方法时都会返回并从该副本中删除一个项目。如果我们继续在迭代器变量上调用 next(),直到发出 StopIteration 错误,该变量将不再包含任何项目,并且为空。例如,如果我们从列表中创建了一个迭代器,然后在它上面调用 next() 方法,直到它检索到所有值,那么迭代器将为空,而原始列表将保持不变。

清单 2-33. 从列表创建迭代器并使用它:

>>> hockey_roster = ['Josh', 'Leo', 'Frank', 'Jim', 'Vic']
>>> hockey_itr = hockey_roster.__iter__()
>>> hockey_itr = hockey_roster.__iter__()
>>> hockey_itr.next()
'Josh'
>>> for x in hockey_itr:
...     print x
...
Leo
Frank
Jim
Vic

# Try to call next() on iterator after it has already used all of its elements
>>> hockey_itr.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

清单 2-34. 对序列和列表进行迭代:

# Iterate over a string and a list
>>> str_a = 'Hello'
>>> list_b = ['Hello','World']
>>> for x in str_a:
...     print x
...
H
e
l
l
o
>>> for y in list_b:
...     print y + '!'
...
Hello!
World!

引用和副本

在 Python 语言中创建副本和引用项目非常简单。你只需要记住,用于复制可变对象和不可变对象的技巧略有不同。

为了创建不可变对象的副本,您只需将其分配给不同的变量。新变量是该对象的精确副本。如果您尝试对可变对象执行相同的操作,实际上您只会创建一个指向原始对象的引用。因此,如果您对原始对象的“副本”执行操作,则实际上会对原始对象执行相同的操作。这是因为新的赋值引用内存中与原始对象相同的可变对象。这就像有人用不同的名字称呼你一样。一个人可能用你的本名称呼你,另一个人可能用你的昵称称呼你,但这两个名字当然都会指代你。

清单 2-35. 使用副本:

# Strings are immutable, so when you assign a string to another variable, it creates
a real copy
>>> mystring = "I am a string, and I am an immutable object"
>>> my_copy = mystring
>>> my_copy
'I am a string, and I am an immutable object'
>>> mystring
'I am a string, and I am an immutable object'
>>> my_copy = "Changing the copy of mystring"
>>> my_copy
'Changing the copy of mystring'
>>> mystring
'I am a string, and I am an immutable object'

# Lists are mutable objects, so assigning a list to a variable

# creates a reference to that list. Changing one of these variables will also

# change the other one – they are just references to the same object.
>>> listA = [1,2,3,4,5,6]
>>> print listA
[1, 2, 3, 4, 5, 6]
>>> listB = listA
>>> print listB
[1, 2, 3, 4, 5, 6]
>>> del listB[2]
# Oops, we've altered the original list!
>>> print listA
[1, 2, 4, 5, 6]

# If you want a new list which contains the same things, but isn't just a reference

# to your original list, you need the copy module
>>> import copy
>>> a = [[]]
>>> b = copy.copy(a)
>>> b
[[]]

# b is not the same list as a, just a copy
>>> b is a
False

# But the list b[0] is the same the same list as the list a[0], and changing one will

# also change the other. This is what is known as a shallow copy – a and b are

# different at the top level, but if you go one level down, you have references to

# to the same things – if you go deep enough, it's not a copy,

# it's the same object.
>>> b[0].append('test')
>>> a
[['test']]
>>> b
[['test']]

要有效地创建可变对象的副本,您有两个选择。您可以创建所谓的原始对象的浅层副本或深层副本。区别在于,对象的浅层副本将创建一个新对象,然后用指向原始对象中包含的项目的引用填充它。因此,如果您修改了任何这些项目,那么每个对象都会受到影响,因为它们都引用了相同的项目。

深层副本创建一个新对象,然后递归地将原始对象的内容复制到新副本中。执行对象的深层副本后,您可以对副本中包含的任何对象执行操作,而不会影响原始对象。您可以使用 Python 标准库的 copy 模块中的 deepcopy 函数来创建这样的副本。让我们看一些创建副本的更多示例,以便让您更好地了解它是如何工作的。

清单 2-36.:

# Create an integer variable, copy it, and modify the copy
>>> a = 5
>>> b = a
>>> print b
5
>>> b = a * 5
>>> b
25
>>> a
5

# Create a deep copy of the list and modify it
>>> import copy
>>> listA = [1,2,3,4,5,6]
>>> listB = copy.deepcopy(listA)
>>> print listB
[1, 2, 3, 4, 5, 6]
>>> del listB[2]
>>> print listB
[1, 2, 4, 5, 6]
>>> print listA
[1, 2, 3, 4, 5, 6]

垃圾回收

这是 CPython 和 Jython 之间的主要区别之一。在 CPython 中,当对象超出范围或不再需要时,它会被垃圾回收。这会自动发生,很少需要开发人员跟踪。在幕后,CPython 使用引用计数技术来维护每个对象的计数,这有效地确定了对象是否仍在使用中。与 CPython 不同,Jython 不会为过时或垃圾回收未使用的对象实现引用计数技术。相反,Jython 利用 Java 平台提供的垃圾回收机制。当 Jython 对象变得陈旧或无法访问时,JVM 可能会或可能不会回收它。JVM 的主要方面之一是,在早期让开发人员如此高兴的是,他们不再需要担心清理代码后的工作。在 C 编程语言中,必须了解哪些对象当前正在使用,以便在不再需要它们时,程序会执行一些清理工作。在 Java 世界中并非如此,JVM 上的 gc 线程会为您处理所有垃圾回收和清理工作。

即使我们还没有详细讨论类,您在第 1 章中也看到了一个简短的示例。现在是时候提到 Python 提供了一种对象清理机制。可以在任何类中定义一个终结器方法,以确保垃圾收集器执行特定任务。任何需要在对象超出范围时执行的清理代码都可以放在此终结器方法中。重要的是要注意,终结器方法不能被视为始终在对象陈旧时调用的方法。这是因为终结器方法是由 Java 垃圾回收线程调用的,无法确定何时以及是否会对对象调用垃圾收集器。关于终结器的另一个需要注意的问题是它们会造成性能损失。如果您正在编写一个已经执行效果不佳的应用程序,那么在其中添加大量终结器可能不是一个好主意。

以下是一个 Python 终结器的示例。它是一个必须命名为 __del__ 的实例方法。

清单 2-37. Python 终结器示例:

class MyClass:
    def __del__(self):
        pass    # Perform some cleanup here

使用 JVM 垃圾回收机制的缺点是,无法真正保证对象何时以及是否会被回收。因此,在处理性能密集型对象时,最好不要依赖于终结器被调用。在处理文件和数据库等对象时,始终要确保使用正确的编码技术。切勿将文件的 close() 方法编码到终结器中,因为如果终结器没有被调用,可能会导致问题。最佳实践是确保在调用终结器之前执行所有必要的清理活动。

摘要

本章涵盖了大量内容。阅读完这些内容后,你应该对 Python 有了更深入的了解。我们从介绍赋值和将数据分配给特定对象或数据类型开始。你了解到,使用每种类型的数据对象会打开不同的门,因为我们处理每种类型的数据对象的方式不同。我们从数字和字符串开始探索数据对象,并讨论了字符串对象可用的多种方法。我们了解到,字符串与列表和元组一起属于 Python 集合对象的序列家族。我们介绍了如何创建和使用列表,以及使用列表时可用的各种选项。你发现列表推导可以帮助创建给定列表的副本,并根据表达式或函数操作其元素。在讨论完列表之后,我们继续讨论字典、集合和元组。

在讨论完集合类型之后,我们了解到 Jython 拥有自己的一套与 Python 中不同的集合对象。我们可以利用 Java 平台的优势,在 Jython 中使用 Java 集合类型。最后,我们讨论了引用、副本和垃圾回收。创建对象的不同副本并不总是能得到你想要的结果,而且 Jython 的垃圾回收与 Python 的垃圾回收有很大不同。

下一章将帮助你将本章中学习的一些主题结合起来,因为你将学习如何定义表达式和使用控制流。