您当前位置:首页 深入 Python 3

难度等级:♦♦♢♢♢

原生数据类型

奇迹是所有哲学的基石,探索是其进程,无知是其终点。
— 米歇尔·德·蒙田

 

深入

数据类型。先把第一个 Python 程序放一边,让我们谈谈数据类型。在 Python 中,每个值都有一个数据类型,但您不需要声明变量的数据类型。这是怎么运作的?根据每个变量的初始赋值,Python 会判断它的类型,并在内部进行跟踪。

Python 有许多原生数据类型。以下是重要的类型:

  1. 布尔值要么是 True,要么是 False
  2. 数字可以是整数(12)、浮点数(1.11.2)、分数(1/22/3),甚至可以是复数。
  3. 字符串是 Unicode 字符序列,例如 HTML 文档。
  4. 字节字节数组,例如 JPEG 图片文件。
  5. 列表是值的有序序列。
  6. 元组是值的有序、不可变序列。
  7. 集合是值无序的集合。
  8. 字典是键值对无序的集合。

当然,除了这些类型之外还有其他类型。在 Python 中,一切都是对象,因此还有像模块函数方法文件,甚至编译代码这样的类型。您已经见过其中的一些了:模块有名称函数有 docstrings等等。您将在类和迭代器中了解类,在文件中了解文件。

字符串和字节非常重要——而且非常复杂——因此它们拥有自己的章节。让我们先看看其他类型。

布尔值

布尔值要么是真,要么是假。Python 有两个常量,巧妙地命名为 TrueFalse,它们可以用于直接分配布尔值。表达式也可以计算为布尔值。在某些地方(例如 if 语句),Python 期望表达式计算为布尔值。这些地方被称为布尔上下文。您可以在布尔上下文中使用几乎任何表达式,Python 将尝试确定其真值。不同的数据类型在布尔上下文中哪些值是真或假方面有不同的规则。(当您在本节后面看到一些具体示例时,这一点就会变得更加清晰。)

例如,请看来自humansize.py的这段代码片段:

if size < 0:
    raise ValueError('number must be non-negative')

size 是一个整数,0 是一个整数,< 是一个数值运算符。表达式 size < 0 的结果始终是一个布尔值。您可以在 Python 交互式 shell 中自行测试这一点

>>> size = 1
>>> size < 0
False
>>> size = 0
>>> size < 0
False
>>> size = -1
>>> size < 0
True

由于 Python 2 中遗留的一些问题,布尔值可以被视为数字。True1False 是 0。

>>> True + True
2
>>> True - False
1
>>> True * False
0
>>> True / False
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: int division or modulo by zero

哎哟,哎哟,哎哟!不要这样做。忘掉我甚至提过它吧。

数字

数字太棒了。有太多选择。Python 支持整数浮点数。没有类型声明来区分它们;Python 通过有无小数点来区分它们。

>>> type(1) 
<class 'int'>
>>> isinstance(1, int) 
True
>>> 1 + 1 
2
>>> 1 + 1.0 
2.0
>>> type(2.0)
<class 'float'>
  1. 您可以使用 type() 函数检查任何值或变量的类型。正如您可能预期的那样,1 是一个 int
  2. 类似地,您可以使用 isinstance() 函数检查值或变量是否为给定类型。
  3. 将一个 int 加到一个 int 会得到一个 int
  4. 将一个 int 加到一个 float 会得到一个 float。Python 会将 int 强制转换为 float 以执行加法,然后返回一个 float 作为结果。

强制整数转换为浮点数,反之亦然

正如您刚刚看到的,一些运算符(例如加法)会根据需要将整数强制转换为浮点数。您也可以自己强制转换它们。

>>> float(2) 
2.0
>>> int(2.0) 
2
>>> int(2.5) 
2
>>> int(-2.5) 
-2
>>> 1.12345678901234567890 
1.1234567890123457
>>> type(1000000000000000) 
<class 'int'>
  1. 您可以通过调用 float() 函数来显式地将 int 强制转换为 float
  2. 不出所料,您也可以通过调用 int() 函数来将 float 强制转换为 int
  3. int() 函数会截断,而不是四舍五入。
  4. int() 函数会将负数截断至 0。它是一个真正的截断函数,而不是地板函数。
  5. 浮点数精确到 15 位小数。
  6. 整数可以任意大。

Python 2 中有 intlong 的单独类型。int 数据类型受 sys.maxint 的限制,它因平台而异,但通常是 232-1。Python 3 只有一个整数类型,它的行为与 Python 2 中旧的 long 类型基本一致。有关详细信息,请参见 PEP 237。

常见的数字运算

您可以对数字进行各种操作。

>>> 11 / 2 
5.5
>>> 11 // 2 
5
>>> −11 // 2 
−6
>>> 11.0 // 2 
5.0
>>> 11 ** 2 
121
>>> 11 % 2 
1
  1. / 运算符执行浮点数除法。即使分子和分母都是 int,它也会返回一个 float
  2. // 运算符执行一种奇怪的整数除法。当结果为正时,您可以将其视为截断(而不是四舍五入)到 0 位小数,但要注意这一点。
  3. 在进行负数的整数除法时,// 运算符会向上舍入到最接近的整数。从数学角度来说,它是在向下舍入,因为 −6 小于 −5,但如果您期望它截断到 −5,则可能会误导您。
  4. // 运算符并不总是返回一个整数。如果分子或分母是 float,它仍然会四舍五入到最接近的整数,但实际返回值将是一个 float
  5. ** 运算符表示“乘方”。112121
  6. % 运算符给出进行整数除法后的余数。11 除以 2 等于 5,余数为 1,因此这里的结果为 1

在 Python 2 中,/ 运算符通常表示整数除法,但您可以通过在代码中包含一个特殊指令使其表现得像浮点数除法。在 Python 3 中,/ 运算符始终表示浮点数除法。有关详细信息,请参见 PEP 238。

分数

Python 不仅限于整数和浮点数。它还可以进行您在高中学习过的所有那些花哨的数学运算,并且很快就忘记了。

>>> import fractions 
>>> x = fractions.Fraction(1, 3) 
>>> x
Fraction(1, 3)
>>> x * 2 
Fraction(2, 3)
>>> fractions.Fraction(6, 4) 
Fraction(3, 2)
>>> fractions.Fraction(0, 0) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "fractions.py", line 96, in __new__
    raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
ZeroDivisionError: Fraction(0, 0)
  1. 要开始使用分数,请导入 fractions 模块。
  2. 要定义一个分数,请创建一个 Fraction 对象,并将分子和分母作为参数传递进去。
  3. 您可以对分数执行所有常见的数学运算。运算会返回一个新的 Fraction 对象。2 * (1/3) = (2/3)
  4. Fraction 对象会自动约简分数。(6/4) = (3/2)
  5. Python 有着良好的判断力,不会创建分母为零的分数。

三角函数

您还可以在 Python 中进行基本的三角函数运算。

>>> import math
>>> math.pi 
3.1415926535897931
>>> math.sin(math.pi / 2) 
1.0
>>> math.tan(math.pi / 4) 
0.99999999999999989
  1. math 模块有一个常量,用于表示 π,即圆周与其直径之比。
  2. math 模块包含所有基本的三角函数,包括 sin()cos()tan(),以及 asin() 等变体。
  3. 但是,请注意,Python 没有无限精度。tan(π / 4) 应该返回 1.0,而不是 0.99999999999999989

数字在布尔上下文中的表现

您可以在布尔上下文中使用数字,例如 if 语句。零值是假,非零值是真。

>>> def is_it_true(anything): 
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(1) 
yes, it's true
>>> is_it_true(-1)
yes, it's true
>>> is_it_true(0)
no, it's false
>>> is_it_true(0.1) 
yes, it's true
>>> is_it_true(0.0)
no, it's false
>>> import fractions
>>> is_it_true(fractions.Fraction(1, 2)) 
yes, it's true
>>> is_it_true(fractions.Fraction(0, 1))
no, it's false
  1. 您是否知道您可以在 Python 交互式 shell 中定义自己的函数?只需在每行末尾按 ENTER 键,并在空白行上按 ENTER 键完成即可。
  2. 在布尔上下文中,非零整数为真;0 为假。
  3. 非零浮点数为真;0.0 为假。注意这一点!如果存在轻微的舍入误差(并非不可能,如上一节所示),那么 Python 将测试 0.0000000000001 而不是 0,并将返回 True
  4. 分数也可以在布尔上下文中使用。Fraction(0, n) 对于所有 n 值都是假的。所有其他分数都是真的。

列表

列表是 Python 的主力数据类型。当我提到“列表”时,您可能在想“我必须预先声明大小的数组,它只能包含相同类型的项目,等等”。别这么想。列表比这酷多了。

Python 中的列表就像 Perl 5 中的数组。在 Perl 5 中,存储数组的变量总是以 @ 字符开头;在 Python 中,变量可以命名为任何东西,Python 会在内部跟踪数据类型。

Python 中的列表远不止 Java 中的数组(尽管如果这就是您生活中真正想要的全部,它可以被用作数组)。一个更好的类比是 ArrayList 类,它可以保存任意对象,并且可以在添加新项目时动态扩展。

创建列表

创建列表很简单:使用方括号括住用逗号分隔的值列表即可。

>>> a_list = ['a', 'b', 'mpilgrim', 'z', 'example'] 
>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example']
>>> a_list[0] 
'a'
>>> a_list[4] 
'example'
>>> a_list[-1] 
'example'
>>> a_list[-3] 
'mpilgrim'
  1. 首先,您定义了一个包含五个项目的列表。请注意,它们保留了原始顺序。这并非偶然。列表是项目的有序集合。
  2. 列表可以像零基数组一样使用。任何非空列表的第一个项目始终是 a_list[0]
  3. 这个包含五个项目的列表的最后一个项目是 a_list[4],因为列表始终是零基的。
  4. 负索引从列表的末尾反向计数访问项目。任何非空列表的最后一个项目始终是 a_list[-1]
  5. 如果您觉得负索引令人困惑,可以这样想:a_list[-n] == a_list[len(a_list) - n]。因此,在这个列表中,a_list[-3] == a_list[5 - 3] == a_list[2]

切片列表

定义好列表之后,您就可以获取列表中的任何部分作为新列表。这被称为对列表切片

>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example']
>>> a_list[1:3] 
['b', 'mpilgrim']
>>> a_list[1:-1] 
['b', 'mpilgrim', 'z']
>>> a_list[0:3] 
['a', 'b', 'mpilgrim']
>>> a_list[:3] 
['a', 'b', 'mpilgrim']
>>> a_list[3:] 
['z', 'example']
>>> a_list[:] 
['a', 'b', 'mpilgrim', 'z', 'example']
  1. 您可以通过指定两个索引来获取列表的一部分,称为“切片”。返回值是一个新列表,其中包含列表中所有项目的顺序,从第一个切片索引(在本例中为 a_list[1])开始,一直到但不包括第二个切片索引(在本例中为 a_list[3])。
  2. 如果一个或两个切片索引为负数,则切片仍然有效。如果对您有帮助,可以这样想:从左到右读取列表,第一个切片索引指定您想要的第一个项目,第二个切片索引指定您不想要的第一个项目。返回值是它们之间的所有内容。
  3. 列表是零基的,因此 a_list[0:3] 返回列表的前三个项目,从 a_list[0] 开始,一直到但不包括 a_list[3]
  4. 如果左侧切片索引为 0,则可以省略它,隐式地默认为 0。因此,a_list[:3]a_list[0:3] 相同,因为起始 0 是隐式的。
  5. 类似地,如果右边的切片索引是列表的长度,则可以省略它。因此,a_list[3:]a_list[3:5] 相同,因为此列表有五个项目。这里有一个令人愉快的对称性。在这个包含五个项目的列表中,a_list[:3] 返回前 3 个项目,而 a_list[3:] 返回最后两个项目。实际上,a_list[:n] 始终返回前 n 个项目,而 a_list[n:] 返回其余项目,而不管列表的长度如何。
  6. 如果两个切片索引都省略,则包括列表的所有项目。但这与原始的 a_list 变量不同。这是一个新的列表,它恰好包含所有相同的项目。a_list[:] 是创建列表完整副本的简写形式。

向列表中添加项目

有四种方法可以向列表中添加项目。

>>> a_list = ['a']
>>> a_list = a_list + [2.0, 3] 
>>> a_list 
['a', 2.0, 3]
>>> a_list.append(True) 
>>> a_list
['a', 2.0, 3, True]
>>> a_list.extend(['four', 'Ω']) 
>>> a_list
['a', 2.0, 3, True, 'four', 'Ω']
>>> a_list.insert(0, 'Ω') 
>>> a_list
['Ω', 'a', 2.0, 3, True, 'four', 'Ω']
  1. + 运算符连接列表以创建新列表。列表可以包含任意数量的项目;没有大小限制(除了可用内存)。但是,如果内存是一个问题,您应该注意列表连接会在内存中创建第二个列表。在这种情况下,该新列表会立即分配给现有的变量 a_list。因此,这一行代码实际上是一个两步过程——连接然后分配——当您处理大型列表时,这可能会(暂时)消耗大量内存。
  2. 列表可以包含任何数据类型的项目,并且单个列表中的项目不需要全部是同一类型。这里我们有一个包含字符串、浮点数和整数的列表。
  3. append() 方法将单个项目添加到列表的末尾。(现在列表中有*四个*不同的数据类型!)
  4. 列表作为类实现。“创建”列表实际上是在实例化类。因此,列表具有对其进行操作的方法。extend() 方法接受一个参数(一个列表),并将该参数的每个项目追加到原始列表中。
  5. insert() 方法将单个项目插入列表中。第一个参数是列表中将被从位置中剔除的第一个项目的索引。列表项目不需要是唯一的;例如,现在有两个具有值 'Ω' 的独立项目:第一个项目 a_list[0] 和最后一个项目 a_list[6]

a_list.insert(0, value) 类似于 Perl 中的 unshift() 函数。它将一个项目添加到列表的开头,并且所有其他项目的位置索引都会向上移动以腾出空间。

让我们更仔细地看一下 append()extend() 之间的区别。

>>> a_list = ['a', 'b', 'c']
>>> a_list.extend(['d', 'e', 'f']) 
>>> a_list
['a', 'b', 'c', 'd', 'e', 'f']
>>> len(a_list) 
6
>>> a_list[-1]
'f'
>>> a_list.append(['g', 'h', 'i']) 
>>> a_list
['a', 'b', 'c', 'd', 'e', 'f', ['g', 'h', 'i']]
>>> len(a_list) 
7
>>> a_list[-1]
['g', 'h', 'i']
  1. extend() 方法接受一个参数,该参数始终是列表,并将该列表的每个项目添加到 a_list 中。
  2. 如果从包含三个项目的列表开始并将其扩展到另一个包含三个项目的列表,则最终将获得一个包含六个项目的列表。
  3. 另一方面,append() 方法接受一个参数,该参数可以是任何数据类型。这里,您正在使用包含三个项目的列表调用 append() 方法。
  4. 如果从包含六个项目的列表开始并将其追加到另一个列表中,则最终将获得……一个包含七个项目的列表。为什么是七个?因为最后一个项目(您刚刚追加的项目)*本身就是一个列表*。列表可以包含任何类型的数据,包括其他列表。这可能是您想要的,也可能不是。但这是您要求的,也是您得到的。

在列表中搜索值

>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new']
>>> a_list.count('new') 
2
>>> 'new' in a_list 
True
>>> 'c' in a_list
False
>>> a_list.index('mpilgrim') 
3
>>> a_list.index('new') 
2
>>> a_list.index('c') 
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: list.index(x): x not in list
  1. 正如您所料,count() 方法返回列表中特定值的出现次数。
  2. 如果您只想了解某个值是否在列表中,in 运算符比使用 count() 方法略快。in 运算符始终返回 TrueFalse;它不会告诉您该值在列表中出现的次数。
  3. in 运算符和 count() 方法都不会告诉您值出现在列表中的位置。如果您需要了解值在列表中的位置,请调用 index() 方法。默认情况下,它将搜索整个列表,尽管您可以指定可选的第二个参数(从 0 开始的)索引来开始搜索,甚至可以指定可选的第三个参数(从 0 开始的)索引来停止搜索。
  4. index() 方法找到列表中值的第一个出现位置。在本例中,'new' 在列表中出现两次,在 a_list[2]a_list[4] 中,但 index() 方法只会返回第一个出现位置的索引。
  5. 正如您可能*不会*预料到的那样,如果在列表中找不到该值,index() 方法将引发异常。

等等,什么?没错:如果在列表中找不到该值,index() 方法将引发异常。这与大多数语言显着不同,大多数语言会返回一些无效索引(如 -1)。虽然这在一开始似乎很烦人,但我认为您会逐渐喜欢它。这意味着您的程序将在问题的源头崩溃,而不是稍后以奇怪且无声的方式失败。请记住,-1 是有效的列表索引。如果 index() 方法返回 -1,那么这可能会导致一些不太有趣的调试会话!

从列表中删除项目

列表可以自动扩展和收缩。您已经看到了扩展部分。还有几种不同的方法可以从列表中删除项目。

>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new']
>>> a_list[1]
'b'
>>> del a_list[1] 
>>> a_list
['a', 'new', 'mpilgrim', 'new']
>>> a_list[1] 
'new'
  1. 您可以使用 del 语句从列表中删除特定项目。
  2. 在删除索引 1 后访问索引 1 不会导致错误。所有被删除项目之后的项目都会将它们的位置索引移至“填充”删除项目后创建的“间隙”。

不知道位置索引?不用担心;您可以按值而不是按位置删除项目。

>>> a_list.remove('new') 
>>> a_list
['a', 'mpilgrim', 'new']
>>> a_list.remove('new') 
>>> a_list
['a', 'mpilgrim']
>>> a_list.remove('new')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
  1. 您还可以使用 remove() 方法从列表中删除项目。remove() 方法接受一个,并从列表中删除该值的第一个出现位置。同样,所有被删除项目之后的项目都会将它们的位置索引向下移动以“填充”间隙。列表永远不会有间隙。
  2. 您可以根据需要多次调用 remove() 方法,但如果您尝试删除不在列表中的值,它将引发异常。

从列表中删除项目:奖励回合

另一个有趣的列表方法是 pop()pop() 方法是另一种从列表中删除项目的方法,但带有一个小技巧。

>>> a_list = ['a', 'b', 'new', 'mpilgrim']
>>> a_list.pop() 
'mpilgrim'
>>> a_list
['a', 'b', 'new']
>>> a_list.pop(1) 
'b'
>>> a_list
['a', 'new']
>>> a_list.pop()
'new'
>>> a_list.pop()
'a'
>>> a_list.pop() 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: pop from empty list
  1. 当不带参数调用时,pop() 列表方法将删除列表中的最后一个项目,并返回它删除的值
  2. 您可以从列表中弹出任意项目。只需将位置索引传递给 pop() 方法。它将删除该项目,将所有位于其后的项目移至“填充”间隙,并返回它删除的值。
  3. 在空列表上调用 pop() 将引发异常。

不带参数调用 pop() 列表方法类似于 Perl 中的 pop() 函数。它从列表中删除最后一个项目,并返回已删除项目的 value。Perl 还有另一个函数 shift(),它删除第一个项目并返回其值;在 Python 中,这等效于 a_list.pop(0)

列表在布尔上下文中

您也可以在布尔上下文中使用列表,例如 if 语句。

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true([]) 
no, it's false
>>> is_it_true(['a']) 
yes, it's true
>>> is_it_true([False]) 
yes, it's true
  1. 在布尔上下文中,空列表为假。
  2. 任何至少包含一个项目的列表都为真。
  3. 任何至少包含一个项目的列表都为真。项目的 value 无关紧要。

元组

一个 元组 是一个不可变的列表。元组一旦创建,就不能以任何方式更改。

>>> a_tuple = ("a", "b", "mpilgrim", "z", "example") 
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
>>> a_tuple[0] 
'a'
>>> a_tuple[-1] 
'example'
>>> a_tuple[1:3] 
('b', 'mpilgrim')
  1. 元组的定义方式与列表相同,只是所有元素都被括在括号中而不是方括号中。
  2. 元组中的元素具有定义的顺序,就像列表一样。元组索引从零开始,就像列表一样,因此非空元组的第一个元素始终是 a_tuple[0]
  3. 负索引从元组的末尾开始计数,就像列表一样。
  4. 切片也适用,就像列表一样。当您切片列表时,您会得到一个新的列表;当您切片元组时,您会得到一个新的元组。

元组和列表之间最主要的区别是,元组不能更改。在技术术语中,元组是 不可变的。在实践术语中,它们没有允许您更改它们的方法。列表具有 append()extend()insert()remove()pop() 等方法。元组没有任何这些方法。您可以切片元组(因为这会创建一个新的元组),并且可以检查元组是否包含特定值(因为这不会更改元组),并且……就是这样。

# continued from the previous example
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
>>> a_tuple.append("new") 
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'append'
>>> a_tuple.remove("z") 
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'remove'
>>> a_tuple.index("example") 
4
>>> "z" in a_tuple 
True
  1. 您无法向元组中添加元素。元组没有 append()extend() 方法。
  2. 您无法从元组中删除元素。元组没有 remove()pop() 方法。
  3. 可以在元组中找到元素,因为这不会更改元组。
  4. 您也可以使用 in 运算符来检查元组中是否存在元素。

那么元组有什么用呢?

元组可以转换为列表,反之亦然。内置的 tuple() 函数接受一个列表,并返回一个包含相同元素的元组,而 list() 函数接受一个元组,并返回一个列表。实际上,tuple() 会冻结一个列表,而 list() 会解冻一个元组。

元组在布尔上下文中

您可以在布尔上下文中使用元组,例如 if 语句。

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(()) 
no, it's false
>>> is_it_true(('a', 'b')) 
yes, it's true
>>> is_it_true((False,)) 
yes, it's true
>>> type((False)) 
<class 'bool'>
>>> type((False,))
<class 'tuple'>
  1. 在布尔上下文中,空元组为假。
  2. 任何至少包含一个项目的元组都为真。
  3. 任何至少包含一个项目的元组都为真。项目的 value 无关紧要。但是那里面的逗号是怎么回事呢?
  4. 要创建一个包含一个项目的元组,您需要在 value 后面加一个逗号。如果没有逗号,Python 只会假设您有一对额外的括号,这无关紧要,但它不会创建一个元组。

一次分配多个值

这是一个很酷的编程快捷方式:在 Python 中,您可以使用元组一次分配多个值。

>>> v = ('a', 2, True)
>>> (x, y, z) = v 
>>> x
'a'
>>> y
2
>>> z
True
  1. v 是一个包含三个元素的元组,而 (x, y, z) 是一个包含三个变量的元组。将一个分配给另一个会按顺序将 v 的每个值分配给每个变量。

这有各种用途。假设您想为一系列值分配名称。您可以使用内置的 range() 函数与多变量赋值一起快速分配连续值。

>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7) 
>>> MONDAY 
0
>>> TUESDAY
1
>>> SUNDAY
6
  1. 内置的range()函数构建一个整数序列。(从技术上讲,range()函数返回一个迭代器,而不是列表或元组,但您将在后面了解这种区别。)MONDAYTUESDAYWEDNESDAYTHURSDAYFRIDAYSATURDAYSUNDAY是您定义的变量。(此示例来自calendar模块,这是一个有趣的小模块,它打印日历,就像UNIX程序cal一样。calendar模块为一周中的每一天定义整型常量。)
  2. 现在每个变量都有其值:MONDAY为 0,TUESDAY1,依此类推。

您还可以使用多变量赋值来构建返回多个值的函数,只需返回包含所有值的元组即可。调用者可以将其视为单个元组,也可以将这些值分配给单个变量。许多标准 Python 库都这样做,包括os模块,您将在下一章中了解到。

集合

一个集合是无序的“袋子”,包含唯一的值。单个集合可以包含任何不可变数据类型的元素。一旦您有两个集合,您就可以进行标准的集合运算,如并集、交集和集合差。

创建集合

首先要做的就是创建集合。这很容易。

>>> a_set = {1} 
>>> a_set
{1}
>>> type(a_set) 
<class 'set'>
>>> a_set = {1, 2} 
>>> a_set
{1, 2}
  1. 要创建一个包含一个值的集合,请将该值放在花括号 ({}) 中。
  2. 集合实际上是用实现的,但现在不用担心这一点。
  3. 要创建一个包含多个值的集合,请用逗号分隔这些值,并将它们全部用花括号括起来。

您还可以从一个列表中创建一个集合。

>>> a_list = ['a', 'b', 'mpilgrim', True, False, 42]
>>> a_set = set(a_list) 
>>> a_set 
{'a', False, 'b', True, 'mpilgrim', 42}
>>> a_list 
['a', 'b', 'mpilgrim', True, False, 42]
  1. 要从列表创建集合,请使用set()函数。(知道集合如何实现的迂腐的人会指出,这实际上并不是调用函数,而是实例化一个类。我保证您将在本书的后面了解到这两者的区别。现在,只需知道set()的行为像函数一样,它返回一个集合。)
  2. 正如我之前提到的,单个集合可以包含任何数据类型的元素。而且,正如我之前提到的,集合是无序的。该集合不会记住用于创建它的列表的原始顺序。如果您要向该集合添加项,它不会记住您添加它们的顺序。
  3. 原始列表保持不变。

还没有值?没关系。您可以创建一个空集合。

>>> a_set = set() 
>>> a_set 
set()
>>> type(a_set) 
<class 'set'>
>>> len(a_set) 
0
>>> not_sure = {} 
>>> type(not_sure)
<class 'dict'>
  1. 要创建一个空集合,请在不带参数的情况下调用set()
  2. 空集合的打印表示看起来有点奇怪。您可能期望是{}吗?这将表示一个空字典,而不是一个空集合。您将在本章后面的内容中了解字典。
  3. 尽管打印表示看起来很奇怪,但这确实是一个集合…
  4. …而且该集合没有成员。
  5. 由于从 Python 2 中继承的某些历史原因,您不能使用两个花括号创建空集合。这实际上创建的是一个空字典,而不是一个空集合。

修改集合

有两种不同的方法可以向现有集合添加元素:add()方法和update()方法。

>>> a_set = {1, 2}
>>> a_set.add(4) 
>>> a_set
{1, 2, 4}
>>> len(a_set) 
3
>>> a_set.add(1) 
>>> a_set
{1, 2, 4}
>>> len(a_set) 
3
  1. add()方法接受一个参数,该参数可以是任何数据类型,并将给定值添加到集合中。
  2. 该集合现在有 3 个成员。
  3. 集合是唯一值的袋子。如果您尝试添加一个已经存在于集合中的值,它将不会执行任何操作。它不会引发错误;它只是一个无操作。
  4. 该集合仍然有 3 个成员。
>>> a_set = {1, 2, 3}
>>> a_set
{1, 2, 3}
>>> a_set.update({2, 4, 6}) 
>>> a_set 
{1, 2, 3, 4, 6}
>>> a_set.update({3, 6, 9}, {1, 2, 3, 5, 8, 13}) 
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 13}
>>> a_set.update([10, 20, 30]) 
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 10, 13, 20, 30}
  1. update()方法接受一个参数(一个集合),并将所有成员添加到原始集合中。这就像您对集合中的每个成员都调用了add()方法一样。
  2. 由于集合不能包含重复元素,因此重复元素将被忽略。
  3. 您实际上可以用任何数量的参数调用update()方法。当用两个集合调用时,update()方法会将每个集合的所有成员添加到原始集合中(删除重复元素)。
  4. update()方法可以接受多种不同数据类型的对象,包括列表。当用列表调用时,update()方法会将列表的所有项添加到原始集合中。

从集合中删除项

有三种方法可以从集合中删除单个值。前两种方法,discard()remove(),之间有一个细微的差别。

>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
>>> a_set
{1, 3, 36, 6, 10, 45, 15, 21, 28}
>>> a_set.discard(10) 
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}
>>> a_set.discard(10) 
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}
>>> a_set.remove(21) 
>>> a_set
{1, 3, 36, 6, 45, 15, 28}
>>> a_set.remove(21) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 21
  1. discard()方法接受一个值作为参数,并将该值从集合中删除。
  2. 如果您使用集合中不存在的值调用discard()方法,它不会执行任何操作。不会报错;它只是一个无操作。
  3. remove()方法也接受一个值作为参数,它也会从集合中删除该值。
  4. 不同之处在于:如果该值不存在于集合中,remove()方法会引发KeyError异常。

与列表一样,集合也有pop()方法。

>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
>>> a_set.pop() 
1
>>> a_set.pop()
3
>>> a_set.pop()
36
>>> a_set
{6, 10, 45, 15, 21, 28}
>>> a_set.clear() 
>>> a_set
set()
>>> a_set.pop() 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'pop from an empty set'
  1. pop()方法从集合中删除一个值并返回该值。但是,由于集合是无序的,因此集合中没有“最后一个”值,因此无法控制删除哪个值。它是完全任意的。
  2. clear()方法会从集合中删除所有值,只剩下一个空集合。这等效于a_set = set(),它将创建一个新的空集合并覆盖a_set变量的先前值。
  3. 尝试从空集合中弹出值将引发KeyError异常。

常见的集合运算

Python 的set类型支持几种常见的集合运算。

>>> a_set = {2, 4, 5, 9, 12, 21, 30, 51, 76, 127, 195}
>>> 30 in a_set 
True
>>> 31 in a_set
False
>>> b_set = {1, 2, 3, 5, 6, 8, 9, 12, 15, 17, 18, 21}
>>> a_set.union(b_set) 
{1, 2, 195, 4, 5, 6, 8, 12, 76, 15, 17, 18, 3, 21, 30, 51, 9, 127}
>>> a_set.intersection(b_set) 
{9, 2, 12, 5, 21}
>>> a_set.difference(b_set) 
{195, 4, 76, 51, 30, 127}
>>> a_set.symmetric_difference(b_set) 
{1, 3, 4, 6, 8, 76, 15, 17, 18, 195, 127, 30, 51}
  1. 要测试一个值是否是集合的成员,请使用in运算符。这与列表的工作原理相同。
  2. union()方法返回一个新的集合,其中包含任一集合中的所有元素。
  3. intersection()方法返回一个新的集合,其中包含两个集合中都存在的元素。
  4. difference()方法返回一个新的集合,其中包含存在于a_set中但不存在于b_set中的所有元素。
  5. symmetric_difference()方法返回一个新的集合,其中包含存在于两个集合中一个集合中的所有元素。

这三种方法都是对称的。

# continued from the previous example
>>> b_set.symmetric_difference(a_set) 
{3, 1, 195, 4, 6, 8, 76, 15, 17, 18, 51, 30, 127}
>>> b_set.symmetric_difference(a_set) == a_set.symmetric_difference(b_set) 
True
>>> b_set.union(a_set) == a_set.union(b_set) 
True
>>> b_set.intersection(a_set) == a_set.intersection(b_set) 
True
>>> b_set.difference(a_set) == a_set.difference(b_set) 
False
  1. a_setb_set 的对称差看起来b_seta_set 的对称差不同,但请记住,集合是无序的。任何包含相同值的两个集合(没有剩余的元素)都被认为是相等的。
  2. 而这正是这里发生的事情。不要被 Python Shell 对这些集合的打印表示所迷惑。它们包含相同的值,因此它们是相等的。
  3. 两个集合的并集也是对称的。
  4. 两个集合的交集也是对称的。
  5. 两个集合的差集不是对称的。这是有道理的;它类似于从另一个数字中减去一个数字。操作数的顺序很重要。

最后,您还可以向集合提出一些问题。

>>> a_set = {1, 2, 3}
>>> b_set = {1, 2, 3, 4}
>>> a_set.issubset(b_set) 
True
>>> b_set.issuperset(a_set) 
True
>>> a_set.add(5) 
>>> a_set.issubset(b_set)
False
>>> b_set.issuperset(a_set)
False
  1. a_setb_set子集 — a_set 中的所有成员也是b_set 的成员。
  2. 反过来问相同的问题,b_seta_set超集,因为a_set 中的所有成员也是b_set 的成员。
  3. 一旦您向a_set 中添加一个不在b_set 中的值,这两个测试都会返回False

布尔上下文中的集合

您可以在布尔上下文中使用集合,例如在if语句中。

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(set()) 
no, it's false
>>> is_it_true({'a'}) 
yes, it's true
>>> is_it_true({False}) 
yes, it's true
  1. 在布尔上下文中,空集合为假。
  2. 任何至少包含一个项目的集合都为真。
  3. 任何至少包含一个项目的集合都为真。项目的价值无关紧要。

字典

一个字典是一个无序的键值对集合。当您向字典添加一个键时,您还必须为该键添加一个值。(您随时可以更改该值。)Python 字典经过优化,以便在您知道键的情况下检索值,反之则不行。

Python 中的字典类似于 Perl 5 中的哈希。在 Perl 5 中,存储哈希的变量总是以%字符开头。在 Python 中,变量可以命名为任何东西,Python 在内部跟踪数据类型。

创建字典

创建字典很容易。语法类似于集合,但您没有值,而是键值对。一旦您有了字典,您就可以按键查找值。

>>> a_dict = {'server': 'db.diveintopython3.org', 'database': 'mysql'} 
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> a_dict['server'] 
'db.diveintopython3.org'
>>> a_dict['database'] 
'mysql'
>>> a_dict['db.diveintopython3.org'] 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'db.diveintopython3.org'
  1. 首先,您创建一个包含两个项目的字典,并将其分配给变量a_dict。每个项目都是一个键值对,整个项目集用花括号括起来。
  2. 'server'是一个键,它关联的值(由a_dict['server']引用)是'db.diveintopython3.org'
  3. 'database'是一个键,它关联的值(由a_dict['database']引用)是'mysql'
  4. 您可以按键获取值,但不能按值获取键。因此,a_dict['server']'db.diveintopython3.org',但a_dict['db.diveintopython3.org']会引发异常,因为'db.diveintopython3.org'不是键。

修改字典

字典没有预定义的大小限制。您可以随时向字典添加新的键值对,也可以修改现有键的值。从前面的示例继续

>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> a_dict['database'] = 'blog' 
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'blog'}
>>> a_dict['user'] = 'mark' 
>>> a_dict 
{'server': 'db.diveintopython3.org', 'user': 'mark', 'database': 'blog'}
>>> a_dict['user'] = 'dora' 
>>> a_dict
{'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}
>>> a_dict['User'] = 'mark' 
>>> a_dict
{'User': 'mark', 'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}
  1. 您不能在字典中包含重复的键。将值分配给现有键将清除旧值。
  2. 您可以随时添加新的键值对。此语法与修改现有值相同。
  3. 新的字典项(键'user',值'mark')似乎位于中间。事实上,这些项在第一个示例中似乎按顺序排列只是一个巧合;它们现在似乎不再按顺序排列也是一个巧合。
  4. 将值分配给现有字典键只是用新值替换旧值。
  5. 这会将user键的值改回 "mark" 吗?不会!仔细观察该键 — 那是"User"中的大写U。字典键区分大小写,因此该语句正在创建一个新的键值对,而不是覆盖现有键值对。它可能看起来很像,但就 Python 而言,它完全不同。

混合值字典

字典不仅仅用于字符串。字典值可以是任何数据类型,包括整数、布尔值、任意对象,甚至其他字典。在一个字典中,这些值并不需要全部是同一类型;您可以根据需要进行混合匹配。字典键的限制更多,但可以是字符串、整数以及其他几种类型。您也可以在一个字典中混合匹配键数据类型。

事实上,您已经在您的第一个 Python 程序中看到了包含非字符串键和值的字典。

SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
            1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

让我们在交互式 shell 中将其拆解。

>>> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
...             1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
>>> len(SUFFIXES) 
2
>>> 1000 in SUFFIXES 
True
>>> SUFFIXES[1000] 
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
>>> SUFFIXES[1024] 
['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
>>> SUFFIXES[1000][3] 
'TB'
  1. 列表集合一样,len()函数会告诉您字典中键的数量。
  2. 与列表和集合类似,您可以使用 `in` 运算符来测试字典中是否定义了特定的键。
  3. 1000 **是** `SUFFIXES` 字典中的一个键;它的值是一个包含八个元素的列表(确切地说,是八个字符串)。
  4. 类似地,1024 是 `SUFFIXES` 字典中的一个键;它的值也是一个包含八个元素的列表。
  5. 由于 `SUFFIXES[1000]` 是一个列表,因此您可以通过它们的 0 为基的索引来访问列表中的各个元素。

布尔上下文中的字典

您也可以在 布尔上下文中 使用字典,例如 `if` 语句。

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true({}) 
no, it's false
>>> is_it_true({'a': 1}) 
yes, it's true
  1. 在布尔上下文中,空字典为假。
  2. 任何至少包含一个键值对的字典都为真。

None 是 Python 中的一个特殊常量。它是一个 值。NoneFalse 不同。None 不是 0。None 不是空字符串。将 None 与除 None 以外的任何内容进行比较,始终会返回 False

None 是唯一的空值。它有自己的数据类型 (NoneType)。您可以将 None 分配给任何变量,但不能创建其他 NoneType 对象。所有值为 None 的变量都彼此相等。

>>> type(None)
<class 'NoneType'>
>>> None == False
False
>>> None == 0
False
>>> None == ''
False
>>> None == None
True
>>> x = None
>>> x == None
True
>>> y = None
>>> x == y
True

布尔上下文中的 `None`

布尔上下文中None 为假,而 not None 为真。

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(None)
no, it's false
>>> is_it_true(not None)
yes, it's true

进一步阅读

© 2001–11 马克·皮尔格里姆