22、Python 输入输出操作全解析

Python 输入输出操作全解析

1. 环境变量

有时,数据会通过命令外壳中设置的环境变量传递给程序。例如,可以使用如下 shell 命令启动 Python 程序:

bash $ env SOMEVAR=somevalue python3 somescript.py

在 Python 中,可以通过 os.environ 映射以文本字符串的形式访问环境变量。示例代码如下:

import os
path = os.environ['PATH']
user = os.environ['USER']
editor = os.environ['EDITOR']
val = os.environ['SOMEVAR']

若要修改环境变量,可设置 os.environ 变量,示例如下:

os.environ['NAME'] = 'VALUE'

os.environ 的修改会影响正在运行的程序以及后续创建的任何子进程,比如由 subprocess 模块创建的子进程。与命令行选项一样,编码错误的环境变量可能会产生使用 'surrogateescape' 错误处理策略的字符串。

2. 文件和文件对象

2.1 打开文件

使用内置的 open() 函数打开文件,通常需要提供文件名和文件模式,并且常与 with 语句结合使用作为上下文管理器。以下是一些常见的文件操作模式:

# 一次性将文本文件作为字符串读取
with open('filename.txt', 'rt') as file:
    data = file.read()

# 逐行读取文件
with open('filename.txt', 'rt') as file:
    for line in file:
        ...

# 写入文本文件
with open('out.txt', 'wt') as file:
    file.write('Some output\n')
    print('More output', file=file)

常见的文件打开模式如下表所示:
| 模式 | 描述 |
| ---- | ---- |
| open('name.txt') | 以只读模式打开 “name.txt” |
| open('name.txt', 'rt') | 以文本读取模式打开 “name.txt”(与上一种相同) |
| open('name.txt', 'wt') | 以文本写入模式打开 “name.txt” |
| open('data.bin', 'rb') | 以二进制读取模式打开 “data.bin” |
| open('data.bin', 'wb') | 以二进制写入模式打开 “data.bin” |

2.2 文件名

打开文件时,需要给 open() 函数提供文件名,文件名可以是完全指定的绝对路径名,如 '/Users/guido/Desktop/files/old/data.csv' ,也可以是相对路径名,如 'data.csv' '..\old\data.csv' 。对于相对文件名,文件位置相对于 os.getcwd() 返回的当前工作目录确定,可使用 os.chdir(newdir) 更改当前工作目录。

文件名的编码形式有多种:
- 若为文本字符串,在传递给主机操作系统之前,将根据 sys.getfilesystemencoding() 返回的文本编码进行解释。
- 若为字节字符串,则不进行编码,直接传递。这种方式在处理可能存在退化或编码错误的文件名时很有用。

此外,任何实现了特殊方法 __fspath__() 的对象都可以用作文件名,该方法必须返回对应实际名称的文本或字节对象,这也是 pathlib 等标准库模块的工作机制。示例如下:

from pathlib import Path
p = Path('Data/portfolio.csv')
p.__fspath__()

最后,文件名可以是低级整数文件描述符,但这要求“文件”已在系统中以某种方式打开,可能对应网络套接字、管道或其他暴露文件描述符的系统资源。示例如下:

import os
fd = os.open('/etc/passwd', os.O_RDONLY)
file = open(fd, 'rt')
data = file.read()

当以这种方式打开现有文件描述符时,返回文件的 close() 方法也会关闭底层描述符,可通过传递 closefd=False open() 来禁用此功能,示例如下:

file = open(fd, 'rt', closefd=False)

2.3 文件模式

打开文件时,需要指定文件模式,核心文件模式有:
- 'r' :读取模式。
- 'w' :写入模式,会替换任何现有文件的内容。
- 'a' :追加模式,打开文件进行写入,并将文件指针定位到文件末尾,以便追加新数据。

特殊的文件模式 'x' 用于写入文件,但仅当文件不存在时可用,若文件已存在,会引发 FileExistsError 异常。

Python 严格区分文本和二进制数据,可在文件模式后追加 't' 'b' 来指定数据类型,例如 'rt' 以文本读取模式打开文件, 'rb' 以二进制读取模式打开文件。模式决定了文件相关方法(如 f.read() )返回的数据类型,文本模式返回字符串,二进制模式返回字节。

二进制文件可通过提供加号 (+) 字符以进行原地更新,如 'rb+' 'wb+' 。当以更新模式打开文件时,只要所有输出操作在任何后续输入操作之前刷新其数据,就可以同时进行输入和输出。若以 'wb+' 模式打开文件,其长度首先会被截断为零。更新模式常用于结合 seek 操作对文件内容进行随机读写访问。

2.4 I/O 缓冲

默认情况下,文件以启用 I/O 缓冲的方式打开,I/O 操作以较大块进行,以避免过多的系统调用。例如,写入操作会先填充内部内存缓冲区,只有当缓冲区填满时才会实际输出。可以通过给 open() 函数提供 buffering 参数来更改此行为,示例如下:

# 以无 I/O 缓冲的方式打开二进制模式文件
with open('data.bin', 'wb', buffering=0) as file:
    file.write(data)

buffering 参数取值说明如下:
- 0 :指定无缓冲 I/O,仅适用于二进制模式文件。
- 1 :指定行缓冲,通常仅对文本模式文件有意义。
- 其他正值:表示要使用的缓冲区大小(以字节为单位)。

若未指定 buffering 值,默认行为取决于文件类型。如果是磁盘上的普通文件,缓冲以块为单位管理,缓冲区大小设置为 io.DEFAULT_BUFFER_SIZE ,通常是 4096 字节的小倍数,可能因系统而异。如果文件表示交互式终端,则使用行缓冲。

对于普通程序,I/O 缓冲通常不是主要问题,但在涉及进程间主动通信的应用程序中,缓冲可能会产生影响。例如,有时会出现两个通信子进程因内部缓冲问题而死锁的情况,可通过指定无缓冲 I/O 或在关联文件上显式调用 flush() 方法来解决,示例如下:

file.write(data)
file.write(data)
...
file.flush()

2.5 文本模式编码

对于以文本模式打开的文件,可以使用 encoding errors 参数指定可选的编码和错误处理策略,示例如下:

with open('file.txt', 'rt',
          encoding='utf-8', errors='replace') as file:
    data = file.read()

encoding errors 参数的值与字符串和字节的 encode() decode() 方法的含义相同。默认文本编码由 sys.getdefaultencoding() 的值决定,可能因系统而异。若事先知道编码,即使它与系统上的默认编码匹配,也最好显式提供。

2.6 文本模式行处理

对于文本文件,换行符的编码是一个复杂问题。换行符根据主机操作系统编码为 '\n' '\r\n' '\r' ,例如,UNIX 上为 '\n' ,Windows 上为 '\r\n' 。默认情况下,Python 在读取时会将所有这些行结尾转换为标准的 '\n' 字符,写入时会将换行符转换回系统上使用的默认行结尾,这种行为在 Python 文档中有时被称为“通用换行模式”。

可以通过给 open() 函数提供 newline 参数来更改换行行为,示例如下:

# 精确要求 '\r\n' 并保持不变
file = open('somefile.txt', 'rt', newline='\r\n')

newline 参数取值说明如下:
- None :启用默认行处理行为,将所有行结尾转换为标准的 '\n' 字符。
- '' :使 Python 识别所有行结尾,但禁用转换步骤,若行以 '\r\n' 结尾, '\r\n' 组合将原封不动地保留在输入中。
- '\n' '\r' '\r\n' :将该值作为预期的行结尾。

3. I/O 抽象层

open() 函数可看作是创建不同 I/O 类实例的高级工厂函数,这些类体现了不同的文件模式、编码和缓冲行为,并且以分层的方式组合在一起。 io 模块中定义了以下类:
- FileIO(filename, mode='r', closefd=True, opener=None) :以原始无缓冲二进制 I/O 方式打开文件, filename 可以是 open() 函数接受的任何有效文件名,其他参数与 open() 含义相同。
- BufferedReader(file [, buffer_size]) BufferedWriter(file [, buffer_size]) BufferedRandom(file [, buffer_size]) :为文件实现缓冲二进制 I/O 层, file FileIO 的实例, buffer_size 指定要使用的内部缓冲区大小,类的选择取决于文件是用于读取、写入还是更新数据。
- TextIOWrapper(buffered, [encoding, [errors [, newline [, line_buffering [, write_through]]]]]) :实现文本模式 I/O, buffered 是缓冲二进制模式文件,如 BufferedReader BufferedWriter encoding errors newline 参数与 open() 含义相同, line_buffering 是布尔标志,强制在换行符处刷新 I/O(默认值为 False ), write_through 是布尔标志,强制所有写入操作立即刷新(默认值为 False )。

以下是一个逐层构建文本模式文件的示例:

import io
raw = io.FileIO('filename.txt', 'r')
buffer = io.BufferedReader(raw)
file = io.TextIOWrapper(buffer, encoding='utf-8')

通常不需要手动构建这些层,内置的 open() 函数会处理所有工作。但如果已有一个现有的文件对象,并且想以某种方式更改其处理方式,可以按上述方式操作。可以使用文件的 detach() 方法剥离层,例如将已有的文本模式文件转换为二进制模式文件:

f = open('something.txt', 'rt')
fb = f.detach()
data = fb.read()

4. 文件方法

open() 函数返回的对象的确切类型取决于提供的文件模式和缓冲选项的组合,但生成的文件对象支持以下方法:
| 方法 | 描述 |
| ---- | ---- |
| f.readable() | 如果文件可以读取,返回 True |
| f.read([n]) | 最多读取 n 字节 |
| f.readline([n]) | 读取最多 n 个字符的单行输入,若省略 n ,则读取整行 |
| f.readlines([size]) | 读取所有行并返回一个列表, size 可选地指定在停止读取文件之前要读取的近似字符数 |
| f.readinto(buffer) | 将数据读取到内存缓冲区中 |
| f.writable() | 如果文件可以写入,返回 True |
| f.write(s) | 写入字符串 s |
| f.writelines(lines) | 写入可迭代对象 lines 中的所有字符串 |
| f.close() | 关闭文件 |
| f.seekable() | 如果文件支持随机访问查找,返回 True |
| f.tell() | 返回当前文件指针 |
| f.seek(offset [, where]) | 移动到新的文件位置 |
| f.isatty() | 如果 f 是交互式终端,返回 True |
| f.flush() | 刷新输出缓冲区 |
| f.truncate([size]) | 将文件截断为最多 size 字节 |
| f.fileno() | 返回整数文件描述符 |

readable() writable() seekable() 方法用于测试文件支持的功能和模式。 read() 方法返回整个文件作为字符串,除非提供可选的长度参数指定最大字符数。 readline() 方法返回下一行输入,包括终止换行符; readlines() 方法将所有输入文件作为字符串列表返回。 readline() 方法可选地接受最大行长 n ,如果读取的行超过 n 个字符,将返回前 n 个字符,剩余的行数据不会被丢弃,将在后续读取操作中返回。 readlines() 方法接受 size 参数,指定在停止之前要读取的近似字符数,实际读取的字符数可能会更大,具体取决于已经缓冲的数据量。 readinto() 方法用于避免内存复制。

read() readline() 方法通过返回空字符串来指示文件结束(EOF),以下是检测 EOF 条件的代码示例:

while True:
    line = file.readline()
    if not line:
        break
    statements
    ...

也可以使用如下代码:

while (line:=file.readline()):
    statements
    ...

使用 for 循环迭代是读取文件中所有行的便捷方式:

for line in file:
    ...

write() 方法将数据写入文件, writelines() 方法将可迭代的字符串写入文件, write() writelines() 方法不会在输出中添加换行符,因此生成的所有输出应已经包含所有必要的格式。

每个打开的文件对象内部都有一个文件指针,存储下一次读取或写入操作将发生的字节偏移量。 tell() 方法返回当前文件指针的值, seek(offset [, whence]) 方法用于根据整数偏移量和 whence 中的放置规则随机访问文件的部分内容。如果 whence os.SEEK_SET (默认值), seek() 假设偏移量相对于文件开头;如果 whence os.SEEK_CUR ,位置相对于当前位置移动;如果 whence os.SEEK_END ,偏移量从文件末尾开始计算。

fileno() 方法返回文件的整数文件描述符,有时用于某些库模块中的低级 I/O 操作,例如 fcntl 模块在 UNIX 系统上使用文件描述符提供低级文件控制操作。

5. 标准输入、输出和错误

解释器提供了三个标准类文件对象,分别是标准输入、标准输出和标准错误,可通过 sys.stdin sys.stdout sys.stderr 访问。 stdin 是对应提供给解释器的输入字符流的文件对象, stdout 是接收 print() 产生的输出的文件对象, stderr 是接收错误消息的文件。通常, stdin 映射到用户的键盘,而 stdout stderr 在屏幕上输出文本。

可以使用前面章节描述的方法与用户进行 I/O 操作,例如,以下代码向标准输出写入内容并从标准输入读取一行输入:

import sys
sys.stdout.write("Enter your name: ")
name = sys.stdin.readline()

另外,内置函数 input(prompt) 可以从 stdin 读取一行文本,并可选地打印提示信息。

综上所述,Python 提供了丰富的输入输出操作功能,涵盖环境变量、文件操作、I/O 抽象层以及标准输入输出等多个方面。通过合理运用这些功能,可以高效地完成各种数据处理和交互任务。

下面是一个简单的 mermaid 流程图,展示文件打开和操作的基本流程:

graph TD;
    A[开始] --> B[设置环境变量];
    B --> C[打开文件];
    C --> D{文件模式};
    D -- 读取 --> E[读取文件内容];
    D -- 写入 --> F[写入文件内容];
    D -- 追加 --> G[追加文件内容];
    E --> H[处理文件内容];
    F --> H;
    G --> H;
    H --> I[关闭文件];
    I --> J[结束];

文件对象的常用方法可以通过以下表格进一步总结:
| 方法分类 | 方法名称 | 描述 |
| ---- | ---- | ---- |
| 文件属性判断 | readable() | 判断文件是否可读 |
| | writable() | 判断文件是否可写 |
| | seekable() | 判断文件是否支持随机访问 |
| 文件读写操作 | read([n]) | 读取最多 n 字节 |
| | readline([n]) | 读取一行输入 |
| | readlines([size]) | 读取所有行并返回列表 |
| | readinto(buffer) | 读取数据到内存缓冲区 |
| | write(s) | 写入字符串 s |
| | writelines(lines) | 写入可迭代字符串 |
| 文件指针操作 | tell() | 返回当前文件指针 |
| | seek(offset [, where]) | 移动文件指针 |
| 其他操作 | close() | 关闭文件 |
| | isatty() | 判断是否为交互式终端 |
| | flush() | 刷新输出缓冲区 |
| | truncate([size]) | 截断文件 |
| | fileno() | 返回整数文件描述符 |

通过对这些内容的学习和实践,你可以更加熟练地运用 Python 进行各种输入输出操作,提高编程效率和数据处理能力。

6. 输入输出操作的实际应用场景

6.1 数据处理与分析

在数据处理和分析领域,Python 的输入输出操作至关重要。例如,从 CSV 文件中读取数据进行分析,或者将分析结果保存到新的文件中。以下是一个简单的示例,展示如何读取 CSV 文件并计算某列的总和:

import csv

total = 0
with open('data.csv', 'rt', encoding='utf-8') as file:
    reader = csv.reader(file)
    next(reader)  # 跳过标题行
    for row in reader:
        value = float(row[1])  # 假设第二列是需要计算的数据
        total += value

print(f"总和为: {total}")

# 将结果保存到新文件
with open('result.txt', 'wt', encoding='utf-8') as out_file:
    out_file.write(f"总和为: {total}")

6.2 日志记录

在开发应用程序时,日志记录是一项重要的功能。可以使用 Python 的输入输出操作将程序运行过程中的信息记录到日志文件中。以下是一个简单的日志记录示例:

import datetime

def log_message(message):
    now = datetime.datetime.now()
    log_time = now.strftime("%Y-%m-%d %H:%M:%S")
    with open('app.log', 'at', encoding='utf-8') as log_file:
        log_file.write(f"{log_time}: {message}\n")

# 记录日志
log_message("程序开始运行")
try:
    # 模拟一些操作
    result = 1 / 0
except ZeroDivisionError:
    log_message("发生除零错误")
log_message("程序结束运行")

6.3 配置文件读取

许多应用程序需要读取配置文件来获取一些参数设置。可以使用 Python 的输入输出操作读取配置文件,例如 INI 格式的配置文件。以下是一个读取 INI 配置文件的示例:

import configparser

config = configparser.ConfigParser()
config.read('config.ini')

# 获取配置项
database_host = config.get('database', 'host')
database_port = config.getint('database', 'port')
database_user = config.get('database', 'user')
database_password = config.get('database', 'password')

print(f"数据库主机: {database_host}")
print(f"数据库端口: {database_port}")
print(f"数据库用户: {database_user}")
print(f"数据库密码: {database_password}")

7. 输入输出操作的注意事项

7.1 异常处理

在进行输入输出操作时,可能会遇到各种异常,如文件不存在、权限不足等。因此,需要进行适当的异常处理。以下是一个读取文件时进行异常处理的示例:

try:
    with open('nonexistent_file.txt', 'rt', encoding='utf-8') as file:
        data = file.read()
except FileNotFoundError:
    print("文件未找到,请检查文件路径。")
except PermissionError:
    print("没有权限访问该文件。")

7.2 资源管理

使用 with 语句可以确保文件在使用完毕后自动关闭,避免资源泄漏。例如:

with open('test.txt', 'rt', encoding='utf-8') as file:
    data = file.read()
# 文件会在 with 语句块结束后自动关闭

7.3 编码问题

在处理文本文件时,需要注意文件的编码格式。如果编码格式不正确,可能会导致乱码。建议在打开文件时明确指定编码格式,例如:

with open('file.txt', 'rt', encoding='utf-8') as file:
    data = file.read()

8. 总结

Python 的输入输出操作涵盖了环境变量、文件操作、I/O 抽象层、标准输入输出等多个方面,为开发者提供了丰富的功能。通过合理运用这些功能,可以高效地完成各种数据处理和交互任务。

在实际应用中,需要根据具体需求选择合适的文件模式、缓冲方式和编码格式,并注意异常处理和资源管理。同时,了解文件对象的各种方法和属性,能够更加灵活地操作文件。

以下是一个 mermaid 流程图,展示整个输入输出操作的流程:

graph LR;
    A[开始] --> B{选择输入方式};
    B -- 环境变量 --> C[读取环境变量];
    B -- 文件 --> D[打开文件];
    B -- 标准输入 --> E[读取标准输入];
    C --> F[处理数据];
    D --> F;
    E --> F;
    F --> G{选择输出方式};
    G -- 文件 --> H[写入文件];
    G -- 标准输出 --> I[输出到标准输出];
    H --> J[关闭文件];
    I --> K[结束];
    J --> K;

通过对 Python 输入输出操作的深入学习和实践,开发者可以更好地掌握数据的读取和写入,提高编程效率和程序的健壮性。希望本文能够帮助你更好地理解和应用 Python 的输入输出操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值