本文为译文,原文连接 read-write-files-python 本人博客: 编程禅师html
使用Python作的最多见的任务是读取和写入文件。不管是写入简单的文本文件,读取复杂的服务器日志,仍是分析原始的字节数据。全部这些状况都须要读取或写入文件。python
在本教程中,你将学习:程序员
本教程主要面向初学者到中级的Python开发者,可是这里有一些提示,更高级的程序员也能够从中获益。shell
在咱们开始研究如何使用Python中的文件以前,了解文件到底是什么以及现代操做系统如何处理它们的某些方面是很是重要的。编程
从本质上讲,文件是用于存储数据的连续字节集。这些数据以特定格式组织,能够是任何像文本文件同样简单的数据,也能够像程序可执行文件同样复杂。最后,这些字节文件被翻译成二进制文件1
,0
以便计算机更容易处理。json
大多数现代文件系统上的文件由三个主要部分组成:bash
数据表示的内容取决于所使用的格式规范,一般由扩展名表示。例如,扩展名为.gif
的文件最可能符合图形交换格式规范。有数百个(若是不是数千个)文件扩展名。对于本教程,你将只处理.txt
或.csv
文件扩展名。服务器
在操做系统上访问文件时,须要文件路径。文件路径是表示文件位置的字符串。它分为三个主要部分:app
/
(Unix)或反斜杠\
(Windows)分隔.
),用于表示文件类型这是一个简单的例子。假设你有一个位于文件结构中的文件,以下所示:函数
/
│
├── path/
| │
│ ├── to/
│ │ └── cats.gif
│ │
│ └── dog_breeds.txt
|
└── animals.csv
复制代码
假设您要访问该cats.gif
文件,而且你当前的位置与文件夹中path
平级。要访问该文件,你须要浏览该path
文件夹,而后查看to
文件夹,最后到达该cats.gif
文件。文件夹路径是path/to/
。文件名是cats
。文件扩展名是.gif
。因此完整的道路是path/to/cats.gif
。
如今假设你当前的位置或当前工做目录(cwd)位于咱们的示例文件夹结构的to
文件夹中。能够经过文件名和扩展名简单地引用文件cats.gif
,而不是引用cats.gif
完整路径path/to/cats.gif
。
/
│
├── path/
| │
| ├── to/ ← Your current working directory (cwd) is here
| │ └── cats.gif ← Accessing this file
| │
| └── dog_breeds.txt
|
└── animals.csv
复制代码
但对于dog_breeds.txt
如何进行访问呢?若是不使用完整路径,你将如何访问?你可使用特殊字符双点(..
)来向前移动一个目录。这意味着能够在to
目录使用../dog_breeds.txt
引用dog_breeds.txt
文件。
/
│
├── path/ ← Referencing this parent folder
| │
| ├── to/ ← Current working directory (cwd)
| │ └── cats.gif
| │
| └── dog_breeds.txt ← Accessing this file
|
└── animals.csv
复制代码
双点(..
)能够链接在一块儿以遍历当前目录以前的多个目录。例如,在to
文件夹中要访问animals.csv
,你将使用../../animals.csv
。
处理文件数据时常常遇到的一个问题是新行或行结尾的表示。行结尾起源于莫尔斯电码时代,使用一个特定的符号被用来表示传输的结束或一行的结尾。
后来,国际标准化组织(ISO)和美国标准协会(ASA)对电传打字机进行了标准化。ASA标准规定行尾应使用回车(序列CR
或\r
)和换行(LF
或\n
)字符(CR+LF
或\r\n
)。然而,ISO标准容许CR+LF
字符或仅LF
字符。
Windows使用CR+LF
字符表示新行,而Unix和较新的Mac版本仅使用LF
字符。当你处理来源于不一样操做系统上的文件时,这可能会致使一些复杂状况。这是一个简单的例子。假设咱们检查在Windows系统上建立的文件dog_breeds.txt
:
Pug\r\n
Jack Russel Terrier\r\n
English Springer Spaniel\r\n
German Shepherd\r\n
Staffordshire Bull Terrier\r\n
Cavalier King Charles Spaniel\r\n
Golden Retriever\r\n
West Highland White Terrier\r\n
Boxer\r\n
Border Terrier\r\n
复制代码
一样的输出将在Unix设备上以不一样方式解释:
Pug\r
\n
Jack Russel Terrier\r
\n
English Springer Spaniel\r
\n
German Shepherd\r
\n
Staffordshire Bull Terrier\r
\n
Cavalier King Charles Spaniel\r
\n
Golden Retriever\r
\n
West Highland White Terrier\r
\n
Boxer\r
\n
Border Terrier\r
\n
复制代码
这可能会使每行重复出现问题,你可能须要考虑这样的状况。
你可能面临的另外一个常见问题是字节数据的编码。编码是从字节数据到人类可读字符的转换。一般经过指定编码的格式来完成。两种最多见的编码是ASCII和UNICODE格式。ASCII只能存储128个字符,而Unicode最多可包含1,114,112个字符。
ASCII其实是Unicode(UTF-8)的子集,这意味着ASCII和Unicode共享相同的数值字符值。重要的是要注意,使用不正确的字符编码解析文件可能会致使字符转换失败和出错。例如,若是文件是使用UTF-8编码建立的,而且你尝试使用ASCII编码对其进行解析,则若是存在超出这128个值的字符,则会引起错误。
当你想使用文件时,首先要作的就是打开它。该操做经过调用 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()
orwrite()
等方法)暴露给底层资源的对象。”(来源)
有三种不一样类型的文件对象:
这些中每一种文件类型的都在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())
Pug
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))
Pug
Jack
Russe
l Ter
rier
复制代码
译者注:第一次调用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()
Pug
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='')
Pug
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='')
Pug
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中打开该文件并检查内容!因为.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
相似的工具,将其转换一个文件,将它的的行结束\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())
Pug
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
pass
复制代码
这是一个很好的例子。还记得咱们有可爱的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已经汇总了一些关于如何处理这些内容的精彩文章:
此外,还有内置库,可使用它们来帮助你:
.plist
文件还有更多的东西。此外,PyPI还有更多第三方工具可用。一些流行的是如下:
你如今知道如何使用Python处理文件,包括一些高级技术。使用Python中的文件如今比以往任什么时候候都更容易,当你开始这样作时,这是一种有益的感受。
在本教程中,你已经了解到:
关注公众号 <代码与艺术>,学习更多国外精品技术文章。