文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 2019-11-11   2.3k

PythonIO编程

主要记录Python的文件读写文件和目录操作以及JSON序列化

文件读写

所有模式的定义及含义可以参考Python的官方文档

读文件

1
2
3
4
5
6
try:
f = open('/path/to/file', 'r')
print(f.read())
finally:
if f:
f.close()

每次这样过于繁琐,Python引入了with语句自动帮我们调用close()方法:

1
2
with open('/path/to/file', 'r') as f:
print(f.read())

read()函数会一次性读取文件的全部内容,如果文件太大,Python程序就直接挂掉了,所以可以使用read(size)来指定每次最多读取size字节的内容,或者使用readline()readlines()

一般来说,如果文件较小,直接使用read()方法;如果不能确定文件大小,就反复调用read(size)方法;如果是配置文件,调用readlines()最方便:

1
2
for line in f.readlines():
print(line.strip()) # 去掉'\n'

open()函数返回的这种有个read()方法的对象,在Python中称为file-like-object。除了file外,还可以是内存的字节流,网络流,自定义流。

二进制文件

1
2
3
>>> f = open('/User/xxx/test.jpg', 'rb')
>>> f.read()
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节

字符编码

读取非Unicode编码的文件:

1
>>> f = open('/User/xxx/gbk.txt', 'r', encoding='gbk')

如果遇到一些编码不规范的文件,open()函数还接收一个error参数。

1
>>> f.open('\User\xxx\gbk.txt', 'r', encoding='gbk', errors='ignore')

写文件

1
2
3
>>> f = open('/User/xxx/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()

只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with语句来得保险:

1
2
with open('/User/xxx/test.txt', 'w') as f:
f.write('Hello, world')

如果我们希望追加到文件末尾怎么办?可以传入'a'以追加(append)模式写入。

StringIO和BytesIO

StringIO

写入

1
2
3
4
5
6
7
8
9
>>> f = StringIO()
>>> f. write('Hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
Hello world!

getvalue()方法用于获得写入后的str。

读取

要读取StringIO,可以用一个str初始化StringIO,然后像读取文件一样读取:

1
2
3
4
5
6
7
8
9
10
>>> f = StringIO('Hello!\nHi!\nGoogbye!')
>>> while True:
... s = f.readline()
... if s == '':
... break
... print(s.strip())
...
Hello!
Hi!
Googbye!

BytesIO

写入

BytesIO实现了在内存中读写bytes,我们创建一个BytesIO,然后写入一些bytes:

1
2
3
4
5
6
>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'

请注意,写入的不是str,而是经过UTF-8编码的bytes。

读取

和StringIO类似,可以用一个bytes初始化BytesIO,然后,像读文件一样读取:

1
2
3
4
>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'

操作文件和目录

操作目录

1
2
3
4
5
6
7
8
# 查看当前目录的绝对路径
>>> os.path.abspath(.)
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来
>>> os.path.join('/Users/xxx', 'testdir')
# 然后创建一个目录
>>> os.mkdir('/Users/xxx/testdir')
# 删掉一个目录
>>> os.rmdir('/Users/xxx/testdir')

把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符。

同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:

1
2
>>> os.path.split('/Users/xxx/testdir/test.txt')
('/Users/xxx/testdir', 'test.txt')

os.path.splitext()可以直接得到文件扩展名:

1
2
>>>os.path.splitext('/Users/xxx/testdir/test.txt')
('/Users/xxx/testdir/test', '.txt')

操作文件

假定当前目录下有一个test.txt文件:

1
2
3
4
# 对文件重命名
>>> os.rename('test.txt', 'test.py')
# 删除文件
>>> os.removez('test.py')

Python的os模块没有提供文件的复制操作,但是在shutil模块提供了很多中不同需求的文件复制操作。

  1. shutil.copyfileobj(文件1,文件2):将文件1的数据覆盖copy给文件2

    1
    2
    3
    4
    5
    6
    7
    import shutil

    f1 = open("1.txt",encoding="utf-8")

    f2 = open("2.txt","w",encoding="utf-8")

    shutil.copyfileobj(f1,f2)
  2. shutil.copy(文件1,文件2):拷贝文件和权限都进行copy。

  3. shutil.move(源文件,指定路径):递归移动一个文件。

  4. shutil.copytree(源目录,目标目录):可以递归copy多个目录到指定目录。

过滤文件

列出当前目录下的所有目录:

1
>>> [x for x in os.listdir('.') if os.path.isdir(x)]

列出指定后缀名的文件:

1
>>> [x for x in os.listdir('x') if os.path.isfile(x) and os.path.splitext(x)[1] == 'py']

序列化

Python提供了pickle模块来实现序列化。

首先,把一个对象序列化并写入文件:

1
2
3
4
>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'

pickle.dumps()方法把任意对象序列化成一个bytes,然后,就可以把这个bytes写入文件。还有一个pickle.dump()方法,它直接把对象序列化后写入一个file-like Object:

1
2
3
>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()

当我们要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化出对象,也可以用pickle.load()方法直接从一个file-like Object中直接反序列化出对象。

1
2
3
4
5
>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}

JSON

JSON和Python内置的数据类型对应如下:

JSON类型 Python类型
{} dict
[] list
“string” str
1234.56 int或float
true/false True/False
null None

Python对象—>JSON对象

1
2
3
4
>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
'{"age": 20, "score": 88, "name": "Bob"}'

dumps()方法返回一个str,内容就是标准的JSON。类似的,dump()方法可以直接把JSON写入一个file-like Object。

JSON对象—>Python对象

要把JSON反序列化为Python对象,用loads()或者对应的load()方法,前者把JSON的字符串反序列化,后者从file-like Object中读取字符串并反序列化:

1
2
3
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
>>> json.loads(json_str)
{'age': 20, 'score': 88, 'name': 'Bob'}

如果想对一个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 the default() method to serialize additional types), specify it with the cls kwarg; otherwise JSONEncoder is used.

也就是说如果要序列化一个class对象,我们必须自己写一个序列化的过程,不然Python是不知道如何对一个class对象进行序列化操作的。

1
2
3
4
5
6
7
8
9
10
11
12
class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score

def student2dict(std):
return {
'name': std.name
'age': std.age
'score': std.score
}

这样,Student实例首先被student2dict()函数转换成dict,然后再被序列化为JSON:

1
2
3
>>> s = Student('Bob', 20, 88)
>>> print(json.dumps(s, defalut=student2dict))
{"age": 20, "name": "Bob", "score": 88}

但是为每一个class写一个类似转化函数未免代码的重复率有点太高。

1
print(json.dumps(s, default=lambda obj: obj.__dict__))

通常class的实例都有一个__dict__属性,它就是一个dict,用来存储实例变量,所以我们可以直接使用__dict__属性。

如果我们要把JSON反序列化为一个Student对象实例,loads()方法首先转换出一个dict对象,然后,我们传入的object_hook函数负责把dict转换为Student实例:

1
2
def dict2student(d):
return Student(d['name'], d['age'], d['score'])

结果如下:

1
2
3
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
>>> print(json.loads(json_str, object_hook=dict2student))
<__main__.Student object at 0x10cd3c190>
Copyright © ca01h 2019-2020 | 本站总访问量