Pug\r
Jack Russel Terrier\r
English Springer Spaniel\r
German Shepherd\r
Staffordshire Bull Terrier\r
Cavalier King Charles Spaniel\r
Golden Retriever\r
West Highland White Terrier\r
Boxer\r
Border Terrier\r
这可能会使每行重复出现问题,你可能需要考虑这样的情况。
你可能面临的另一个常见问题是字节数据的编码。编码是从字节数据到人类可读字符的转换。通常通过指定编码的格式来完成。两种最常见的编码是ASCII和UNICODE格式。ASCII只能存储128个字符,而Unicode最多可包含1,114,112个字符。
ASCII实际上是Unicode(UTF-8)的子集,这意味着ASCII和Unicode共享相同的数值字符值。重要的是要注意,使用不正确的字符编码解析文件可能会导致字符转换失败和出错。例如,如果文件是使用UTF-8编码创建的,并且你尝试使用ASCII编码对其进行解析,则如果存在超出这128个值的字符,则会引发错误。
在Python中打开和关闭文件
当你想使用文件时,首先要做的就是打开它。该操作通过调用 open() 内置函数完成的。open()有一个必需的参数,它是文件的路径。open()有一个返回,是这个文件的文件对象:
file = open('dog_breeds.txt')
打开文件后,接下来要学习的是如何关闭它。
警告:你应始终确保正确关闭打开的文件。
重要的是要记住,关闭文件是你的责任。在大多数情况下,在应用程序或脚本终止时,文件最终将被关闭。但是,无法保证实际上将会发生什么。这可能导致不必要的行为,包括资源泄漏。这也是Python(Pythonic)中的最佳实践,以确保你的代码以明确定义的方式运行并减少任何不需要的行为。
当你操作文件时,有两种方法可以确保文件正确关闭,即使遇到错误也是如此。关闭文件的第一种方法是使用try-finally块:
reader = open('dog_breeds.txt')
try:
# Further file processing goes here
finally:
reader.close()
如果你不熟悉try-finally块的内容,请查看Python Exceptions:An Introduction。
关闭文件的第二种方法是使用以下with语句:
with open('dog_breeds.txt') as reader:
# Further file processing goes here
使用 with语句,一旦离开了with块或甚至在错误的情况下,系统也会自动关闭文件。我强烈建议你尽可能使用with语句,因为它的代码更加清晰并使你更容易处理任何意外错误。
最有可能的是,你也想要使用第二个位置参数mode。此参数是一个字符串,其中包含多个字符以表示你要如何打开文件。默认值和最常见的是'r',表示以只读模式将文件作为文本文件打开:
with open('dog_breeds.txt', 'r') as reader:
# Further file processing goes here
其他模式请看在线文档,但最常用的模式如下:
| 模式 | 含义 | | ---------------- | ------------------------------------- | | 'r' | 只读模式打开(默认) | | ‘w’ | 写入模式打开,会覆盖文件 | | 'rb' 或 'wb' | 以二进制模式打开(使用字节数据读/写) |
让我们回过头来谈谈文件对象。文件对象是:
“将面向文件的API(使用read()or write() 等方法)暴露给底层资源的对象。”(来源)
有三种不同类型的文件对象:
缓冲的二进制文件
原始二进制文件
这些中每一种文件类型的都在io模块中定义。这里简要介绍了这三种类型。
文本文件是你将遇到的最常见的文件。以下是一些如何打开这些文件的示例:
open('abc.txt')
open('abc.txt', 'r')
open('abc.txt', 'w')
对于此类型的文件,open()将返回一个TextIOWrapper文件对象:
>>> file = open('dog_breeds.txt')
>>> type(file)
<class '_io.TextIOWrapper'>
这是open()默认返回的文件对象。
缓冲二进制文件类型
缓冲二进制文件类型用于读取和写入二进制文件。以下是一些如何打开这些文件的示例:
open('abc.txt', 'rb')
open('abc.txt', 'wb')
对于此类型的文件,open()将返回一个BufferedReader或BufferedWriter文件对象:
>>> file = open ('dog_breeds.txt' , 'rb' )
>>> type (file )
<class'_io.BufferedReader'>
>>> file = open ('dog_breeds.txt' , 'wb' )
> >> type (file )
<class'_io.BufferedWriter'>
原始文件类型
原始文件类型是:
“通常用作二进制和文本流的低级构建块。”(来源)
因此通常不使用它。
以下是如何打开这些文件的示例:
open('abc.txt', 'rb', buffering=0)
对于此类型的文件,open()将返回一个FileIO文件对象:
>>> file = open('dog_breeds.txt', 'rb', buffering=0)
>>> type(file)
<class '_io.FileIO'>
读写已打开的文件
打开文件后,你将需要读取或写入文件。首先,让我们来阅读一个文件。可以在文件对象上调用多种方法:
| 方法 | 描述 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | .read(size=-1) | 这将根据size字节数从文件中读取。如果没有传递参数或None或-1,那么整个文件被读取。 | | .readline(size=-1) | 这将从该行读取最多size数量的字符。直到到行结尾,然后到下一行。如果没有参数被传递或None或-1,则整行(或行剩余的部分)被读出。 | | | |
使用上面使用过的 dog_breeds.txt 文件,我们来看一些如何使用这些方法的示例。以下是如何使用 .read() 命令打开和读取整个文件的示例:
>>> with open('dog_breeds.txt', 'r') as reader:
>>> # Read & print the entire file
>>> print(reader.read())
Jack Russel Terrier
English Springer Spaniel
German Shepherd
Staffordshire Bull Terrier
Cavalier King Charles Spaniel
Golden Retriever
West Highland White Terrier
Boxer
Border Terrier
这是一个如何使用.readline()在一行中每次读取5个字节的示例:
>>> with open('dog_breeds.txt', 'r') as reader:
>>> # Read & print the first 5 characters of the line 5 times
>>> print(reader.readline(5))
>>> # Notice that line is greater than the 5 chars and continues
>>> # down the line, reading 5 chars each time until the end of the
>>> # line and then "wraps" around
>>> print(reader.readline(5))
>>> print(reader.readline(5))
>>> print(reader.readline(5))
>>> print(reader.readline(5))
Russe
l Ter
译者注:第一次调用reader.readline(5) 实际打印出 Pug\r\n,因此可以看到有输出一个换行
以下是使用.readlines()将整个文件作为列表读取的示例:
>>> f = open('dog_breeds.txt')
>>> f.readlines() # Returns a list object
['Pug\n', 'Jack Russel Terrier\n', 'English Springer Spaniel\n', 'German Shepherd\n', 'Staffordshire Bull Terrier\n', 'Cavalier King Charles Spaniel\n', 'Golden Retriever\n', 'West Highland White Terrier\n', 'Boxer\n', 'Border Terrier\n']
上面的例子也可以通过使用list()从文件对象创建列表来完成:
>>> f = open('dog_breeds.txt')
>>> list(f)
['Pug\n', 'Jack Russel Terrier\n', 'English Springer Spaniel\n', 'German Shepherd\n', 'Staffordshire Bull Terrier\n', 'Cavalier King Charles Spaniel\n', 'Golden Retriever\n', 'West Highland White Terrier\n', 'Boxer\n', 'Border Terrier\n']
迭代文件中的每一行
读取文件时常见的事情是迭代每一行。以下是如何使用.readline()执行该迭代的示例:
>>> with open('dog_breeds.txt', 'r') as reader:
>>> # Read and print the entire file line by line
>>> line = reader.readline()
>>> while line != '': # The EOF char is an empty string
>>> print(line, end='')
>>> line = reader.readline()
Jack Russel Terrier
English Springer Spaniel
German Shepherd
Staffordshire Bull Terrier
Cavalier King Charles Spaniel
Golden Retriever
West Highland White Terrier
Boxer
Border Terrier
迭代文件中每一行的另一种方法是使用.readlines()文件对象。请记住,.readlines()返回一个列表,其中列表中的每个元素代表文件中的一行:
>>> with open('dog_breeds.txt', 'r') as reader:
>>> for line in reader.readlines():
>>> print(line, end='')
Jack Russell Terrier
English Springer Spaniel
German Shepherd
Staffordshire Bull Terrier
Cavalier King Charles Spaniel
Golden Retriever
West Highland White Terrier
Boxer
Border Terrier
但是,通过迭代文件对象本身可以进一步简化上述示例:
>>> with open('dog_breeds.txt', 'r') as reader:
>>> # Read and print the entire file line by line
>>> for line in reader:
>>> print(line, end='')
Jack Russel Terrier
English Springer Spaniel
German Shepherd
Staffordshire Bull Terrier
Cavalier King Charles Spaniel
Golden Retriever
West Highland White Terrier
Boxer
Border Terrier
最后的方法更Pythonic,可以更快,更高效。因此,建议你改用它。
注意:上面的一些示例包含print('some text', end='')。这end=''是为了防止Python为正在打印的文本添加额外的换行符,并仅打印从文件中读取的内容。
现在让我们深入研究文件。与读取文件一样,文件对象有多种方法可用于写入文件:
| 方法 | 描述 | | ---------------- | ------------------------------------------------------------ | | .write(string) | 将字符串写入文件。 | | .writelines(seq) | 将序列写入文件。不会给每个序列项附加结尾符。这会由你来添加适当的结尾符。 |
以下是使用.write()和的简单示例.writelines():
with open('dog_breeds.txt', 'r') as reader:
# Note: readlines doesn't trim the line endings
dog_breeds = reader.readlines()
with open('dog_breeds_reversed.txt', 'w') as writer:
# Alternatively you could use
# writer.writelines(reversed(dog_breeds))
# Write the dog breeds to the file in reversed order
for breed in reversed(dog_breeds):
writer.write(breed)
有时,你可能需要使用字节字符串处理文件。可以通过在mode参数中添加'b'字符来完成。适用于文件对象的所有相同方法。但是,每个方法都期望并返回一个bytes对象:
>>> with open(`dog_breeds.txt`, 'rb') as reader:
>>> print(reader.readline())
b'Pug\n'
使用b标志打开文本文件并不那么有趣。假设我们有一张Jack Russell Terrier(jack_russell.png)的可爱图片:
你可以在Python中打开该文件并检查内容!由于ref="https://en.wikipedia.org/wiki/Portable_Network_Graphics">.png文件格式定义的那样,文件的标题是8个字节,如下所示:
| 值 | 描述 | | -------------- | --------------------------------------- | | 0x89 | 一个“魔术”数字,表示这是一个PNG的开始 | | 0x50 0x4E 0x47 | PNG 的ASCII | | 0x0D 0x0A | DOS样式行结束 \r\n | | 0x1A | DOS风格的EOF字符 | | 0x0A | 一个Unix风格的行结尾 \n |
当打开文件并单独读取这些字节时,可以看到这确实是一个.png头文件:
>>> with open('jack_russell.png', 'rb') as byte_reader:
>>> print(byte_reader.read(1))
>>> print(byte_reader.read(3))
>>> print(byte_reader.read(2))
>>> print(byte_reader.read(1))
>>> print(byte_reader.read(1))
b'\x89'
b'PNG'
b'\r\n'
b'\x1a'
b'\n'
一个完整的例子: dos2unix.py
让我们把知识点整理一下,看看如何读取和写入文件的完整示例。下面是一个dos2unix类似的工具,将其转换一个文件,将它的的行结束\r\n转为\n。
该工具分为三个主要部分。第一个是str2unix()将字符串从\\r\\n行结尾转换为\\n。第二个是dos2unix()将包含\r\n字符的字符串转换为\n。dos2unix()调用str2unix()。最后,有__main__块,只有当文件作为脚本执行时才会调用。
A simple script and library to convert files or strings from dos like
line endings with Unix like line endings.
import argparse
import os
def str2unix(input_str: str) -> str:
r"""\
Converts the string from \r\n line endings to \n
Parameters
----------
input_str
The string whose line endings will be converted
Returns
-------
The converted string
r_str = input_str.replace('\r\n', '\n')
return r_str
def dos2unix(source_file: str, dest_file: str):
"""\
Coverts a file that contains Dos like line endings into Unix like
Parameters
----------
source_file
The path to the source file to be converted
dest_file
The path to the converted file for output
# NOTE: Could add file existence checking and file overwriting
# protection
with open(source_file, 'r') as reader:
dos_content = reader.read()
unix_content = str2unix(dos_content)
with open(dest_file, 'w') as writer:
writer.write(unix_content)
if __name__ == "__main__":
# Create our Argument parser and set its description
parser = argparse.ArgumentParser(
description="Script that converts a DOS like file to an Unix like file",
# Add the arguments:
# - source_file: the source file we want to convert
# - dest_file: the destination where the output should go
# Note: the use of the argument type of argparse.FileType could
# streamline some things
parser.add_argument(
'source_file',
help='The location of the source '
parser.add_argument(
'--dest_file',
help='Location of dest file (default: source_file appended with `_unix`',
default=None
# Parse the args (argparse automatically grabs the values from
# sys.argv)
args = parser.parse_args()
s_file = args.source_file
d_file = args.dest_file
# If the destination file wasn't passed, then assume we want to
# create a new file based on the old one
if d_file is None:
file_path, file_extension = os.path.splitext(s_file)
d_file = f'{file_path}_unix{file_extension}'
dos2unix(s_file, d_file)
技巧和窍门
现在你已经掌握了读取和写入文件的基础知识,这里有一些提示和技巧可以帮助你提高技能。
## __file__
该__file__属性是模块的特殊属性,类似于__name__。它是:
“如果是从文件加载的,它就为加载模块的文件的路径名,”(来源)
注意:__file__返回相对于调用初始Python脚本的路径。如果需要完整的系统路径,可以使用os.getcwd()获取执行代码的当前工作目录。
这是一个真实的例子。在我过去的一份工作中,我对硬件设备进行了多次测试。每个测试都是使用Python脚本编写的,测试脚本文件名用作标题。然后将执行这些脚本并使用__file__特殊属性打印其状态。这是一个示例文件夹结构:
project/
├── tests/
| ├── test_commanding.py
| ├── test_power.py
| ├── test_wireHousing.py
| └── test_leds.py
└── main.py
运行main.py产生以下内容:
>>> python main.py
tests/test_commanding.py Started:
tests/test_commanding.py Passed!
tests/test_power.py Started:
tests/test_power.py Passed!
tests/test_wireHousing.py Started:
tests/test_wireHousing.py Failed!
tests/test_leds.py Started:
tests/test_leds.py Passed!
追加文件内容
有时,你可能希望追加到文件或在已有文件的末尾开始写入。这可以通过在参数mode中追加'a'字符来完成:
with open('dog_breeds.txt', 'a') as a_writer:
a_writer.write('\nBeagle')
当对dog_breeds.txt再次检查时,你将看到文件的开头未更改,Beagle现在已添加到文件的末尾:
>>> with open('dog_breeds.txt', 'r') as reader:
>>> print(reader.read())
Jack Russel Terrier
English Springer Spaniel
German Shepherd
Staffordshire Bull Terrier
Cavalier King Charles Spaniel
Golden Retriever
West Highland White Terrier
Boxer
Border Terrier
Beagle
同时使用两个文件
有时你可能想要读取文件并同时写入另一个文件。如果你使用在学习如何写入文件时显示的示例,它实际上可以合并到以下内容中:
d_path = 'dog_breeds.txt'
d_r_path = 'dog_breeds_reversed.txt'
with open(d_path, 'r') as reader, open(d_r_path, 'w') as writer:
dog_breeds = reader.readlines()
writer.writelines(reversed(dog_breeds))
创建属于你自己的上下文管理器
有时候,你可能需要通过将文件对象放在自定义类中来更好地控制文件对象。执行此操作时,除非添加一些魔术方法,否则无法再使用with语句:通过添加__enter__和__exit__,你将创建所谓的上下文管理器。
__enter__()调用with语句时调用。__exit__()从with语句块退出时被调用。
这是一个可用于制作自定义类的模板:
class my_file_reader():
def __init__(self, file_path):
self.__path = file_path
self.__file_object = None
def __enter__(self):
self.__file_object = open(self.__path)
return self
def __exit__(self, type, val, tb):
self.__file_object.close()
# Additional methods implemented below
现在你已经拥有了带有上下文管理器的自定义类,你可以与使用内置open()那样使用它:
with my_file_reader('dog_breeds.txt') as reader:
# Perform custom class operations
这是一个很好的例子。还记得我们有可爱的Jack Russell形象吗?也许你想打开其他.png文件,但不想每次都解析头文件。这是一个如何做到这一点的例子。此示例还使用自定义迭代器。如果你不熟悉它们,请查看Python迭代器:
class PngReader():
# Every .png file contains this in the header. Use it to verify
# the file is indeed a .png.
_expected_magic = b'\x89PNG\r\n\x1a\n'
def __init__(self, file_path):
# Ensure the file has the right extension
if not file_path.endswith('.png'):
raise NameError("File must be a '.png' extension")
self.__path = file_path
self.__file_object = None
def __enter__(self):
self.__file_object = open(self.__path, 'rb')
magic = self.__file_object.read(8)
if magic != self._expected_magic:
raise TypeError("The File is not a properly formatted .png file!")
return self
def __exit__(self, type, val, tb):
self.__file_object.close()
def __iter__(self):
# This and __next__() are used to create a custom iterator
# See https://dbader.org/blog/python-iterators
return self
def __next__(self):
# Read the file in "Chunks"
# See https://en.wikipedia.org/wiki/Portable_Network_Graphics#%22Chunks%22_within_the_file
initial_data = self.__file_object.read(4)
# The file hasn't been opened or reached EOF. This means we
# can't go any further so stop the iteration by raising the
# StopIteration.
if self.__file_object is None or initial_data == b'':
raise StopIteration
else:
# Each chunk has a len, type, data (based on len) and crc
# Grab these values and return them as a tuple
chunk_len = int.from_bytes(initial_data, byteorder='big')
chunk_type = self.__file_object.read(4)
chunk_data = self.__file_object.read(chunk_len)
chunk_crc = self.__file_object.read(4)
return chunk_len, chunk_type, chunk_data, chunk_crc
你现在可以打开.png文件,并使用自定义上下文管理器正确解析它们:
>>> with PngReader('jack_russell.png') as reader:
>>> for l, t, d, c in reader:
>>> print(f"{l:05}, {t}, {c}")
00013, b'IHDR', b'v\x121k'
00001, b'sRGB', b'\xae\xce\x1c\xe9'
00009, b'pHYs', b'(<]\x19'
00345, b'iTXt', b"L\xc2'Y"
16384, b'IDAT', b'i\x99\x0c('
16384, b'IDAT', b'\xb3\xfa\x9a$'
16384, b'IDAT', b'\xff\xbf\xd1\n'
16384, b'IDAT', b'\xc3\x9c\xb1}'
16384, b'IDAT', b'\xe3\x02\xba\x91'
16384, b'IDAT', b'\xa0\xa99='
16384, b'IDAT', b'\xf4\x8b.\x92'
16384, b'IDAT', b'\x17i\xfc\xde'
16384, b'IDAT', b'\x8fb\x0e\xe4'
16384, b'IDAT', b')3={'
01040, b'IDAT', b'\xd6\xb8\xc1\x9f'
00000, b'IEND', b'\xaeB`\x82'
不要重复造轮子
在处理文件时可能会遇到常见情况。大多数情况可以使用其他模块处理。您可能需要使用的两种常见文件类型是.csv和.json。Real Python已经汇总了一些关于如何处理这些内容的精彩文章:
用Python读写CSV文件
在Python中使用JSON数据
此外,还有内置库,可以使用它们来帮助你:
wave:读写WAV文件(音频)
aifc:读写AIFF和AIFC文件(音频)
sunau:读取和写入Sun AU文件
tarfile:读取和写入tar归档文件
zipfile:使用ZIP存档
configparser:轻松创建和解析配置文件
xml.etree.ElementTree:创建或读取基于XML的文件
msilib:读取和写入Microsoft Installer文件
plistlib:生成并解析Mac OS X .plist文件
还有更多的东西。此外,PyPI还有更多第三方工具可用。一些流行的是以下:
PyPDF2:PDF工具包
xlwings:读取和写入Excel文件
Pillow:图像阅读和操作
你现在知道如何使用Python处理文件,包括一些高级技术。使用Python中的文件现在比以往任何时候都更容易,当你开始这样做时,这是一种有益的感觉。
在本教程中,你已经了解到:
什么是文件
如何正确打开和关闭文件
如何读写文件
使用文件时的一些高级技术
一些库使用常见的文件类型