6. Python中文件的奇淫巧技
1. 如果想要打开一个文件的话,直接使用open函数就可以了
Open函数中支持不同的模式,包含读取 写入 如果想要以文本格式进行读取,则可以使用rt模式进行打开
with open(‘somefile.txt’, ‘rt’) as f:
data = f.read() 以及 with open(‘somefile.txt’, ‘rt’) as f: for line in f: |
同理,如果想要写入一个文件的话,可以使用wt模式,只不过这种模式下,文件写入会覆盖之前的内容
如果是追加写入的话,则应该使用at模式
而且,在open函数内部,还支持传入编码格式,默认情况下,读写会采用系统编码
sys.getdefaultencoding(),直接使用如下
with open(‘somefile.txt’, ‘rt’, encoding=’latin-1′) as f:
…
对于open函数,可以不使用with open这种格式
但是需要注意,如果不使用with open的格式的话,就需要手动进行关闭
其次在open 函数内部,也支持指定换行符,默认的话会使用系统默认的换行符,比如Unix的 \n 和 Window的 \r\n 如果不希望这样换行,则可以传入 newline参数来转换下
with open(‘somefile.txt’, ‘rt’, newline=”) as f:
最后open函数还有一个errors的参数,方便进行错误处理,我们可以指定当错误出现的时候怎么办
比如指定errors忽略
>>> g = open(‘sample.txt’, ‘rt’, encoding=’ascii’, errors=’ignore’)
>>> g.read()
2. 输出到某一个文件中
如果希望输出到某一个文件中,则可以使用print函数,在其中指定file关键字
with open(‘d:/work/test.txt’, ‘wt’) as f:
print(‘Hello World!’, file=f)
3. print的其他使用
我们可以使用print输出的时候,指定分隔符和行尾符,在print函数中使用sep和end关键词参数,可以指定分隔符和结尾
>>> print(‘ACME’, 50, 91.5, sep=’,’)
ACME,50,91.5
>>> print(‘ACME’, 50, 91.5, sep=’,’, end=’!!\n’)
ACME,50,91.5!!
4. 读写字节数据
如果希望读写二进制文件,则可以在open的时候指定模式为rb或者wb,比如
with open(‘somefile.bin’, ‘rb’) as f:
data = f.read() # Write binary data to a file with open(‘somefile.bin’, ‘wb’) as f: f.write(b’Hello World’) |
5. open的x模式
当我们希望只有文件不存在的时候才会进行读写的话,那么我们可以使用x模式,其要求这个文件在系统上不存在的时候才可以进行写入。wt和xb对应的是文本模式写入和二进制模式写入,
>>> with open(‘somefile’, ‘xt’) as f:
… f.write(‘Hello\n’)
当然,另一个完美解决方案是使用 os.path.exists函数
这个函数会检测文件是否存在
>>> if not os.path.exists(‘somefile’):
with open(‘somefile’, ‘wt’) as f:
f.write(‘Hello\n’)
6. 字符串的IO操作
如果希望使用和文件类似的读取方式去读取字符串的话
可以使用io.StringIO 以及 io.BytesIO来创建一个对象从而以操作文件方式来操作字符串
>>> s = io.StringIO()
>>> s.write(‘Hello World\n’)
>>> print(‘This is a test’, file=s)
>>> s.getvalue()
‘Hello World\nThis is a test\n’
>>> s = io.StringIO(‘Hello\nWorld\n’)
>>> s.read(4)
‘Hell’
>>> s.read()
‘o\nWorld\n’
这个函数非常适合在单元测试中,模拟读取文件,配合使用某些参数为文件的函数
7. 读写压缩文件
如果想要读写gzip和bz2格式的压缩文件的话
那么可以使用gzip或者bz2模块,都提供了open函数
如果想要以文本的形式来读取这种压缩文件,则可以
import gzip
with gzip.open(‘somefile.gz’, ‘rt’) as f: text = f.read() # bz2 compression import bz2 with bz2.open(‘somefile.bz2’, ‘rt’) as f: text = f.read() |
同样为了写入压缩数据,则可以
import gzip
with gzip.open(‘somefile.gz’, ‘wt’) as f: f.write(text) # bz2 compression import bz2 with bz2.open(‘somefile.bz2’, ‘wt’) as f: f.write(text) |
默认情况下,gzip和bz2的打开模式都是二进制模式,如果是希望是文本数据,则应该指定模式,除了基本的读取格式,其也是可以指定encoding errors 以及 newline这些参数的
当写入压缩数据的时候,可以使用compresslevel来指定压缩级别,比如
with gzip.open(‘somefile.gz’, ‘wt’, compresslevel=5) as f:
f.write(text)
8. 读取固定大小的文件
一般来说,在python中读取文件,都是以行为单位的,如果想要读取固定大小的数据,则可以使用iter和functools.partial() 函数
from functools import partial
RECORD_SIZE = 32 with open(‘somefile.data’, ‘rb’) as f: records = iter(partial(f.read, RECORD_SIZE), b”) for r in records: |
其中iter会创建一个迭代器,会一直调用直到返回标记值为止,此时迭代终止
而functools.partial就是创建一个每次被调用的时候从文件中读取固定数据字节的可调用对象,标记值 b” 就是达到结尾的时候的返回值
一般来说,这种读取方式都是用于的二进制打开的文件,如果是文本文件,则建议按行读取
9. 读取到内存
接下来来点比较神乎其技的东西
如果想要读取二进制文件到一个可变缓冲区中,则可以使用readinto方法
def read_into_buffer(filename):
buf = bytearray(os.path.getsize(filename))
with open(filename, ‘rb’) as f:
f.readinto(buf)
return buf
我们创建了一个byte数组,然后将文件中的内容读取到了byte数组中
这样就可以避免在读取文件的时候多次占用内存,可以反复的利用同一块内存块进行读取,比如
record_size = 32 # Size of each record (adjust value)
buf = bytearray(record_size) with open(‘somefile’, ‘rb’) as f: while True: n = f.readinto(buf) if n < record_size: break |
而且我们还可以使用memoryview进行配合
利用内存指针的方式指向了原有的数组,通过这种方式来进行上下文传递
>>> buf
bytearray(b’Hello World’)
>>> m1 = memoryview(buf)
>>> m2 = m1[-5:]
>>> m2[:] = b’WORLD’
>>> buf
除此外,还可以使用mmap来内存映射文件,将文件直接映射到一个可变字节数组中,进行访问以及修改
直接使用可以如下
mmap.mmap(fd, size, access=mmap.ACCESS_WRITE)
我们可以对这个返回值直接进行操作
>>> m[0:10]
>>> m[0:11] = b’Hello World’
>>> m.close()
>>> with open(‘data’, ‘rb’) as f:
print(f.read(11))
我们上面指定的access方式是可以读写
如果希望只读则是 mmap.ACCESS_READ
如果希望只是在本地修改数据,而不影响到原始文件,则可以使用mmap.ACCESS_COPY
需要注意的是,内存映射一个文件并不会导致整个文件读取到内存中,而是选择读取某部分的时候,加载某部分。这些都是透明的
10. os中的函数
os.path目录下的存在一些函数来获取文件名,目录名,绝对路径等信息
比如我们使用basename来获取到这个路径对应的文件名
>>> path = ‘/Users/beazley/Data/data.csv’
>>> os.path.basename(path)
‘data.csv’
使用dirname获取对应的目录名称
>>> os.path.dirname(path)
‘/Users/beazley/Data’
利用join进行路径的拼接
>>> os.path.join(‘tmp’, ‘data’, os.path.basename(path))
‘tmp/data/data.csv’
利用expanduser,从当前路径获取到绝对路径
>>> path = ‘~/Data/data.csv’
>>> os.path.expanduser(path)
‘/Users/beazley/Data/data.csv’
利用splitext来分割路径
>>> os.path.splitext(path)
(‘~/Data/data’, ‘.csv’)
上文我们提到过的exists来检测文件或者目录是否存在
>>> os.path.exists(‘/etc/passwd’)
以及检测是否是文件或者文件夹的isfile isdir 这些函数
>>> os.path.isfile(‘/etc/passwd’)
>>> os.path.isdir(‘/etc/passwd’)
>>> os.path.islink(‘/usr/local/bin/python3’)
>>> os.path.realpath(‘/usr/local/bin/python3’)
除此之外,还可以获取到文件的大小
>>> os.path.getsize(‘/etc/passwd’)
以及获取到修改日期
>>> os.path.getmtime(‘/etc/passwd’)
1272478234.0
>>> time.ctime(os.path.getmtime(‘/etc/passwd’))
‘Wed Apr 28 13:10:34 2010’
利用os.stat() 可以获取到全量数据
>>> meta = os.stat(name)
>>> print(name, meta.st_size, meta.st_mtime)
os.path下的函数能够有效的兼容不同的系统区别,比如window和unix,他们的路径表达
最后唯一需要注意的是文件权限
如果想要获取到文件系统中某个目录下的所有文件列表
可以使用os.listdir获取
names = os.listdir(‘somedir’)
返回一个文件名列表,比如下面的代码,我们就过滤获取到所有的目录
dirnames = [name for name in os.listdir(‘somedir’)
if os.path.isdir(os.path.join(‘somedir’, name))]
除此外,我们还可以使用glob来进行匹配
import glob
pyfiles = glob.glob(‘somedir/*.py’)
11.python中的文件分层
在python中,文件主要由以下的层级组成
>>> f = open(‘sample.txt’,’w’)
>>> f <_io.TextIOWrapper name=’sample.txt’ mode=’w’ encoding=’UTF-8′> >>> f.buffer <_io.BufferedWriter name=’sample.txt’> >>> f.buffer.raw <_io.FileIO name=’sample.txt’ mode=’wb’> |
在其中,io.TextIOWrapper是一个编码和解码的Unicode的文本处理层
BufferedWriter是处理二进制数据的缓冲IO层,io.FileIO是操作底层文件描述符的原始层
如果我们想要修改,一个文件的编解码的话,直接替换最外面的TextIOWrapper即可
不过需要注意,不能直接在BufferWriter层外面包裹TextIOWrapper,而是应该先利用detach方法断开文件最顶层,获取第二层
>>> b = f.detach()
>>> b
>>> f = io.TextIOWrapper(b, encoding=’latin-1′)
>>> f
<_io.TextIOWrapper name=’sample.txt’ encoding=’latin-1′>
>>>
那么如果我们想要将原始的字节数据写入到文件中,那么直接获取文件的bufferedWriter进行写入即可
>>> sys.stdout.buffer.write(b’Hello\n’)
上面就是我们获取到的标准输出的bufferWriter进行的输出
12. 将文件描述符包装为文件对象
直接使用open函数即可
client_in = open(client_sock.fileno(), ‘rt’, encoding=’latin-1′,closefd=False)
需要注意的是,后面的closed是当高层的文件对象被破坏的时候,底层描述符是否关闭
13. 创建临时文件和文件夹
如果希望创建一些临时文件或者文件夹供测试使用,可以考虑tempfile模块
比如创建一个临时文件,可以直接
with TemporaryFile(‘w+t’) as f:
这样在超过代码域之后,这个文件会被自动清理
其中的w+t是指的文本模式操作,除此外还有w+b
当然这函数创建的文件是匿名的,如果希望不是匿名的,可以使用NamedTemporaryFile()来代替
with NamedTemporaryFile(‘w+t’) as f:
print(‘filename is:’, f.name)
如果想要创建一个临时目录,可以使用
with TemporaryDirectory() as dirname:
print(‘dirname is:’, dirname)
当然这些都会自动清理,如果不希望自动清理
则可以使用mkstemp或者mkdtemp来创建临时文件或则目录
这些只会返回一个原始的OS文件描述符
当然,这些文件都只会在系统默认的位置被创建,想要获取这些位置,可以使用
tempfile.gettempdir() 函数
>>> tempfile.gettempdir()
‘/var/folders/7W/7WZl5sfZEF0pljrEB1UMWE+++TI/-Tmp-‘
>>>
14. 串行端口的数据通信
使用pySerial包可以实现,这里简单介绍下,基本没有使用机会
直接打开一个端口的代码如下
ser = serial.Serial(‘/dev/tty.usbmodem641′, # Device name varies
baudrate=9600,
bytesize=8,
parity=’N’,
stopbits=1)
15. python自带的序列化工具
如果希望将一个对象序列化为一个字节流,则可以使用pickle模块,主要涉及的函数主要由dumps和loads两个函数
dump可以将一个对象序列化并存储在一个文件中
load则是可以从文件中序列化对象回来
pickle支持序列化多个对象
>>> pickle.dump({‘Apple’, ‘Pear’, ‘Banana’}, f)
但是需要注意,其序列化和反序列化的时候,会尝试导入类相关信息,这就导致,如果不是同一个源数据,那么会出现错误
对应自定义类,首先__getstate__和__setstate__即可
最后提一嘴,如果只是对数据进行序列化和反序列化,使用后XML,JSON, CSV 会更好