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,其他的权限有rwxabt以及+;这些权限表示的含义如下所示:

  • 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的时候就报错了。同理,权限benable,写入的内容不是bytes同样会报错【TypeError: a bytes-like object is required, not ‘str’】
  • 测试rtrb,测试之前先用如下代码给文件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一下。

下面附上一个总结的表格,

python open 文件路径 python open函数路径_shell


第四个参数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')那么会用指定的格式进行编码。

剩下的的参数由于时间问题之后之后会慢慢更新。如果有总结错误的地方欢迎大家指出。