Python文件处理2
14.3 序列化
在我们开发过程中,我们会读取或设置配置信息,对数据进行分析,将爬取的数据保存等……因此,我们可能会操作不同类型的文件。本节会介绍csv与json文件类型的特征,然后给出该类型文件的读取与写入方式。
14.3.1 csv
CSV(Comma Separated Values),是一种存文本格式的文件,文件中各个元素值通常使用逗号(,)进行分隔,但这不是必须的,扩展名为.csv。例如,一个班级的学生信息,我们就可以存储为csv的格式:
姓名,年龄,所在小组
张某,15,A组
赵某,14,B组
王某,15,C组
……
我们可以使用csv模块来操作csv类型的文件。
14.3.2 json
JSON(JavaScript Object Notation),是一种轻量级的数据交换格式。json采用的是一组键与值的映射,键与值之间使用“:”进行分隔,而键值对之间使用“,”进行分隔。json文件中的类型可以是:
- 对象类型:使用{}表示。
- 数组类型:使用[]表示。
- 字符串类型:使用双引号界定。
- 布尔类型:true与false。
- 数值类型:整数与浮点数。例如5,2.8等。
json格式的文件示例如下:
{
"bg": "green",
"title": {
"data": ["data1", "data2", "data3", "data4"],
"align": "左对齐"
}
}
从json文件的文件格式我们发现,jaon非常类似于Python中的字典类型。的确,我们可以在json文件与Python中的字典类型之间进行转换。json模块提供了相关的功能。
处理程序【dump&load】
使用json格式的文件可以方便的进行数据交换。我们可以通过json类型的数据进行对象的序列化与反序列化。所谓序列化,就是将对象类型转换成字符串的形式。而反序列化,即为将序列化的字符串恢复为对象类型。通过序列化与反序列化,我们就可以方便的对复杂的对象进行存储与恢复(因为文件读写只支持字符串类型),或者通过网络进行传输,将对象共享给远程的其他程序使用。
序列化与反序列化程序【dumps & loads】
因为Python中的数据类型与json格式的数据类型并非完全相符,因此,在进行转换的时候,可能会进行一些映射处理,如下(json -> Python):
- 布尔类型(true与false)映射Python中布尔类型(True与False)。
- 空值类型(null)映射为None。
- 整数与浮点类型映射为整数(int)与(float)类型。
- 字符串类型映射为字符串(str)类型。
- 数组类型([])映射为列表(list)类型。
- 对象类型(object)映射为字典(dict)类型。
映射程序示例。
json在序列化时,不能序列化我们自定义的类型(以上类型之外的类型)。如果我们需要自定义的类型也能够序列化,可以定义一个编码类,该类继承json.JSONEncoder,实现类中的default方法,指定序列化的方式,同时,在调用序列化方法时(dump或dumps),使用cls参数指定我们定义的编码类。
自定义序列化程序
14.3.3 pickle
我们也可以使用pickle模块提供的功能来序列化类型。在序列化自定义类型上,pickle可以比json模块更加方便(不需要定义类似的编码器类)。
pickle与json在序列化上的区别见下表。
表格 15 2 json与pickle在序列化上的区别
序列化 json pickle
序列化格式:文本格式,可进行正常查看。 二进制格式,不方便查看。
序列化类型的支持:支持一部分内建的类型,如果需要序列化自定义类型,需要编写编码类。 支持非常广泛的类型,包括自定义类型,不需要编写编码类。
适用广泛性:适用广泛,对于序列化的内容可以用于Python语言之外的程序中进行反序列化。 适用受限,只能用于Python程序中,其他语言的程序无法反序列化。
Pickle的问题和所有其他编程语言特有的序列化问题一样,就是它只能用于Python,并且可能不同版本的Python彼此都不兼容,因此,只能用Pickle保存那些不重要的数据,不能成功地反序列化也没关系。
14.4 上下文管理器
14.4.1 自定义上下文管理器
我们之前介绍过with的使用。通过with,我们可以代替try-finally语句,实现更简洁的资源清理工作。然而,with为什么有这种神奇的特性呢?是什么给了with如此的保证?
实际上,with语句跟随的表达式会返回一个上下文管理器,该上下文管理器中定义相关方法,在with开始执行与退出时会调用,也就是说,上下文管理器为with提供一个执行环境。
__enter__(self)
with语句体开始执行时,会调用该方法。我们可以在该方法中执行某些初始化的操作,该方法的返回值会赋值给with语句中as后面的变量。
__exit__(self, exc_type, exc_val, exc_tb)
with语句体执行结束后,会调用该方法。我们在__enter__方法中执行的初始化,就可以在该方法中执行相关的清理,例如,文件的关闭,线程锁的释放,状态的恢复等。这就可以实现于finally语句同样的功能。相关参数如下:
- exc_type:产生异常的类型。
- exc_val:产生异常类型的对象。
- exc_tb:traceback类型的对象,包含了异常产生位置的堆栈调用信息。
如果在with语句体中没有产生异常,则相关的参数(exc_type,exc_val与 exc_tb)的值为None。否则,异常类,异常对象与轨迹信息会依次传递给这三个参数,此时,如果该方法的返回值为True,则压制异常,即异常不会在with语句体结束后继续抛出,如果方法的返回值为False,则异常继续抛出。
对于with,也可以关联两个上下文管理器,例如:
with Manager1() as m1, Manager2 as m2:
语句
这相当于是:
with Manager1() as m1:
with Manager2() as m2:
语句
14.4.2 @contextmanager装饰器
在contextlib模块中,定义了@contextmanager装饰器,该装饰器可以用来修饰一个生成器,从而将生成器变成一个上下文管理器,从而可以省略编写完整的上下文管理器类,在一定程度上可以简化程序。
关于@contextmanager装饰器,有以下几点说明:
- 在@contextmanager修饰的生成器中,yield之前的语句会在进入with语句体时执行(相当于__enter__方法),而yield之后的语句会在离开with语句体时执行(相当于__eixt__方法)。
- with后的表达式会返回生成器对象(假设为gen_obj),进入with语句体时,内部会调用next函数,用来激活生成器对象,进而执行生成器的函数体:next(gen_obj)
- 从而令生成器对象执行。yield产生的值则会赋值给with语句as后的变量(相当于__enter__方法的返回值)。
- 当with语句体结束时,如果with语句体没有产生异常,则继续调用next,令生成器从之前yield暂停的位置处继续执行(这相当于实现__exit__方法)。如果with语句体产生异常,该异常会在生成器函数体yield的位置抛出。而如果生成器函数体没有处理该异常,将会导致yield之后的语句不会得到执行,这相当于是没有成功的执行__exit__方法。因此,为了能够保证yield之后的语句能够得到执行,我们应该总是在生成器的函数体内使用try-finally语句。