19、Python 文件、数据库与上下文管理操作指南

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 文件、数据库与上下文管理操作的详细介绍,希望对你有所帮助。如果你有任何问题或建议,欢迎留言讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值