python进阶三-文件与IO

简介

在实际项目中,文件读写是比较常见的操作,本文将基于python-cookbook进行讲解。

1.读写文本数据

文件的读写主要使用open函数,主要涉及到读写的文件路径、对文件的操作、操作文件的编码、不同平台的换行符等,常见写法如下:

def read_file(file_path):
    with open(file_path, mode="r", encoding="utf-8") as r_f:
        for line in r_f:
            print(line)
            if not line:
                break

详细可以查看python文件操作

2.将打印输出写入文件

就是将print的输出输出到文件中, 如下是print的相关参数,print含义其实就是将value输入到指定file的文本流,默认是系统输出。

print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
  • end:末尾符号,默认是换行符,
  • file:指定的文本流输出位置,参数必须是一个具有 write(string) 方法的对象;如果参数不存在或为 None,则将使用 sys.stdout,例如:
print('hello, world', file=open(file, "w"))
  • flush: 输出是否缓存通常取决于 file,但如果 flush 关键字参数为 True,输出流会被强制刷新。
    因此将打印输出到文件,只需要指定file参数即可,需要注意的是指定的file需要有write的方法,也就是需要使用open读取对象。

3.设置输出的不同字符间的分隔符以及尾部符号

上述我们已经知道了print()方法有哪些参数,因此我们本题的答案就在这些参数中。
如下是设置分隔符的示例,我们只需要设置sep的符号就可以设置不同输出文字之间的输出了,需要注意的是此参数适用于多个字符输出情况,只有单个字符串时是不会生效的。

print("hello", "world", sep=",")
hello,world

如下是设置尾符号的示例,使用end参数设置尾符号,默认为\n,通过这个参数就可以在输出时不换行了

print("hello, world", end="")
print("hello, world", end="")
hello, worldhello, world

4.读取字节数据

我们之前都是读取文本数据进行处理,然而除了直接读取文本数据之外,我们也可以通过读取字节数据,不过需要对字节数据进行编码和解码,否则会出现乱码的情况,示例如下:

with open(file_path, mode="rb") as r_f:
    for line in r_f:
        print(line.decode("utf-8"))
        if not line:
            break

读取字节数据只需要将mode后添加一个b即可,但是不能设置encoding参数,并且在对于读取的结果处理过程中需要将数据进行解码,才能得到正常的数据(arraryC结构体类型除外)。
readinto()方法可以将二进制数据读取到内存当中去

5.文件不存在时写入数据,存在则不写入数据

这个问题实际上就是事先进行文件的存在判断,再根据是否存在做相应处理,代码如下:

from os.path import exists

if not exists(file_path):
    with open(file_path, mode="w", encoding="utf-8") as w_f:
        for i in range(1000):
            w_f.write(str(i) + "\n")

上述方法确实可以实现相应的功能,但是在open方法中的mode提供了一种模式x方便了这种场景的处理, 当然如果需要使用字节时,只需要添加b即可,代码如下:

with open(file_path, mode="x", encoding="utf-8") as w_f:
    for i in range(1000):
        w_f.write(str(i) + "\n")

当文件存在时写入会出现如下异常,我们可以捕捉异常处理

FileExistsError: [Errno 17] File exists:
try:
    with open(file_path, mode="x", encoding="utf-8") as w_f:
        for i in range(1000):
            w_f.write(str(i) + "\n")
except FileExistsError:
    print("file exists")

6.字符串的IO操作

在有些情况下,我们可以使用类文件对象来操作字符串或者字节,如下

from io import StringIO, BytesIO

s = StringIO("hello")
print(type(s))
print(s.getvalue())
b = BytesIO(b"hello")
print(b.getvalue())

这些类文件对象具有open方法中的大部分方法,StringIO是针对于文本数据的,BytesIO是针对于字节数据的。

7.读写压缩文件(不常用)

当我们需要读写gz文件或者bz2压缩文件时,我们需要使用到gzbz2模块,详细示例如下

import gzip


file_path = r"C:\Users\ts\Desktop\2022.7\2022.8.11\test.gz"

with gzip.open(file_path, mode="wt") as w_f:
    w_f.write("hello")

with gzip.open(file_path, mode="rt") as r_f:
    for line in r_f:
        print(line)
        if not line:
            break

import bz2

file_path = r"C:\Users\ts\Desktop\2022.7\2022.8.11\test.bz2"

with bz2.open(file_path, mode="wt") as w_f:
    w_f.write("hello")

with bz2.open(file_path, mode="rt") as r_f:
    for line in r_f:
        print(line)
        if not line:
            break

上述分别使用了gzbz2模块进行了读写文本数据到压缩文件,需要注意的是,当不指定是文本还是二进制时,默认是二进制模式

import gzip

file_path = r"C:\Users\ts\Desktop\2022.7\2022.8.11\test.gz"

with gzip.open(file_path, mode="w") as w_f:
    w_f.write(b"hello")

with gzip.open(file_path, mode="r") as r_f:
    for line in r_f:
        print(line)
        if not line:
            break
b'hello'

除此之外,gzbz2可以对文件对象进行操作,即使是open打开的文件对象,例如套接字、管道、内存中文件

import gzip

file_path = r"C:\Users\ts\Desktop\2022.7\2022.8.11\test.gz"

with gzip.open(file_path, mode="w") as w_f:
    w_f.write(b"hello")

with gzip.open(open(file_path, "rb"), mode="rt") as r_f:
    for line in r_f:
        print(line)
        if not line:
            break

8.读取文件的固定大小内容

看到这个问题,其实就是对于文件的分块读写,我们在读取时设置size来确保读取的文件大小,如下:

from functools import partial

SIZE = 16

with open(file_path, mode="rb") as r_f:
    for line in iter(partial(r_f.read, SIZE), b''):
        if not line:
            break
        print(line)

这样就是进行了分块读取,partial(r_f.read, SIZE)可以创建一个可调用对象其实就是创建了一个新的函数,可以使用lambda: r_f.read(SIZE)替代,iter将可调用对象和标记值,生成一个迭代器,会不断的去调用可调用对象,直到出现标记值停止及可调用对象停止,相当于循环调用r_f.read(SIZE).
需要注意的是上述是分块读取的是二进制文件,如果需要对文本读取建议还是一行一行的去读取比较稳妥。

9.读取二进制数据进可变缓冲区 ?

当我们需要读取二进制文件直接写入缓冲区,我们可以使用readinto函数进行读取写入数据

with open(test_path, "wb") as w_f:
    w_f.write(b'hello world')

buff = None
with open(test_path, mode="rb") as r_f:
    buff = bytearray(16)
    ret = r_f.readinto(buff)
    print(ret)

print(buff)

buff = None
with open(test_path, mode="rb") as r_f:
    buff = bytearray(8)
    ret = r_f.readinto(buff)
    print(ret)

print(buff)
11
bytearray(b'hello world\x00\x00\x00\x00\x00')
8
bytearray(b'hello wo')

由结果可以直到,读取数据后可直接写入到缓冲区或者某个文件中,相当于open()了一个文件对象,通过read读取写入到文件中。读取的字节数取决于原始数据的长度以及读取进入到文件的长度。
如果需要修改二进制数据之后又写入到文件中,那该怎么做呢?
可以使用到memoryview对二进制数据进行切片

with open(test_path, "wb") as w_f:
    w_f.write(b"hello, world")

buff = None
with open(test_path, mode="rb") as r_f:
    buff = bytearray(16)
    ret = r_f.readinto(buff)
    print(ret)
12
bytearray(b'hello, world\x00\x00\x00\x00')
<memory at 0x00000257F47ADE40>
<memory at 0x00000257F47ADCC0>
bytearray(b'testo, world\x00\x00\x00\x00')
8
bytearray(b'hello, w')

memoryview可以对二进制数据进行切片并可以修改数据对原二进制数据会产生影响。

注意事项

  • 使用readinto进行读写时会返回读取的字节数,如果读取的字节数<缓冲区大小,则此数据会被截断

10.内存映射到二进制文件

11.文件路径名的操作

我们在实际开发中免不了需要对文件或者文件夹路径进行操作,我们平常会使用os.path来获取当前路径、文件是否存在等以及路径拼接等,除此之外,我目前比较喜欢也比较习惯的是pathlib模块,它其中包含了os.path的大部分功能且使用起来也较为方便,也是内置模块,推荐大家使用pathlib
pathlib操作:pathlib
os操作:os

12.测试文件是否存在

同11点查阅os、pathlib相关操作

13.获取文件夹中的文件列表

同11点查阅os、pathlib相关操作
获取文件列表的方法有只能获取当前文件夹下的文件及文件夹,也有可以获取到文件夹下所有的文件及文件夹的,当然也可以通过递归去实现多文件夹或者多文件的操作。

14.忽略文件编码

默认情况下,当对一个文件进行操作时会使用当前系统的默认编码,可能很多编码为gbk2312,导致读写时出现编码错误问题,因此我们

15.打印不合法的文件名

16.增加或改变已打开文件的编码

17.将字节写入文本文件

正常情况下,我们会对于文件的文本进行操作,将字节写入文件,我们首先想到的是在写入文件时直接设置模式wb,这样会导致只能写入字节内容,其实在文本写入过程中也可以进行字节的写入,使用buffer进行字节数据的写入,示例如下:

from pathlib import Path

file_path = Path(__file__).parent.joinpath("test.txt")

with open(file_path, mode="wt", encoding="utf-8") as w_f:
    for i in range(10):
        w_f.write(str(i) + "\n")
    w_f.buffer.write(b"hello\n")

hello
0
1
2
3
4
5
6
7
8
9

由结果可知,字节流的位置与文本流的位置是不同的,后写的字节竟然到了文本的前面,我们可以手动获取当前流的位置后再移到对应位置即可。

from pathlib import Path

file_path = Path(__file__).parent.joinpath("test.txt")

with open(file_path, mode="wt", encoding="utf-8") as w_f:
    for i in range(10):
        w_f.write(str(i) + "\n")
    w_f.seek(w_f.tell())
    w_f.buffer.write(b"hello\n")

0
1
2
3
4
5
6
7
8
9
hello

由此可见,结果趋于正常了。
文本文件是通过在一个拥有缓冲的二进制模式文件上增加一个Unicode编码/解码层来创建。 buffer 属性指向对应的底层文件。如果你直接访问它的话就会绕过文本编码/解码层。

18.将文件描述符包装成文件对象

19.创建临时文件和临时文件夹

可能在某些场景下,我们写入的某些内容只是临时使用,在后面可能需要手动删除,例如在安装过程中产生的临时文件,需要删除等。这就需要我们使用tempfile模块进行处理。
基础使用

  • TemporaryFile是基础的创建临时文件的方法。
  • NamedTemporaryFileTemporaryFile类似,不过是可以获得创建的临时文件的名称,这两个方法在创建完临时文件后会自动删除
  • 还有一个是SpooledTemporaryFile, 它的使用方法大体与TemporaryFile基本一致,区别在于,它有一个max_size参数,它一开始的写入是在缓存中,只有大小超过了max_size了才会写入文件,它有一个方法可以将缓存中的内容立即写入到文件中, 就是rollover, 需要注意:
    • 在 Posix 或 Cygwin 以外的平台(笼统讲,就是linux)上,TemporaryFile 是 NamedTemporaryFile 的别名。
from tempfile import TemporaryFile

with TemporaryFile(mode="w+t", encoding="utf-8") as w_f:
    w_f.write("hello, world")
    w_f.write("Testing")
    w_f.seek(0)
    print(w_f.name)
    data = w_f.read()
    print(data)

from tempfile import NamedTemporaryFile

with NamedTemporaryFile(mode="w+t", encoding="utf-8") as w_f:
    w_f.write("hello, world")
    w_f.write("Testing")
    w_f.seek(0)
    print(w_f.name)
    data = w_f.read()
    print(data)
..\Local\Temp\tmpf4_yhpak
hello, worldTesting

TemporaryDirectory创建临时文件夹,它也是可以进行上下文处理的,它可以直接输出对应的临时文件夹的位置

with TemporaryDirectory() as tmpdir:
    print(tmpdir)
Local\Temp\tmppfe10t5h

tempfile.gettempdir()可以获取临时文件的目录,不同平台会有所不同

  • 在 Windows 上,依次为 C:\TEMP、C:\TMP、\TEMP 和 \TMP。正常为C:\Users\user\AppData\Local\Temp\tmp*。
  • 在所有其他平台上,依次为 /tmp、/var/tmp 和 /usr/tmp。

20.串口通信

在一些开发中,我们可能需要进行刷机,进入串口进行读写数据,这里推荐使用pyserial

安装

pip install pyserial

基本使用

import serial
ser = serial.Serial('/dev/tty.usbmodem641', # Device name varies
                    baudrate=9600,
                    bytesize=8,
                    parity='N',
                    stopbits=1)

之后就可以使用read,write进行读写数据了
官方文档:serial官方文档

21.序列化python对象

参考

python3-cookbook 文件与IO章节

posted @ 2022-08-24 19:38  形同陌路love  阅读(79)  评论(0编辑  收藏  举报