python 文件操作
open 打开文件
open(file,mode)
>>> f = open('E:\\test\\pythontest.txt','r')
>>> f
<_io.TextIOWrapper name='E:\\test\\pythontest.txt' mode='r' encoding='cp936'>
1>文件打开模式
打开模式 | 执行操作 |
'r' | 以只读方式打开文件(默认) |
'w' | 以写入的方式打开文件,会覆盖已存在的文件 |
'x' | 如果文件已经存在,使用此模式打开将引发异常 |
'a' | 以写入模式打开,如果文件存在,则在末尾追加写入 |
'b' | 以二进制模式打开文件 |
't' | 以文本模式打开(默认) |
'+' | 可读写模式(可添加到其他模式中使用) |
'U' | 通用换行符支持 |
2>文件对象方法
文件对象方法 | 执行操作 |
f.close() | 关闭文件 |
f.read([size=-1]) | 从文件读取size个字符,当未给定size或给定负值的时候,读取剩余的所有字符,然后作为字符串返回 |
f.readline([size=-1]) | 从文件中读取并返回一行(包括行结束符),如果有size有定义则返回size个字符 |
f.write(str) | 将字符串str写入文件 |
f.writelines(seq) | 向文件写入字符串序列seq,seq应该是一个返回字符串的可迭代对象 |
f.seek(offset, from) | 在文件中移动文件指针,从from(0代表文件起始位置,1代表当前位置,2代表文件末尾)偏移offset个字节 |
f.tell() | 返回当前在文件中的位置 |
f.truncate([size=file.tell()]) | 截取文件到size个字节,默认是截取到文件指针当前位置 |
readinto()
文件对象的 readinto() 方法能被用来为预先分配内存的数组填充数据,甚至包括由 array 模块或 numpy 库创建的数组。和普通 read() 方法不同的是, readinto() 填充已存在的缓冲区而不是为新对象重新分配内存再返回它们。因此,你可以使用它来避免大量的内存分配操作。
import os.path
def read_into_buffer(filename):
buf = bytearray(os.path.getsize(filename))
with open(filename, 'rb') as f:
f.readinto(buf)
return buf
3>按行输出打印
>>> f.seek(0,0)
0
>>> for each_line in f:
print(each_line)
也可以先转为列表再打印,但是效率不高
>>> lines = list(f)
>>> for each_line in lines:
print(each_line)
例子
将两人的聊天记录,分开保存在不同的文件内
def save_file(boy, girl,count):
file_name_boy = 'boy_' + str(count) + '.txt'
file_name_girl = 'girl_' + str(count) + '.txt'
boy_file = open(file_name_boy,'w')
girl_file = open(file_name_girl,'w')
boy_file.writelines(boy)
girl_file.writelines(girl)
boy_file.close()
girl_file.close()
def split_file(file_name):
f = open('record.txt')
boy = []
girl = []
count = 1
for each_line in f:
if each_line[:6] != '======':
(role,line_spoken) = each_line.split(':',1)
if role == 'xiaoming':
boy.append(line_spoken)
if role == 'xiaohua'
girl.append(line_spoken)
else:
save_file(boy,girl,count)
boy = []
girl = []
count += 1
save_file(boy,girl,count)
f.close()
with语句
with 语句给被使用到的文件创建了一个上下文环境,但 with 控制块结束时,文件会自动关闭。不使用 with 语句,但是这时候你就必须记得手动关闭文件
使用带有 rt 模式的 open() 函数读取文本文件
# Read the entire file as a single string
with open('somefile.txt', 'rt') as f:
data = f.read()
# Iterate over the lines of the file
with open('somefile.txt', 'rt') as f:
for line in f:
# process line
...
写入一个文本文件,使用带有 wt 模式的 open() 函数,如果之前文件内容存在则清除并覆盖掉
# Write chunks of text data
with open('somefile.txt', 'wt') as f:
f.write(text1)
f.write(text2)
...
# Redirected print statement
with open('somefile.txt', 'wt') as f:
print(line1, file=f)
print(line2, file=f)
...
在已存在文件中添加内容,使用模式为 at 的 open() 函数,在末尾追加写入
文件的读写操作默认使用系统编码,可以通过调用 sys.getdefaultencoding() 来得到。在大多数机器上面都是 utf-8 编码。如果你已经知道你要读写的文本是其他编码方式,那么可以通过传递一个可选的 encoding 参数给 open() 函数。
with open('somefile.txt', 'rt', encoding='latin-1') as f:
...
Python 支持非常多的文本编码。
几个常见的编码是 ascii, latin-1, utf-8 和 utf-16。在 web 应用程序中通常都使用的是 UTF-8。 ascii 对应从 U+0000 到 U+007F 范围内的 7 位字符。 latin-1 是字节 0-255 到 U+0000 至 U+00FF 范围内 Unicode 字符的直
接映射。当读取一个未知编码的文本时使用 latin-1 编码永远不会产生解码错误。使用latin-1 编码读取一个文件的时候也许不能产生完全正确的文本解码数据,但是它也能从中提取出足够多的有用数据。同时,如果你之后将数据回写回去,原先的数据还是会
保留的。
关于换行符的识别问题
在 Unix 和 Windows 中是不一样的 (分别是 \n 和 \r\n )。默认情况下, Python 会以统一模式处理换行符。这种模式下,在读取文本的时候, Python 可以识别所有的普通换行符并将其转换为单个 \n 字符。类似的,在输出时会将换行符 \n 转换为系统默认的换行符。如果你不希望这种默认的处理方式,可以给 open() 函数传入参数 newline=''
# Read with disabled newline translation
with open('somefile.txt', 'rt', newline='') as f:
...
在 Unix 机器上面读取一个 Windows 上面的文本文件,里面的内容是 hello world!\r\n :
>>> # Newline translation enabled (the default)
>>> f = open('hello.txt', 'rt')
>>> f.read()
'hello world!\n'
>>> # Newline translation disabled
>>> g = open('hello.txt', 'rt', newline='')
>>> g.read()
'hello world!\r\n'
将 print() 函数的输出重定向到一个文件中去
在 print() 函数中指定 file 关键字参数,像下面这样:
with open('d:/work/test.txt', 'wt') as f:
print('Hello World!', file=f)
注意的是文件必须是以文本模式打开。如果文件是二进制模式的话,打印就会出错。
print() 函数输出数据,改变默认的分隔符或者行尾符
在 print() 函数中使用 sep 和 end 关键字参数,以你想要的方式输出。
>>> print('ACME', 50, 91.5)
ACME 50 91.5
>>> print('ACME', 50, 91.5, sep=',')
ACME,50,91.5
>>> print('ACME', 50, 91.5, sep=',', end='!!\n')
ACME,50,91.5!!
使用 end 参数也可以在输出中禁止换行。
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
>>> for i in range(5):
... print(i,end=' ')
...
0 1 2 3 4 >>>
使用非空格分隔符来输出数据的时候,给 print() 函数传递一个 sep 参数是最简单的方案。使用 str.join() 来完成同样的事情
>>> print(','.join(('ACME','50','91.5')))
ACME,50,91.5
str.join() 的问题在于它仅仅适用于字符串。这意味着你通常需要执行另外一些转换才能让它正常工作。
>>> row = ('ACME', 50, 91.5)
>>> print(','.join(row))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sequence item 1: expected str instance, int found
>>> print(','.join(str(x) for x in row))
ACME,50,91.5
使用print可以直接写成
>>> print(*row, sep=',')
ACME,50,91.5
读写二进制文件
使用模式为 rb 或 wb 的 open() 函数来读取或写入二进制数据。
# Read the entire file as a single byte string
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')
在读取二进制数据时,需要指明的是所有返回的数据都是字节字符串格式的,而不是文本字符串。类似的,在写入的时候,必须保证参数是以字节形式对外暴露数据的对象 (比如字节字符串,字节数组对象等)。
字节字符串和文本字符串的语义差异可能会导致一个潜在的陷阱。特别需要注意的是,索引和迭代动作返回的是字节的值而不是字节字符串。
#text string
>>> t = 'Hello'
>>> t[0]
'H'
>>> for c in t:
... print(c)
...
H
e
o
o
l
#Byte string
>>> b = b'Hello'
>>> b[0]
72
>>> for c in b:
... print(c)
...
72
101
108
108
111
如果你想从二进制模式的文件中读取或写入文本数据,必须确保要进行解码和编码操作
with open('somefile.bin', 'rb') as f:
data = f.read(16)
text = data.decode('utf-8')
with open('somefile.bin', 'wb') as f:
text = 'Hello World'
f.write(text.encode('utf-8'))
二进制 I/O 还有一个的特性就是数组和 C 结构体类型能直接被写入,而不需要中间转换为自己对象。
import array
nums = array.array('i', [1, 2, 3, 4])
with open('data.bin','wb') as f:
f.write(nums)
很多对象还允许通过使用文件对象的 readinto() 方法直接读取二进制数据到其底层的内存中去。
>>> import array
>>> a = array.array('i', [0, 0, 0, 0, 0, 0, 0, 0])
>>> with open('data.bin', 'rb') as f:
... f.readinto(a)
...
16
>>> a
array('i', [1, 2, 3, 4, 0, 0, 0, 0])
'x'模式打开文件
如果文件已经存在,使用此模式打开将引发异常 |
可以防止不小心覆盖掉已存在的文件
>>> with open('somefile', 'wt') as f:
... f.write('Hello\n')
...
>>> with open('somefile', 'xt') as f:
... f.write('Hello\n')
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileExistsError: [Errno 17] File exists: 'somefile
也可以使用os.path.exits()来替代,但是用'x'简单直接
>>> import os
>>> if not os.path.exists('somefile'):
... with open('somefile', 'wt') as f:
... f.write('Hello\n')
... else:
... print('File already exists!')
...
File already exists!
字符串的 I/O 操作
使用 io.StringIO() 和 io.BytesIO() 类来创建类文件对象操作字符串数据。
>>> s = io.StringIO()
>>> s.write('Hello World\n')
12
>>> print('This is a test', file=s)
15
>>> # Get all of the data written so far
>>> s.getvalue()
'Hello World\nThis is a test\n'
>>>
>>> # Wrap a file interface around an existing string
>>> s = io.StringIO('Hello\nWorld\n')
>>> s.read(4)
'Hell'
>>> s.read()
'o\nWorld\n'
>>>
操作二进制数据,要使用 io.BytesIO 类来代替。
>>> s = io.BytesIO()
>>> s.write(b'binary data')
>>> s.getvalue()
b'binary data'
当你想模拟一个普通的文件的时候 StringIO 和 BytesIO 类是很有用的。比如,在单元测试中,你可以使用 StringIO 来创建一个包含测试数据的类文件对象,这个对象可以被传给某个参数为普通文件对象的函数。需要注意的是, StringIO 和 BytesIO 实例并没有正确的整数类型的文件描述符。因此,它们不能在那些需要使用真实的系统级文件如文件,管道或者是套接字的程序中使用。
固定大小记录的文件迭代
在一个固定长度记录或者数据块的集合上迭代,而不是在一个文件中一行一行的迭代。
使用 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:
...
例子中的 records 对象是一个可迭代对象,它会不断的产生固定大小的数据块,直到文件末尾。要注意的是如果总记录大小不是块大小的整数倍的话,最后一个返回元素的字节数会比期望值少。
iter() 函数有一个鲜为人知的特性就是,如果你给它传递一个可调用对象和一个标记值,它会创建一个迭代器。这个迭代器会一直调用传入的可调用对象直到它返回标记值为止,这时候迭代终止。functools.partial 用来创建一个每次被调用时从文件中读取固定数目字节的可调用对象。标记值 b'' 就是当到达文件结尾时的返回值。
上面的例子中的文件时以二进制模式打开的。如果是读取固定大小的记录,这通常是最普遍的情况。而对于文本文件,一行一行的读取 (默认的迭代行为)更普遍点。
读取数据到一个可变数组中
读取二进制数据到一个可变缓冲区中,而不需要做任何的中间复制操作。或者你想原地修改数据并将它写回到一个文件中去。使用文件对象的 readinto() 方法
import os.path
def read_into_buffer(filename):
buf = bytearray(os.path.getsize(filename))
with open(filename, 'rb') as f:
f.readinto(buf)
return buf
>>> with open('sample.bin', 'wb') as f:
... f.write(b'Hello World')
...
>>> buf = read_into_buffer('sample.bin')
>>> buf
bytearray(b'Hello World')
>>> buf[0:5] = b'Hallo'
>>> buf
bytearray(b'Hallo World')
>>> with open('newsample.bin', 'wb') as f:
... f.write(buf)
...
11
可以使用它来避免大量的内存分配操作。
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
# Use the contents of buf
...
另外有一个有趣特性就是 memoryview ,它可以通过零复制的方式对已存在的缓冲区执行切片操作,甚至还能修改它的内容。
>>> buf
bytearray(b'Hello World')
>>> m1 = memoryview(buf)
>>> m2 = m1[-5:]
>>> m2
<memory at 0x100681390>
>>> m2[:] = b'WORLD'
>>> buf
bytearray(b'Hello WORLD')
python os模块
os模块中关于文件/目录常用的函数使用方法
函数名 | 使用方法 |
getcwd() | 返回当前工作目录 |
chdir(path) | 改变工作目录 |
listdir(path='.') | 列举指定目录中的文件名('.'表示当前目录,'..'表示上一级目录) |
mkdir(path) | 创建单层目录,如该目录已存在抛出异常 |
makedirs(path) | 递归创建多层目录,如该目录已存在抛出异常,注意:'E:\\a\\b'和'E:\\a\\c'并不会冲突 |
remove(path) | 删除文件 |
rmdir(path) | 删除单层目录,如该目录非空则抛出异常 |
removedirs(path) | 递归删除目录,从子目录到父目录逐层尝试删除,遇到目录非空则抛出异常 |
rename(old, new) | 将文件old重命名为new |
system(command) | 运行系统的shell命令 |
walk(top) | 遍历top路径以下所有的子目录,返回一个三元组:(路径, [包含目录], [包含文件]) |
以下是支持路径操作中常用到的一些定义,支持所有平台 | |
os.curdir | 指代当前目录('.') |
os.pardir | 指代上一级目录('..') |
os.sep | 输出操作系统特定的路径分隔符(Win下为'\\',Linux下为'/') |
os.linesep | 当前平台使用的行终止符(Win下为'\r\n',Linux下为'\n') |
os.name | 指代当前使用的操作系统(包括:'posix', 'nt', 'mac', 'os2', 'ce', 'java') |
os.path模块中关于路径常用的函数使用方法
函数名 | 使用方法 |
basename(path) | 去掉目录路径,单独返回文件名 |
dirname(path) | 去掉文件名,单独返回目录路径 |
join(path1[, path2[, ...]]) | 将path1, path2各部分组合成一个路径名 |
split(path) | 分割文件名与路径,返回(f_path, f_name)元组。如果完全使用目录,它也会将最后一个目录作为文件名分离,且不会判断文件或者目录是否存在 |
splitext(path) | 分离文件名与扩展名,返回(f_name, f_extension)元组 |
getsize(file) | 返回指定文件的尺寸,单位是字节 |
getatime(file) | 返回指定文件最近的访问时间(浮点型秒数,可用time模块的gmtime()或localtime()函数换算) |
getctime(file) | 返回指定文件的创建时间(浮点型秒数,可用time模块的gmtime()或localtime()函数换算) |
getmtime(file) | 返回指定文件最新的修改时间(浮点型秒数,可用time模块的gmtime()或localtime()函数换算) |
以下为函数返回 True 或 False | |
exists(path) | 判断指定路径(目录或文件)是否存在 |
isabs(path) | 判断指定路径是否为绝对路径 |
isdir(path) | 判断指定路径是否存在且是一个目录 |
isfile(path) | 判断指定路径是否存在且是一个文件 |
islink(path) | 判断指定路径是否存在且是一个符号链接 |
ismount(path) | 判断指定路径是否存在且是一个挂载点 |
samefile(path1, paht2) | 判断path1和path2两个路径是否指向同一个文件 |
>>os.path.join('/hello/','good/date','datbody')
hello/good/date/datbody
文件路径名的操作
>>> import os
>>> path = '/Users/beazley/Data/data.csv'
>>> # Get the last component of the path
>>> os.path.basename(path)
'data.csv'
>>> # Get the directory name
>>> os.path.dirname(path)
'/Users/beazley/Data'
>>> # Join path components together
>>> os.path.join('tmp', 'data', os.path.basename(path))
'tmp/data/data.csv'
>>> # Expand the user's home directory
>>> path = '~/Data/data.csv'
>>> os.path.expanduser(path)
'/Users/beazley/Data/data.csv'
>>> # Split the file extension
>>> os.path.splitext(path)
('~/Data/data', '.csv')
对于任何的文件名的操作,你都应该使用 os.path 模块,而不是使用标准字符串操作来构造自己的代码。特别是为了可移植性考虑的时候更应如此,因为 os.path 模块知道 Unix 和 Windows 系统之间的差异并且能够可靠地处理类似 Data/data.csv 和
Data\data.csv 这样的文件名。其次,你真的不应该浪费时间去重复造轮子。通常最好是直接使用已经为你准备好的功能。
测试文件是否存在
>>> import os
>>> os.path.exists('/etc/passwd')
True
>>> os.path.exists('/tmp/spam')
False
测试这个文件是什么类型的。在下面这些测试中,如果测试的文件不存在的时候,结果都会返回 False:
>>> # Is a regular file
>>> os.path.isfile('/etc/passwd')
True
>>> # Is a directory
>>> os.path.isdir('/etc/passwd')
False
>>> # Is a symbolic link
>>> os.path.islink('/usr/local/bin/python3')
True
>>> # Get the file linked to
>>> os.path.realpath('/usr/local/bin/python3')
'/usr/local/bin/python3.3'
>>>
如果你还想获取元数据 (比如文件大小或者是修改日期),也可以使用 os.path 模块来解决
>>> os.path.getsize('/etc/passwd')
3669
>>> os.path.getmtime('/etc/passwd')
1272478234.0
>>> import time
>>> time.ctime(os.path.getmtime('/etc/passwd'))
'Wed Apr 28 13:10:34 2010'
>>>
os.path 来进行文件测试是很简单的。在写这些脚本时,可能唯一需要注意的就是你需要考虑文件权限的问题,特别是在获取元数据时候。
使用 os.listdir() 函数来获取某个目录中的文件列表
import os
names = os.listdir('somedir')
结果会返回目录中所有文件列表,包括所有文件,子目录,符号链接等等。如果你需要通过某种方式过滤数据,可以考虑结合 os.path 库中的一些函数来使用列表推导。比如:
import os.path
# Get all regular files
names = [name for name in os.listdir('somedir')
if os.path.isfile(os.path.join('somedir', name))]
# Get all dirs
dirnames = [name for name in os.listdir('somedir')
if os.path.isdir(os.path.join('somedir', name))]
字符串的 startswith() 和 endswith() 方法对于过滤一个目录的内容也是很有用的。比如:
pyfiles = [name for name in os.listdir('somedir')
if name.endswith('.py')]
对于文件名的匹配,你可能会考虑使用 glob 或 fnmatch 模块。比如:
import glob
pyfiles = glob.glob('somedir/*.py')
from fnmatch import fnmatch
pyfiles = [name for name in os.listdir('somedir')
if fnmatch(name, '*.py)]
获取目录中的列表是很容易的,但是其返回结果只是目录中实体名列表而已。如果你还想获取其他的元信息,比如文件大小,修改时间等等,你或许还需要使用到os.path 模块中的函数或着 os.stat() 函数来收集数据。
# Example of getting a directory listing
import os
import os.path
import glob
pyfiles = glob.glob('*.py')
# Get file sizes and modification dates
name_sz_date = [(name, os.path.getsize(name), os.path.getmtime(name))
for name in pyfiles]
for name, size, mtime in name_sz_date:
print(name, size, mtime)
# Alternative: Get file metadata
file_metadata = [(name, os.stat(name)) for name in pyfiles]
for name, meta in file_metadata:
print(name, meta.st_size, meta.st_mtime)
要注意的就是,有时候在处理文件名编码问题时候可能会出现一些问题。通常来讲,函数 os.listdir() 返回的实体列表会根据系统默认的文件名编码来解码。但是有时候也会碰到一些不能正常解码的文件名。
将字节写入文本文件
在文本模式打开的文件中写入原始的字节数据,将字节数据直接写入文件的缓冲区即可
>>> import sys
>>> sys.stdout.write(b'Hello\n')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: must be str, not bytes
>>> sys.stdout.buffer.write(b'Hello\n')
Hello
5
类似的,能够通过读取文本文件的 buffer 属性来读取二进制数据。
I/O 系统以层级结构的形式构建而成。文本文件是通过在一个拥有缓冲的二进制模式文件上增加一个 Unicode 编码/解码层来创建。 buffer 属性指向对应的底层文件。如果你直接访问它的话就会绕过文本编码/解码层。
例子展示的 sys.stdout 可能看起来有点特殊。默认情况下, sys.stdout 总是以文本模式打开的。但是如果你在写一个需要打印二进制数据到标准输出的脚本的话,你可以使用上面演示的技术来绕过文本编码层。
将文件描述符包装成文件对象
一个文件描述符和一个打开的普通文件是不一样的。文件描述符仅仅是一个操作系统指定的整数,用来指代某个系统的I/O通道。如果你碰巧有这么一个文件描述符,你可以通过使用open()函数来将其包装为一个Python的文件对象。你仅仅只需要用这个整数值得文件描述符作为第一个参数来代替文件名即可。
# Open a low-level file descriptor
import os
fd = os.open('somefile.txt', os.O_WRONLY | os.O_CREAT)
# Turn into a proper file
f = open(fd, 'wt')
f.write('hello world\n')
f.close()
当高层的文件对象被关闭或者破坏的时候,底层的文件描述符也会被关闭。如果这个并不是你想要的结果,你可以给 open() 函数传递一个可选的 colsefd=False 。比如:
# Create a file object, but don't close underlying fd when done
f = open(fd, 'wt', closefd=False)
...
在 Unix 系统中,这种包装文件描述符的技术可以很方便的将一个类文件接口作用于一个以不同方式打开的 I/O 通道上,如管道、套接字等。举例来讲,下面是一个操作管道的例子:
from socket import socket, AF_INET, SOCK_STREAM
def echo_client(client_sock, addr):
print('Got connection from', addr)
# Make text-mode file wrappers for socket reading/writing
client_in = open(client_sock.fileno(), 'rt', encoding='latin-1',
closefd=False)
client_out = open(client_sock.fileno(), 'wt', encoding='latin-1',
closefd=False)
# Echo lines back to the client using file I/O
for line in client_in:
client_out.write(line)
client_out.flush()
client_sock.close()
def echo_server(address):
sock = socket(AF_INET, SOCK_STREAM)
sock.bind(address)
sock.listen(1)
while True:
client, addr = sock.accept()
echo_client(client, addr)
创建临时文件和文件夹
创建一个临时文件或目录,并希望使用完之后可以自动销毁,tempfile 模块中有很多的函数可以完成这任务。为了创建一个匿名的临时文件,可以使用 tempfile.TemporaryFile :
from tempfile import TemporaryFile
with TemporaryFile('w+t') as f:
#Read/write to the file
f.write('Hello World\n')
f.write('Testing\n')
#Seek back to begining and read the data
f.seek(0)
data = f.read()
# Temporary file is destroyed
或者还可以像这样使用临时文件:
f = TemporaryFile('w+t')
# Use the temporary file
...
f.close()
# File is destroyed
TemporaryFile() 的第一个参数是文件模式,通常来讲文本模式使用 w+t ,二进制模式使用 w+b 。这个模式同时支持读和写操作,在这里是很有用的,因为当你关闭文件去改变模式的时候,文件实际上已经不存在了。 TemporaryFile() 另外还支持跟内
置的 open() 函数一样的参数。
with TemporaryFile('w+t', encoding='utf-8', errors='ignore') as f:
...
在大多数 Unix 系统上,通过 TemporaryFile() 创建的文件都是匿名的,甚至连目录都没有。如果你想打破这个限制,可以使用 NamedTemporaryFile() 来代替。比如:
from tempfile import NamedTemporaryFile
with NamedTemporaryFile('w+t') as f:
print('filename is:', f.name)
...
# File automatically destroyed
这里,被打开文件的 f.name 属性包含了该临时文件的文件名。当你需要将文件名传递给其他代码来打开这个文件的时候,这个就很有用了。和 TemporaryFile() 一样,结果文件关闭时会被自动删除掉。如果你不想这么做,可以传递一个关键字参数
delete=False 即可。比如:
with NamedTemporaryFile('w+t', delete=False) as f:
print('filename is:', f.name)
...
创建一个临时目录,可以使用 tempfile.TemporaryDirectory() 。比如:
from tempfile import TemporaryDirectory
with TemporaryDirectory() as dirname:
print('dirname is:', dirname)
# Use the directory
...
# Directory and all contents destroyed
在一个更低的级别,你可以使用 mkstemp() 和 mkdtemp() 来创建临时文件和目录。
>>> import tempfile
>>> tempfile.mkstemp()
(3, '/var/folders/7W/7WZl5sfZEF0pljrEB1UMWE+++TI/-Tmp-/tmp7fefhv')
>>> tempfile.mkdtemp()
'/var/folders/7W/7WZl5sfZEF0pljrEB1UMWE+++TI/-Tmp-/tmp5wvcv6'
>>>
但是,这些函数并不会做进一步的管理了。例如,函数 mkstemp() 仅仅就返回一个原始的 OS 文件描述符,你需要自己将它转换为一个真正的文件对象。同样你还需要自己清理这些文件。通常来讲,临时文件在系统默认的位置被创建,比如 /var/tmp 或类似的地方。为了获取真实的位置,可以使用 tempfile.gettempdir() 函数。比如:
>>> tempfile.gettempdir()
'/var/folders/7W/7WZl5sfZEF0pljrEB1UMWE+++TI/-Tmp-'
所有和临时文件相关的函数都允许你通过使用关键字参数 prefix 、 suffix 和 dir来自定义目录以及命名规则。比如
>>> f = NamedTemporaryFile(prefix='mytemp', suffix='.txt', dir='/tmp')
>>> f.name
'/tmp/mytemp8ee899.txt'
python pickle模块
Python提供的pickle模块可以序列化对象并保存到磁盘中,并在需要的时候读取出来,任何对象都可以执行序列化操作。
()
import pickle
data = ... # Some Python object
f = open('somefile', 'wb')
pickle.dump(data, f)
1、dump
>>> my_list = [123,3.14,'abc',['another list']]
>>> pickle_file = open('E:\\test\\my_list.pkl','wb')
>>> pickle.dump(my_list,pickle_file)
>>> pickle_file.close()
为了将一个对象转储为一个字符串,可以使用 pickle.dumps() :
s = pickle.dumps(data)
load
>>> picke_file = open('E:\\test\\my_list.pkl','rb')
>>> my_list2 = pickle.load(picke_file)
>>> print(my_list2)
[123, 3.14, 'abc', ['another list']]
为了从字节流中恢复一个对象,使用 picle.load() 或 pickle.loads() 函数。
# Restore from a file
f = open('somefile', 'rb')
data = pickle.load(f)
# Restore from a string
data = pickle.loads(s)
对于大多数应用程序来讲, dump() 和 load() 函数的使用就是你有效使用 pickle模块所需的全部了。它可适用于绝大部分 Python 数据类型和用户自定义类的对象实例。如果你碰到某个库可以让你在数据库中保存/恢复 Python 对象或者是通过网络传输对象的话,那么很有可能这个库的底层就使用了 pickle 模块.
pickle 是一种 Python 特有的自描述的数据编码。通过自描述,被序列化后的数据包含每个对象开始和结束以及它的类型信息。因此,你无需担心对象记录的定义,它总是能工作。举个例子,如果要处理多个对象,你可以这样做
>>> import pickle
>>> f = open('somedata', 'wb')
>>> pickle.dump([1, 2, 3, 4], f)
>>> pickle.dump('hello', f)
>>> pickle.dump({'Apple', 'Pear', 'Banana'}, f)
>>> f.close()
>>> f = open('somedata', 'rb')
>>> pickle.load(f)
[1, 2, 3, 4]
>>> pickle.load(f)
'hello'
>>> pickle.load(f)
{'Apple', 'Pear', 'Banana'}
你还能序列化函数,类,还有接口,但是结果数据仅仅将它们的名称编码成对应的代码对象。例如:
>>> import math
>>> import pickle.
>>> pickle.dumps(math.cos)
b'\x80\x03cmath\ncos\nq\x00.'
>>>
当数据反序列化回来的时候,会先假定所有的源数据时可用的。模块、类和函数会自动按需导入进来。对于 Python 数据被不同机器上的解析器所共享的应用程序而言,数据的保存可能会有问题,因为所有的机器都必须访问同一个源代码。
注意:千万不要对不信任的数据使用 pickle.load()。pickle 在加载时有一个副作用就是它会自动加载相应模块并构造实例对象。
但是某个坏人如果知道 pickle 的工作原理,他就可以创建一个恶意的数据导致 Python 执行随意指定的系统命令。因此,一定要保证 pickle 只在相互之间可以认证对方的解析器的内部使用。
有些类型的对象是不能被序列化的。这些通常是那些依赖外部系统状态的对象,比如打开的文件,网络连接,线程,进程,栈帧等等。用户自定义类可以通过提供__getstate__() 和 __setstate__() 方法来绕过这些限制。如果定义了这两个方法,pickle.dump() 就会调用 __getstate__() 获取序列化的对象。类似的,__setstate__()在反序列化时被调用。为了演示这个工作原理,下面是一个在内部定义了一个线程但仍然可以序列化和反序列化的类:
# countdown.py
import time
import threading
class Countdown:
def __init__(self, n):
self.n = n
self.thr = threading.Thread(target=self.run)
self.thr.daemon = True
self.thr.start()
def run(self):
while self.n > 0:
print('T-minus', self.n)
self.n -= 1
time.sleep(5)
def __getstate__(self):
return self.n
def __setstate__(self, n):
self.__init__(n)
运行序列化
>>> import countdown
>>> c = countdown.Countdown(30)
>>> T-minus 30
T-minus 29
T-minus 28
...
>>> # After a few moments
>>> f = open('cstate.p', 'wb')
>>> import pickle
>>> pickle.dump(c, f)
>>> f.close()
然后退出 Python 解析器并重启后再试验下:
>>> f = open('cstate.p', 'rb')
>>> pickle.load(f)
countdown.Countdown object at 0x10069e2d0>
T-minus 19
T-minus 18
可以看到线程又奇迹般的重生了,从你第一次序列化它的地方又恢复过来。
pickle 对于大型的数据结构比如使用 array 或 numpy 模块创建的二进制数组效率并不是一个高效的编码方式。如果你需要移动大量的数组数据,你最好是先在一个文件中将其保存为数组数据块或使用更高级的标准编码方式如 HDF5 (需要第三方库的支持)。
由于 pickle 是 Python 特有的并且附着在源码上,所有如果需要长期存储数据的时候不应该选用它。例如,如果源码变动了,你所有的存储数据可能会被破坏并且变得不可读取。坦白来讲,对于在数据库和存档文件中存储数据时,你最好使用更加标准的
数据编码格式如 XML, CSV 或 JSON。这些编码格式更标准,可以被不同的语言支持,并且也能很好的适应源码变更。