Python 文件、数据库与上下文管理操作指南
1. 文本文件处理基础
在 Python 中处理文本文件时,我们可以指定缓冲方式,还能设置 Unicode 解码错误的处理方式。Unicode 错误处理有七种选择,具体如下:
| 处理方式 | 说明 |
| ---- | ---- |
| strict | 引发异常 |
| ignore | 悄悄丢弃非法字符 |
| replace | 提供替换策略 |
| xmlcharrefreplace | 提供替换策略 |
| backslashreplace | 提供替换策略 |
| surrogateescape | 提供替换策略 |
下面是使用
open()
函数创建文件对象的示例:
my_file = open("Chapter_10/10letterwords.txt")
text = my_file.read().splitlines()
print(text[:5])
在这个例子中,我们使用了所有默认设置打开文件,模式为只读,文件使用系统默认编码(如 Mac - Roman),依赖默认缓冲和严格的 Unicode 错误处理。我们将整个文件读入一个大字符串,然后将其拆分为单独的行列表,并将该列表赋值给
text
变量,最后显示列表的前五项。默认情况下,
split()
方法不会保留分隔字符。
2. 文本行过滤
接下来,我们看两个关键概念。首先,以 “utf - 8” 编码打开文件:
code_file = open("Chapter_1/ch01_ex1.py", "rt", encoding="utf - 8", errors="replace")
code_lines = list(code_file)
print(code_lines[:5])
这里使用 “rt” 模式打开文件,即只读文本模式(这是默认模式,可省略),并明确指定了 “utf - 8” 编码,它不是操作系统的默认编码。
使用
list()
函数将文件对象转换为行序列。当把文件对象当作可迭代对象使用时,文件会逐行迭代。若不更改文件的换行设置,则使用 “通用换行符” 规则:
\n
、
\r
或
\r\n
会结束一行,并统一为
\n
。处理文件行时,行尾字符会被保留。
通常我们需要去除每行末尾的换行符,这可以使用生成器表达式或
map()
函数和
str.rstrip()
方法。在某些情况下,空行无意义可删除,这可以通过带有
if
子句的生成器表达式或
filter()
函数实现。以下是具体代码:
txt_stripped = (line.rstrip() for line in code_file)
txt_non_empty = (line for line in txt_stripped if line)
code_lines = list(txt_non_empty)
我们将输入清理操作分解为两个生成器表达式。第一个
txt_stripped
将原始行映射为去除尾部空白的行,第二个
txt_non_empty
过滤掉空行。由于生成器表达式是惰性的,直到最后调用
list()
函数才会真正执行操作。
3. 处理原始字节
以下是打开文件查看原始字节的方法:
raw_bytes = open("Chapter_10/favicon.ico", "rb")
data = raw_bytes.read()
print(len(data))
print(data[:22])
这里以二进制模式打开文件,得到的输入是字节而不是字符串。由于字节对象和字符串对象有很多相似特性,我们可以对这些字节进行类似字符串的处理。
要理解这些字节的含义,需要查看 ICO 文件格式的描述。解码这些字节最简单的方法是使用
struct
模块:
import struct
print(struct.unpack("<hhhbbbbhhii", data[:22]))
unpack()
函数需要一个格式字符串来指定对字节流进行的转换类型。在这个例子中,
h
表示两字节的半字,
b
表示单字节,
i
表示四字节整数。字节被组装成数值,结果是一个包含 Python 整数的元组。格式字符串开头的
<
表示使用小端字节序进行整数转换。
4. 使用类文件对象
在 Python 中,任何提供与文件类相似接口的对象都可以替代文件使用,这就是 “类文件对象”。例如,
io
模块的
StringIO
类允许我们将字符串当作文件内容来处理。
以下是一个函数,它对文件或类文件对象的行应用简单的模式匹配,提取复杂文本行中的数值:
import re
def tests_run(log_file):
data_pat = re.compile(r"\s*([\w ]+):\s+(\d+\.?\d*)\s*")
for line in log_file:
match = data_pat.findall(line)
if match:
yield match
我们定义了一个生成器函数,它会将日志文件缩减为与给定模式匹配的几行。使用
re
模块定义了一个模式
data_pat
,用于查找一串单词、一个
:
字符和一个整数或浮点数。
data_pat.findall(line)
表达式会在给定行中定位所有这些单词和数字对。
下面是使用
io.StringIO
对象进行单元测试的示例:
import io
data = io.StringIO('''
Tests run: 1, Failures: 2, Errors: 0, Skipped: 1, Time elapsed: 0.547 sec
Other data
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 sec
''')
print(list(tests_run(data)))
我们创建了一个
io.StringIO
对象,包含模拟输入,将其提供给
tests_run()
函数进行测试。
5. 使用
with
语句的上下文管理器
Python 文件对象通常与操作系统资源相关联,使用完文件后,需要确保文件正确关闭以释放操作系统资源。对于小型命令行应用程序,这可能不太重要;但对于大型、长时间运行的服务器,未正确关闭的文件会累积操作系统资源,最终导致问题。
一般做法是使用上下文管理器确保文件在使用完后关闭。
with
语句可用于指定上下文,文件对象是上下文管理器,
with
语句将文件作为管理器。
with
语句结束时,上下文管理器会退出并关闭文件。一些更复杂的文件结构也是上下文管理器,如
zipfile
模块中的
ZipFile
对象。
以下是使用上下文管理器调用
tests_run()
函数的示例:
file_in = "Chapter_10/log_example.txt"
file_out = "Chapter_10/summary.txt"
with open(file_in) as source, open(file_out, "w") as target:
for stats in tests_run(source):
print(stats, file=target)
我们打开两个文件作为上下文管理器,一个用于读取,一个用于写入。这样处理文件可以确保它们正确关闭,即使发生异常,文件也会关闭。
6. 使用
contextlib
关闭类文件对象
有些情况下,我们希望确保应用程序关闭未实现上下文管理器方法的类文件对象,如
http.client
模块创建的
HTTPConnection
对象,它可能与网络资源相关联。由于该对象不是合适的上下文管理器,在
with
语句中使用时不会自动关闭,会引发
AttributeError
异常。
我们可以使用
contextlib
模块的通用上下文管理器。
contextlib.closing()
函数可以将任何具有
close()
方法的对象包装成上下文管理器。以下是一个 RESTful 网络服务请求的示例:
import contextlib
import http.client
with contextlib.closing(http.client.HTTPConnection("www.example.com")) as host:
host.request("GET", "/path/to/resources/12345/")
response = host.getresponse()
print(response.read())
通过将
HTTPConnection
对象包装在
contextlib.closing()
函数中,我们确保了在请求和处理响应时,该对象的
close()
方法会被正确调用。
7. 使用
shelve
模块作为数据库
文件提供持久存储,但简单使用文件时,数据必须按顺序访问。为了能以任意顺序访问数据,我们引入 “数据库” 的概念。
shelve
模块提供了一个非常灵活的数据库,
shelf
对象行为类似于普通的 Python 映射,且内容是持久的,不过键必须是字符串。
以下是创建一个将键映射到值列表的
shelf
的示例函数:
import contextlib
import shelve
def populate():
with contextlib.closing(shelve.open("Chapter_10/shelf", "n")) as shelf:
with open("Chapter_10/10letterwords.txt") as source:
txt_stripped = (l.strip() for l in source)
txt_non_empty = (l for l in txt_stripped if l and not l.startswith("Tool"))
for word in txt_non_empty:
key = "word_list:{0}".format(word[0])
try:
word_list = shelf[key]
except KeyError:
word_list = []
word_list.append(word)
shelf[key] = word_list
在这个函数中,使用
shelve.open()
打开
shelf
对象,“n” 模式每次运行应用程序时都会创建一个新的空
shelf
文件。由于
shelf
不是合适的上下文管理器,我们使用
contextlib.closing()
函数进行包装。
通过循环遍历输入的单词序列,构建两部分键,获取与键关联的单词列表。若键不存在,则创建一个新的空列表,添加新单词后保存到
shelf
中。要查询以特定字母开头的单词,可以使用
shelf["word_list:" + letter]
。
8. 使用
sqlite
数据库
sqlite
模块提供了一个基于 SQL 的数据库。原则上,利用 SQL 的应用程序是可移植的,但不同的 SQL 实现可能存在问题,所以 SQL 应用程序在不同数据库平台之间很少能完全移植。
SQL 数据库需要正式的模式定义,因此 SQL 应用程序必须包含创建或确认模式的规定。以下是一个包含两列的单表数据库的示例:
CREATE TABLE IF NOT EXISTS word(
letter VARCHAR(1),
word VARCHAR(10),
PRIMARY KEY (letter)
)
这个表有两列:
letter
和
word
。要查找以相同首字母开头的所有单词,需要从该表中检索多行。
以下是建立(或确认)模式的函数:
import sqlite3 as SQL
def schema():
with SQL.connect("Chapter_10/sqlite.sdb") as db:
db.execute("""CREATE TABLE IF NOT EXISTS word(
letter VARCHAR(1),
word VARCHAR(10),
PRIMARY KEY (letter))
""")
以及将文本文件数据加载到表中的函数:
import sqlite3 as SQL
def populate():
with SQL.connect("Chapter_10/sqlite.sdb") as db:
db.execute("""DELETE FROM word""")
with open("Chapter_10/10letterwords.txt") as source:
txt_stripped = (l.strip() for l in source)
txt_non_empty = (l for l in txt_stripped if l and not l.startswith("Tool"))
for word in txt_non_empty:
db.execute("""INSERT INTO WORD(letter, word)
VALUES (:1, :2)""", (word[0], word))
要查看以给定字母开头的单词数量,可以使用 SQL 聚合,执行以下
SELECT
语句:
SELECT letter, COUNT(*) FROM word GROUP BY letter
执行该语句会得到一个 SQL 迭代器(“游标”),它会根据
SELECT
子句生成一个二元组序列,每个元组包含字母和以该字母开头的单词数量。
Python 文件、数据库与上下文管理操作指南(续)
9. 总结与对比
在前面的内容中,我们介绍了多种文件处理、数据库操作以及上下文管理的方法。下面对这些方法进行总结和对比,以便更好地理解和选择合适的工具。
| 工具/模块 | 特点 | 适用场景 |
|---|---|---|
文本文件处理(
open()
)
| 基本的文件打开和读取方式,可指定编码、错误处理等 | 简单的文本文件读取和处理 |
类文件对象(如
io.StringIO
)
| 可以将字符串当作文件处理,方便测试 | 单元测试、模拟文件输入 |
shelve
模块
| 提供持久的 Python 映射,键必须为字符串 | 需要简单持久化存储,且数据访问模式类似字典 |
sqlite
数据库
| 基于 SQL 的数据库,有正式的模式定义 | 数据需要结构化存储,有复杂的查询需求 |
10. 操作流程梳理
为了更清晰地展示各个操作的流程,下面使用 mermaid 流程图进行梳理。
graph TD;
A[开始] --> B[选择操作类型];
B --> C{文件处理};
B --> D{数据库操作};
C --> E[打开文件];
C --> F[读取/写入文件];
C --> G[关闭文件];
D --> H[选择数据库类型];
H --> I{shelve 数据库};
H --> J{sqlite 数据库};
I --> K[打开 shelve];
I --> L[操作 shelve 数据];
I --> M[关闭 shelve];
J --> N[连接 sqlite 数据库];
J --> O[创建/确认表结构];
J --> P[插入/查询数据];
J --> Q[关闭数据库连接];
G --> R[结束];
M --> R;
Q --> R;
这个流程图展示了从开始到结束的整个操作流程,包括文件处理和数据库操作的不同分支。
11. 注意事项
在使用上述工具和方法时,需要注意以下几点:
1.
文件关闭
:无论是普通文件还是数据库连接,都要确保在使用完后正确关闭,避免资源泄漏。可以使用
with
语句或
contextlib.closing()
来自动处理关闭操作。
2.
编码问题
:在处理文本文件时,要明确指定编码,避免出现解码错误。特别是在不同操作系统之间传输文件时,编码差异可能会导致问题。
3.
数据库模式
:使用
sqlite
数据库时,要确保表结构的正确性,避免插入数据时出现类型不匹配等问题。
4.
键的类型
:使用
shelve
数据库时,键必须是字符串,否则会引发错误。
12. 示例扩展
下面我们对之前的示例进行扩展,展示如何结合不同的方法进行更复杂的操作。假设我们要从一个文本文件中读取数据,将数据存储到
sqlite
数据库中,同时使用
shelve
数据库进行简单的统计。
import contextlib
import shelve
import sqlite3 as SQL
# 读取文件并存储到 sqlite 数据库
def populate_sqlite():
with SQL.connect("Chapter_10/sqlite.sdb") as db:
db.execute("""CREATE TABLE IF NOT EXISTS word(
letter VARCHAR(1),
word VARCHAR(10),
PRIMARY KEY (letter))
""")
db.execute("""DELETE FROM word""")
with open("Chapter_10/10letterwords.txt") as source:
txt_stripped = (l.strip() for l in source)
txt_non_empty = (l for l in txt_stripped if l and not l.startswith("Tool"))
for word in txt_non_empty:
db.execute("""INSERT INTO WORD(letter, word)
VALUES (:1, :2)""", (word[0], word))
# 统计每个首字母的单词数量并存储到 shelve 数据库
def populate_shelve():
with contextlib.closing(shelve.open("Chapter_10/shelf", "n")) as shelf:
with SQL.connect("Chapter_10/sqlite.sdb") as db:
cursor = db.execute("SELECT letter, COUNT(*) FROM word GROUP BY letter")
for row in cursor:
letter, count = row
key = "count:{0}".format(letter)
shelf[key] = count
# 执行操作
populate_sqlite()
populate_shelve()
# 查看 shelve 数据库中的统计结果
with contextlib.closing(shelve.open("Chapter_10/shelf")) as shelf:
for key in sorted(shelf.keys()):
print(key, shelf[key])
在这个示例中,我们首先将文本文件中的数据存储到
sqlite
数据库中,然后从
sqlite
数据库中查询每个首字母的单词数量,并将结果存储到
shelve
数据库中。最后,我们查看
shelve
数据库中的统计结果。
13. 总结
通过本文的介绍,我们学习了 Python 中文件处理、数据库操作以及上下文管理的多种方法。这些方法各有特点,适用于不同的场景。在实际应用中,我们可以根据具体需求选择合适的工具和方法,同时要注意编码、资源管理等问题,确保程序的正确性和稳定性。希望这些内容能帮助你更好地处理文件和数据,提高编程效率。
以上就是关于 Python 文件、数据库与上下文管理操作的详细介绍,希望对你有所帮助。如果你有任何问题或建议,欢迎留言讨论。
超级会员免费看
71万+

被折叠的 条评论
为什么被折叠?



