1-7-元组-文件及其他

1. 元组

  • 任意对象的有序集合:元组是一个位置有序的对象的集合,与列表相同,可以嵌入到任何类别的对象中。
  • 通过偏移存取:同字符串、列表一样,通过偏移来访问。
  • 属于不可变序列类型:不支持在任何原处修改操作。
  • 固定长度、异构、任意嵌套:可以包含其他的复合对象。
  • 对象引用的数组:元组最好看做是对象引用的数组,元组存储指向其他对象的存取点(引用),并且对元组进行索引操作的速度相对较快。

常见元组操作:

运算 解释
() 空元组
T = (0,) 单个元素的元组(非表达式)
T = (0, 'Ni', 1.2, 3) 四个元素的元组
T = 0, 'Ni', 1.2, 3 另一个四元素的元组(与前列相同)
T = ('Bob', ('dev', 'mgr')) 嵌套元组
T = tuple('spam') 一个可迭代对象的项的元组
T[i] 索引
T[i][j] 索引的索引
T[i:j] 分片
len(T) 长度
T1 + T2 合并
T * 3 重复
for x in T: print(x) 迭代
'spam' in T 成员关系
[x ** 2 for x in T]
T.index('Ni') 搜索
T.count('Ni') 计数
namedtuple('Emp', ['name', 'jobs']) 命名元组扩展类型

1.1 实际应用中的元组

元组没有方法,但是支持字符串和列表的一般序列操作。

1
(1, 2) + (3, 4)      # 合并
(1, 2, 3, 4)
1
(1, 2)*4              # 重复
(1, 2, 1, 2, 1, 2, 1, 2)
1
2
T = (1, 2, 3, 4)       # 索引,分片
T[0], T[1:3]
(1, (2, 3))

元组的特殊语法:逗号和圆括号

如果圆括号里的单一对象是元组对象而不是一个简单的表达式,需要对 Python 进行特别说明。如果确实想得到一个元组,只要在这一单个元素之后,关闭圆括号之前加一个逗号。

1
2
x = (40)                # 一个整数
x
40
1
2
y = (40,)                # 包含一个整数的元组
y
(40,)

作为特殊情况,在不会引起语法冲突的情况下,Python 允许忽略元组的圆括号。

仅当元组作为常量传给函数调用(圆括号很重要),以及嵌套在一个更大的数据结构例如列表或字典中(逗号很重要),或者在 Python 2.X 的 print 语句中列出时,圆括号才是必不可少的。

转换、方法以及不可变性

除了常量语法不同以外,元组的操作和字符串及列表是一致的。

元组不提供字符串、列表和字典中的方法。

如果想对元组进行排序,通常先得将它转换为列表并使其成为一个可变对象,才能获得使用排序方法调用的权。

1
2
3
4
T = ('cc', 'aa', 'dd', 'bb')
tmp = list(T) #从元组创建列表
tmp.sort() #排序列表
tmp
['aa', 'bb', 'cc', 'dd']
1
2
T = tuple(tmp)              #从列表创建元组
T
('aa', 'bb', 'cc', 'dd')

或者使用新的 sorted 内置方法,它接受任何序列对象。

1
2
T = ('cc', 'aa', 'dd', 'bb')
sorted(T)
['aa', 'bb', 'cc', 'dd']

列表解析(List comprehension)也可用于元组的转换。

1
2
3
T = (1, 2, 3, 4, 5)
L = [x + 20 for x in T]
L
[21, 22, 23, 24, 25]

Python 3.0 之后,元组有了两个自己的方法——index 和 count。

1
2
T = (1, 2, 3, 2, 4, 2)
T.index(2)
1
1
T.count(2)
3

元组的不可变性只适用于元组本身顶层而非其内容,元组内部的列表是可以像往常那样修改的。

1
2
T = (1, [2, 3], 4)
T[1] = 'spam'
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-8-1ef8b3bb1e3d> in <module>()
      1 T = (1, [2, 3], 4)
----> 2 T[1] = 'spam'

TypeError: 'tuple' object does not support item assignment
1
2
T[1][0] = 'spam'
T
(1, ['spam', 3], 4)

1.2 记录重访: Named Tuples

列表通过位置访问数据,字典通过键访问数据,我们还可以同时通过位置和名字来访问记录的字段。

namedtuple 功能,从 collections 库中导入,能够实现扩展类型,为元组加上逻辑,允许成员可以通过位置和属性名称来访问,如果需要,也可以通过 key 将其转换为类字典形式进行访问。

1
2
3
4
from collections import namedtuple
Rec = namedtuple('Rec', ['name', 'age', 'jobs'])
bob = Rec('Bob', age=40.5, jobs=['dev', 'mgr'])
bob
Rec(name='Bob', age=40.5, jobs=['dev', 'mgr'])
1
bob[0], bob[2]            # 通过位置访问
('Bob', ['dev', 'mgr'])
1
bob.name, bob.jobs        # 通过属性访问
('Bob', ['dev', 'mgr'])

当需要时,可以转换为类字典。

1
2
o = bob._asdict()         # 类字典形式
o['name'], o['jobs'] # 也通过键访问
('Bob', ['dev', 'mgr'])
1
o
OrderedDict([('name', 'Bob'), ('age', 40.5), ('jobs', ['dev', 'mgr'])])

named tuple 是元组/类/字典的结合体,扩展元组类型创建新类,为每一个已命名字段插入一个属性访问方法,将名字映射到对应的位置。

2. 文件

文件对象类型提供了一种可以存取 Python 程序内部文件的方法。

内置 open 函数会创建一个 Python 文件对象,调用 open 后,可以通过调用返回文件对象的方法来读写相关外部文件。

文件对象只是常见文件处理任务输出模块,多数文件方法都与执行外部文件相关的文件对象的输入和输出有关,但其他文件方法可查找文件中的新位置、刷新输出缓存等。

常见文件运算

操作 解释
output = open(r'C:\spam', 'w') 创建输出文件('w’是指写入)
input = open('data', 'r') 创建输入文件('r’是指读取)
input = open('data') 与上一行相同('r’是默认值)
aString = input.read() 把整个文件读进单一字符串
aString = input.read(N) 读取之后的N个字节(一或多个)到一个字符串
aString = input.readline() 读取下一行(包括行末标识符)到一个字符串
aList = input.readlines() 读取整个文件到字符串列表
output.write(aString) 写入字节字符串到文件
output.writelines(aList) 把列表内所有字符串写入文件
output.close() 手动关闭(当文件收集完成时会替你关闭文件)
output.flush() 把输出缓冲区刷到硬盘中,但不关闭文件
anyFile.seek(N) 修改文件位置到偏移量N处以便进行下一个操作
for line in open('data'): use line 文件迭代器一行一行地读取
open('f.txt', encoding='latin-1') Python 3.0 Unicode文本文件(str字符串)
open('f.bin', 'rb') Python 3.0 二进制byte文件(bytes字符串)

2.1 打开文件

为了打开一个文件,程序会调用内置 open 函数,首先是外部文件名,接着是处理模式。

外部文件名可能包含平台特定的以及绝对或相对目录路径前缀,没有目录路径时,文件假定存在当前的工作目录中(即 Python 脚本运行的地方)。

处理模式典型地用字符串’r’代表为输入打开文件(默认值),'w’代表为输出生成并打开文件,'a’代表为在文件尾部追加内容而打开文件。

处理模式参数也可以指定为其他选项:

  • 在模式字符串尾部加上b可以进行二进制数据处理(行末转换和 Python 3.0 Unicode 编码被关闭)。
  • 加上“+”意味着同时为输入和输出打开文件(也就是说, 我们可以对相同文件对象进行读写,往往与对文件中的修改的查找操作配合使用)。

open 的两个参数必须都是 Python 字符串,第三个是可选参数,用来控制输出缓存:传入“0”意味着输出无缓存(写入方法调用时立即传给外部文件)。额外的参数可能提供给特别类型的文件(例如 Python 3.X 中 Unicode 文本文件的编码)。

2.2 使用文件

Python程序中的文本文件都采用字符串的形式。

读取文件时会返回字符串形式的文本,文本作为字符串传递给write方法。

基础用法的提示:

  • **文件迭代器是最好的读取行工具:**现在从文本文件读取文字行的最佳方式是根本不要读取该文件,文件也有个迭代器会自动地在for循环、列表解析或者其他迭代语句中对文件进行逐行读取。
  • **内容是字符串,不是对象:**从文件读取的数据回到脚本时是一个字符串。
  • **文件是缓冲的并且是可查找的:**默认情况下,输出文件总是缓冲的,这意味着写入的文本可能不会立即自动从内存转换到硬盘——关闭一个文件,或者运行其flush方法,迫使缓存的数据进入硬盘。可以用额外的open参数来避免缓存,但是,这可能会影响到性能。
  • **close是通常选项:**调用文件close方法将会终止对外部文件的连接,释放系统资源,迫使缓存的输出至硬盘。在Python中,一旦对象不再被引用,则这个对象的内存空间就会被自动收回。当文件对象被收回时,Python也会自动关闭该文件,这意味着不需要总是手动去关闭文件,尤其是对于不会运行很长时间的简单脚本。另一方面,手动关闭调用没有任何坏处,而且在大型程序中通常是个很不错的习惯。严格地讲,文件的这个收集完成后自动关闭的特性不是语言定义的一部分,而且可能随时间而改变。当许多文件在循环中打开时,Python(除了CPython)可能需要调用close来立即释放系统资源。除此之外,调用close有时可能需要清除文件对象中没有被回收的缓冲输出。确保文件自动关闭的一种替代方法,参阅本部分随后的文件对象的上下文管理器,与with/as语句一起使用。

2.3 实际应用中的文件

1
2
myfile = open('myfile.txt', 'w')        # 打开文本输出:创建
myfile.write('hello text file\n') # 写一行文本:字符串
16
1
myfile.write('goodbye text file\n')
18
1
myfile.close()
1
2
myfile = open('myfile.txt')               # 打开文本输入
myfile.readline() # 读取行
'hello text file\n'
1
myfile.readline()
'goodbye text file\n'
1
myfile.readline()                          # 空字符串:文件底部
''

文件的空行是含有新行符的字符串,而不是空字符串。

文件 write 调用返回了在 Python 3.X 中写入的字符数。

写入方法不会为我们添加行终止符,所以程序必须包含它来严格地终止行。

如果想要显示带有末行字符解释的文件内容,用文件对象的 read 方法把整个文件读入到一个字符串中,并打印它:

1
open('myfile.txt').read()                   # 一次全部读取至字符串
'hello text file\ngoodbye text file\n'

如果想要一行一行地扫描一个文本文件,文件迭代器往往是最佳选择:

1
2
for line in open('myfile.txt'):              # 使用文件迭代器,而不是read
print(line, end='')
hello text file
goodbye text file

2.4 文本和二进制文件

文件类型由 open 的第二个参数决定,模式字符串包含一个 ‘b’ 表示二进制。

文本和二进制文件的区别:

  • 文本文件把内容表示为常规的 str 字符串,自动执行 Unicode 编码和解码,并且默认执行末行转换。
  • 二进制文件把内容表示为一个特殊的 bytes 字符串类型,并且允许程序不修改地访问文件内容。

由于文本文件实现了 Unicode 编码,不能以文本模式打开一个二进制数据文件。

二进制文件不会对数据执行任何末行转换。

2.5 在文件中存储并解析 Python 对象

1
2
3
4
5
6
7
8
9
10
X, Y, Z = 43, 44, 45                          # 原始Python对象
S = 'Spam' # 保存至文件必须为字符串
D = {'a':1, 'b':2}
L = [1, 2, 3]

F = open('datafile.txt', 'w') # 创建输出文件
F.write(S + '\n') # 使用 \n 结束行
F.write('%s, %s, %s\n'%(X, Y, Z)) # 将数字转换为字符串
F.write(str(L) + '$' + str(D) + '\n') # 转换,并使用$分割
F.close()
1
2
chars = open('datafile.txt').read()             # 显示原始字符串
chars
"Spam\n43, 44, 45\n[1, 2, 3]${'a': 1, 'b': 2}\n"
1
print(chars)
Spam
43, 44, 45
[1, 2, 3]${'a': 1, 'b': 2}

Python 不会自动把字符串转换为数字或其他类型的对象,因此需要使用其他转换工具,把文本文件中的字符串转换成真正的 Python 对象。

1
2
3
F = open('datafile.txt')                        # 再次打开文件
line = F.readline() # 读取一行
line
'Spam\n'
1
line.rstrip()                                   # 移除行终止符
'Spam'
1
2
line  = F.readline()
line
'43, 44, 45\n'
1
2
parts = line.split(',')                          # 根据逗号划分
parts
['43', ' 44', ' 45\n']
1
int(parts[1])                                    # 从字符串转换至int
44
1
2
numbers = [int(P) for P in parts]                # 一次转换列表中所有元素
numbers
[43, 44, 45]

要转换文件所存储的列表和字典,可以运行 eval 这一内置函数,eval 能够把字符串当做可执行程序代码。

1
2
line = F.readline()
line
"[1, 2, 3]${'a': 1, 'b': 2}\n"
1
2
parts = line.split('$')
parts
['[1, 2, 3]', "{'a': 1, 'b': 2}\n"]
1
eval(parts[0])                                      # 转换至任意对象类型
[1, 2, 3]
1
2
objects = [eval(P) for P in parts]
objects
[[1, 2, 3], {'a': 1, 'b': 2}]

2.6 用 pickle 存储 Python 的原生对象

pickle 模块能够让我们直接在文件中存储几乎任何 Python 对象的高级工具,不需要把字符串转换来转换去,要在文件中存储字典,可以直接用 pickle 来存储。

1
2
3
4
5
D = {'a':1, 'b':2}
F = open('datafile.pkl', 'wb')
import pickle
pickle.dump(D, F) # pickle 任何对象到文件
F.close()

要取回字典时,只要简单地再用一次 pickle 进行重建就可以了:

1
2
3
F = open('datafile.pkl', 'rb')
E = pickle.load(F)
E
{'a': 1, 'b': 2}

pickle 程序创建和使用一个 bytes 字符串对象,这些对象意味着二进制模式文件,因此需要以二进制模式打开用来存储 pickle 化的对象的文件。

2.7 用 JSON 格式存储 Python 对象

JSON 是一种新兴的数据交换格式,它既是编程语言的,又受到不同系统支持。

JSON 不支持像 pickle 那样广泛的 Python 对象类型,但是它的可移植性在某些上下文中是一个优势,它代表了另一种方式来序列化用于存储和传输的特定类别的 Python 对象。

此外,由于 JSON 非常接近 Python 字典和列表的语法,可以由 JSON 标准库模块实现的。

1
2
3
name = dict(first='Bob', last='Smith')
rec = dict(name=name, job=['dev', 'mgr'], age=40.5)
rec
{'age': 40.5, 'job': ['dev', 'mgr'], 'name': {'first': 'Bob', 'last': 'Smith'}}
1
2
import json
json.dumps(rec)
'{"name": {"first": "Bob", "last": "Smith"}, "job": ["dev", "mgr"], "age": 40.5}'

将 Python 对象转换为文件中的 JSON 数据字符串非常简单。

在存储至文件中之前,数据只是 Python 对象;当加载 JSON 文件时,JSON 模块从 JSON 文本表示中重新创建它们。

1
2
json.dump(rec, fp=open('testjson.txt', 'w'), indent=4)
print(open('testjson.txt').read())
{
    "name": {
        "first": "Bob",
        "last": "Smith"
    },
    "job": [
        "dev",
        "mgr"
    ],
    "age": 40.5
}
1
2
P = json.load(open('testjson.txt'))
P
{'age': 40.5, 'job': ['dev', 'mgr'], 'name': {'first': 'Bob', 'last': 'Smith'}}

2.8 文件中打包二进制数据的存储与解析

struct 模块能够构造并解析打包的二进制数据,能够把文件中的字符串解读为二进制数据。

要生成一个打包的二进制数据文件,用“wb”(写入二进制)模式打开它,并将一个格式化字符串和几个 Python 对象传给 struct。

1
2
3
4
F = open('data.bin', 'wb')                        # 打开二进制输出文件
import struct
data = struct.pack('>i4sh', 7, b'spam', 8) # 创建打包的二进制数据
data
b'\x00\x00\x00\x07spam\x00\x08'
1
2
F.write(data)                                      # 写入字节字符串
F.close()

要将二进制数据字符串解析为普通 Python 对象,可以简单地读取字符串,并使用相同格式的字符串把它解压出来。

1
2
3
F = open('data.bin', 'rb')
data = F.read() # 读取打包的二进制数据
data
b'\x00\x00\x00\x07spam\x00\x08'
1
2
values = struct.unpack('>i4sh', data)               # 转换为Python对象
values
(7, b'spam', 8)

2.9 文件上下文管理器

允许我们把文件处理代码包装到一个逻辑层中,以确保在退出后可以自动关闭文件,而不是依赖于垃圾收集上的自动关闭:

1
2
3
with open('datafile.txt') as myfile:
for line in myfile:
pass

3. 核心类型复习与总结

对象分类

对象类型 分类 是否可变
数字 数值
字符串 序列
列表 序列
字典 映射
元组 序列
文件 扩展 N/A
集合 集合

Frozenset|集合|否

bytearray|序列|是

  • 对象根据分类来共享操作;例如,字符串、列表和元组都共享诸如合并、长度和索引等序列操作。
  • 只有可变对象(列表、字典和集合)可以原处修改。
  • 集合类似于一个无值的字典的键,但是,它们不能映射为值,并且没有顺序。frozenset 是集合的一个不可变版本。

3.1 对象灵活性

  • 列表、字典和元组可以包含任何种类的对象。
  • 集合可以包含任何类型的不可变对象。
  • 列表、字典和元组可以任意嵌套。
  • 列表和字典可以动态地扩大和缩小。
1
L = ['abc', [(1, 2), ([3], 4)], {'a': 1}]
1
L[1]
[(1, 2), ([3], 4)]
1
L[1][1]
([3], 4)

3.2 引用 VS 拷贝

赋值操作可能会产生相同对象的多个引用,在原处修改可变对象时可能会影响程序中其他地方对相同对象的其他引用。

如果需要拷贝,可以明确要求。

  • 没有限制条件的分片表达式(L[:])能够复制序列。
  • 字典 copy 方法(X.copy())能够复制字典。
  • 有些内置函数能够生成拷贝(list(L), dict(D), set(S))。
  • copy 标准库模块能够生成完整拷贝。

无条件值的分片以及字典 copy 方法只能做顶层复制,不能够复制嵌套的数据结构。如果需要一个深层嵌套的数据结构的完整的、完全独立的拷贝,需要使用标准的 copy 模块。

1
2
import copy
X = copy.deepcopy(Y) # 任意嵌套对象做完整复制

3.3 比较、相等性和真值

Python 的比较总是检查复合对象的所有部分。

1
2
3
L1 = [1, ('a', 3)]         # 相同值,不同对象
L2 = [1, ('a', 3)]
L1 == L2, L1 is L2
(True, False)
1
2
3
L1 = [1, ('a', 3)]
L2 = [1, ('a', 2)]
L1 < L2, L1 > L2
(False, True)
  • 数字通过相对大小进行比较。
  • 字符串按照字典顺序,一个字符接一个字符进行比较。
  • 列表和元组从左到右对每部分的内容进行比较。
  • 集合的相对大小比较应用于子集和超集测试。
  • 字典通过排序后的(键、值)列表进行比较。

3.4 Python 中真和假的含义

整数 0 代表假,整数 1 代表真。

任意的空数据结构视为假,任何非空数据结构视为真。

对象真值的例子

对象
“spam” True
“” False
[] False
{} False
1 True
0.0 False

None|False

None 对象

None 是 Python 中一种特殊数据类型的唯一值,一般起到一个空的占位作用。

1
L = [None] * 100

None 不代表“未定义”,None 是某些内容,而不是没有内容,它是一个真正的对象。

bool 类型

bool 只是扩展了 Python 中真、假的概念。

  • 当明确地用在真值测试时,True 和 False 这些文字就变成了 1 和 0。
  • 交互模式下运行的布尔测试的结果打印成 True 和 False 字样,而不是 1 和 0。