python中的内置函数open的一般形式为【open(file, mode=‘r’, buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)】;首先一步步来看一下每一个参数所代表的含义:
第一个参数file
,这是一个必选参数,接受的值是一个待操作文件的路径,可以是绝对路径,也可以是相对路径,样例代码如下所示:
# 相对路径
open('test_open.txt', 'w', buffering=-1, newline='\n')
# 绝对路径
open(r'D:\python_files\effective_python\test_open.txt', 'w', buffering=-1, newline='\n')
第二个参数mode
,这是一个可选参数,其默认值是rt
,其他的权限有r
、w
、x
、a
、b
、t
以及+
;这些权限表示的含义如下所示:
-
r
:读文件权限(缺省)。从一个文件中读取内容。 -
w
:写文件权限。将bytes(需要写成wb
才能成功)或者str等对象写入到文件中。如果文件已经存在,会将原先文件中的内容truncate,之后在执行写操作。 -
x
:创建一个文件,执行写操作,如果文件已经存在,那么会抛出异常。 -
a
:追加写。如果要操作的文件存在那么不truncate,直接在文件末尾进行内容的追加。 -
b
:二进制模式。如果读写的内容是bytes对象,那么这个mode是必须的,因为缺省时是t
。 -
t
:文本模式(缺省)。如果采用了这个模式,那么写入到文件的内容需要是str对象,不然会报错,输出从文件中读取的内容也是一个str对象。 -
+
:可以和读写权限进行搭配形成r+
和w+
(两者功能上是相同的),使用这两个权限打开的文件可以对其进行读写操作(前提是该文件允许);也可以和追加权限组成a+
。
下面是针对上面的各个mode做的一些可以增加理解的测试;
- 测试
wt
,代码如下:
def main():
try:
with open('data.bin', 'wt') as f:
f.write('\xe1\xe2')
except:
logging.exception('Expected')
else:
assert True
if __name__ == '__main__':
main()
- 执行之后的输出如下所示:
ERROR:root:Expected
Traceback (most recent call last):
File "D:/python_files/effective_python/test_instance_function.py", line 23, in main
f.write('\xe1\xe2')
UnicodeEncodeError: 'gbk' codec can't encode character '\xe2' in position 1: illegal multibyte sequence
- 可以发现上面的输出报错了,如果正常的话会什么信息都不输出的,阅读错误信息【gdk不能够对’\xe2’进行编码】,通过命令行打开python解释器(windows是
win+r
进入命令行输入python
,ubuntu是ctrl+alt+t
进入命令行输入python
),之后输入如下代码:
>>> import locale
>>> print(locale.getpreferredencoding())
cp936
- 上面这段代码可以查看默认的编码格式,我的机器是
cp936
,经过查阅cp936
=gbk
,如果想让这段代码执行成功,需要指定编码格式,utf-8
可行,更改代码成如下:
def main():
try:
with open('data.bin', 'wt', encoding='utf-8') as f:
f.write('\xe1\xe2')
except:
logging.exception('Expected')
else:
assert True
if __name__ == '__main__':
main()
# data.bin文件的内容如下所示:
áâ
- 上面使用了参数
encoding
,这个之后再介绍,由于utf-8
能够编码的字符数目大于gbk
所以上面的代码能够对字符\xe2
进行编码,所以成功了。 - 测试
wb
,代码如下
def main():
try:
with open('data.bin', 'wb') as f:
f.write(b'\xe1\xe2')
except:
logging.exception('Expected')
else:
assert True
if __name__ == '__main__':
main()
- 上面这段代码成功的把一个
bytes
类对象写入到了data.bin
这个文件中,打开这个文件,可能每个人看到的内容会不一样,因为不同软件的解码格式存在差异,比如我的电脑上,使用pycharm打开,看到的是徕
(使用gdb解码),而使用vscode打开看到的是��
(不知道是用什么解码,肯定不是gdb);既然是测试,那就把上面代码中的wb
改成w
或者wt
,如下:
def main():
try:
with open('data.bin', 'wt') as f:
f.write(b'\xe1\xe2')
except:
logging.exception('Expected')
else:
assert True
if __name__ == '__main__':
main()
- 运行报错,主要的错误提示为【TypeError: write() argument must be str, not bytes】,因为如果不指定写入的文件为bytes对象,则默认写入的应该为str(
t
权限default),所以上面写入bytes的时候就报错了。同理,权限b
enable,写入的内容不是bytes同样会报错【TypeError: a bytes-like object is required, not ‘str’】 - 测试
rt
和rb
,测试之前先用如下代码给文件data.bin
一些内容
def main():
try:
with open('data.bin', 'wb') as f:
f.write(b'\xe1\xe2')
except:
logging.exception('Expected')
else:
assert True
if __name__ == '__main__':
main()
- 测试的代码如下:
def main():
try:
with open('data.bin', 'rb') as f:
print(f.readline())
except:
logging.exception('Expected')
else:
assert True
if __name__ == '__main__':
main()
# 输出如下
b'\xe1\xe2'
- 如果把
rb
改成rt
,那么会把读取的内容做一个解码,得到的输出如下:
徕
- 可以得到上面的输出是因为一开始是将bytes类对象写入到文件中,但是读取的时候是读取的str类对象,需要做一个解码的操作,这个解码操作会采用系统默认的encode,我的机器是
cp936
,因此会输出徕
(print((b'\xe1\xe2').decode('cp936'))
)。 - 测试
w+
和r+
(都具有读写权限,但也稍有不同),代码如下:
def test_w_plus():
file_obj = open('data.bin', 'w+')
# 将光标置到一开始的位置
file_obj.seek(0)
print('一开始文件中的内容为:', file_obj.readline())
file_obj.write('after')
file_obj.seek(0)
print('之后文件中的内容为:', file_obj.readline())
file_obj.close()
def test_r_plus():
file_obj = open('data.bin', 'r+')
file_obj.seek(0)
print('一开始文件中的内容为:', file_obj.readline())
file_obj.write('after')
file_obj.seek(0)
print('之后文件中的内容为:', file_obj.readline())
file_obj.close()
def main():
# 先写入一些内容
with open('data.bin', 'w') as file_obj:
file_obj.write('test')
test_w_plus()
if __name__ == '__main__':
main()
# 输出的内容如下:
一开始文件中的内容为:
之后文件中的内容为: after
- 可以发现如果打开时使用
w+
,会事先将所操作的文件中的内容truncate一下,所以一开始没输出,之后写入了内容之后,光标到了下一行,需要将光标放到一开始才能输出刚才写入的after,使用了seek
方法重置光标位置到第一行,这样就能输出after
了。r+
的测试如下:
def test_w_plus():
file_obj = open('data.bin', 'w+')
# 将光标置到一开始的位置
file_obj.seek(0)
print('一开始文件中的内容为:', file_obj.readline())
file_obj.write('after')
file_obj.seek(0)
print('之后文件中的内容为:', file_obj.readline())
file_obj.close()
def test_r_plus():
file_obj = open('data.bin', 'r+')
file_obj.seek(0)
print('一开始文件中的内容为:', file_obj.readline())
file_obj.write('after')
file_obj.seek(0)
print('之后文件中的内容为:', file_obj.readline())
file_obj.close()
def main():
with open('data.bin', 'w') as file_obj:
file_obj.write('test')
test_r_plus()
if __name__ == '__main__':
main()
# 输出:
一开始文件中的内容为: test
之后文件中的内容为: testafter
- 所以可以得出结论
r+
一开始是不会truncate所要操作的文件的,而w+
则会将要操作的文件truncate一下。
下面附上一个总结的表格,
第四个参数encoding
,open函数实际上是将字符串通过编码的方式变成bytes(如果要写入的就是bytes就不用进行编码了),之后将这个bytes写入到文件中的过程或者是从文件中读出bytes,将这个bytes解码还原成字符串的过程(如果期望得到bytes字符串,那么这个操作忽略),由此可以看出这个encoding
参数只针对mode='t'
的情况,如果要对mode='b'
也指定encoding
参数会报错的ValueError: binary mode doesn't take an encoding argument
。如果需要对写入的字符串进行编码,但是没有指定编码格式,将会采用机器默认的编码格式(可通过print(locale.getpreferredencoding())
进行查询),如果指定了编码格式(比如encoding='utf-8'
)那么会用指定的格式进行编码。
剩下的的参数由于时间问题之后之后会慢慢更新。如果有总结错误的地方欢迎大家指出。