主要记录Python的文件读写、文件和目录操作以及JSON序列化。
所有模式的定义及含义可以参考Python的官方文档。
1 | try: |
每次这样过于繁琐,Python引入了with
语句自动帮我们调用close()
方法:
1 | with open('/path/to/file', 'r') as f: |
read()
函数会一次性读取文件的全部内容,如果文件太大,Python程序就直接挂掉了,所以可以使用read(size)
来指定每次最多读取size字节的内容,或者使用readline()
和readlines()
。
一般来说,如果文件较小,直接使用read()
方法;如果不能确定文件大小,就反复调用read(size)
方法;如果是配置文件,调用readlines()
最方便:
1 | for line in f.readlines(): |
像
open()
函数返回的这种有个read()
方法的对象,在Python中称为file-like-object。除了file外,还可以是内存的字节流,网络流,自定义流。
1 | '/User/xxx/test.jpg', 'rb') f = open( |
读取非Unicode编码的文件:
1 | '/User/xxx/gbk.txt', 'r', encoding='gbk') f = open( |
如果遇到一些编码不规范的文件,open()
函数还接收一个error
参数。
1 | '\User\xxx\gbk.txt', 'r', encoding='gbk', errors='ignore') f.open( |
1 | '/User/xxx/test.txt', 'w') f = open( |
只有调用close()
方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()
的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with
语句来得保险:
1 | with open('/User/xxx/test.txt', 'w') as f: |
如果我们希望追加到文件末尾怎么办?可以传入'a'
以追加(append)模式写入。
1 | f = StringIO() |
getvalue()
方法用于获得写入后的str。
要读取StringIO,可以用一个str初始化StringIO,然后像读取文件一样读取:
1 | 'Hello!\nHi!\nGoogbye!') f = StringIO( |
BytesIO实现了在内存中读写bytes,我们创建一个BytesIO,然后写入一些bytes:
1 | from io import BytesIO |
请注意,写入的不是str,而是经过UTF-8编码的bytes。
和StringIO类似,可以用一个bytes初始化BytesIO,然后,像读文件一样读取:
1 | from io import BytesIO |
1 | # 查看当前目录的绝对路径 |
把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()
函数,这样可以正确处理不同操作系统的路径分隔符。
同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()
函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:
1 | '/Users/xxx/testdir/test.txt') os.path.split( |
os.path.splitext()
可以直接得到文件扩展名:
1 | >>>os.path.splitext('/Users/xxx/testdir/test.txt') |
假定当前目录下有一个test.txt
文件:
1 | # 对文件重命名 |
Python的os模块没有提供文件的复制操作,但是在shutil模块提供了很多中不同需求的文件复制操作。
shutil.copyfileobj(文件1,文件2):将文件1的数据覆盖copy给文件2
1 | import shutil |
shutil.copy(文件1,文件2):拷贝文件和权限都进行copy。
shutil.move(源文件,指定路径):递归移动一个文件。
shutil.copytree(源目录,目标目录):可以递归copy多个目录到指定目录。
列出当前目录下的所有目录:
1 | for x in os.listdir('.') if os.path.isdir(x)] [x |
列出指定后缀名的文件:
1 | for x in os.listdir('x') if os.path.isfile(x) and os.path.splitext(x)[1] == 'py'] [x |
Python提供了pickle
模块来实现序列化。
首先,把一个对象序列化并写入文件:
1 | import pickle |
pickle.dumps()
方法把任意对象序列化成一个bytes,然后,就可以把这个bytes
写入文件。还有一个pickle.dump()
方法,它直接把对象序列化后写入一个file-like Object:
1 | 'dump.txt', 'wb') f = open( |
当我们要把对象从磁盘读到内存时,可以先把内容读到一个bytes
,然后用pickle.loads()
方法反序列化出对象,也可以用pickle.load()
方法直接从一个file-like Object中直接反序列化出对象。
1 | 'dump.txt', 'rb') f = open( |
JSON和Python内置的数据类型对应如下:
JSON类型 | Python类型 |
---|---|
{} | dict |
[] | list |
“string” | str |
1234.56 | int或float |
true/false | True/False |
null | None |
Python对象—>JSON对象
1 | import json |
dumps()
方法返回一个str
,内容就是标准的JSON。类似的,dump()
方法可以直接把JSON写入一个file-like Object。
JSON对象—>Python对象
要把JSON反序列化为Python对象,用loads()
或者对应的load()
方法,前者把JSON的字符串反序列化,后者从file-like Object
中读取字符串并反序列化:
1 | '{"age": 20, "score": 88, "name": "Bob"}' json_str = |
如果想对一个class
对象进行序列化操作,我们应该怎么做呢?
https://docs.python.org/3/library/json.html#json.dumps
可以看到参数列表中dumps()
的参数列表提供一个default
选项。
defalut
:To use a custom JSONEncoder subclass (e.g. one that overrides thedefault()
method to serialize additional types), specify it with the cls kwarg; otherwise JSONEncoder is used.
也就是说如果要序列化一个class对象,我们必须自己写一个序列化的过程,不然Python是不知道如何对一个class对象进行序列化操作的。
1 | class Student(object): |
这样,Student
实例首先被student2dict()
函数转换成dict
,然后再被序列化为JSON:
1 | 'Bob', 20, 88) s = Student( |
但是为每一个class写一个类似转化函数未免代码的重复率有点太高。
1 | print(json.dumps(s, default=lambda obj: obj.__dict__)) |
通常class
的实例都有一个__dict__
属性,它就是一个dict
,用来存储实例变量,所以我们可以直接使用__dict__
属性。
如果我们要把JSON反序列化为一个Student
对象实例,loads()
方法首先转换出一个dict
对象,然后,我们传入的object_hook
函数负责把dict
转换为Student
实例:
1 | def dict2student(d): |
结果如下:
1 | '{"age": 20, "score": 88, "name": "Bob"}' json_str = |