面向对象的 Python - 对象序列化

在数据存储的上下文中,序列化是将数据结构或对象状态转换为可存储(例如,在文件或内存缓冲区中)或稍后传输和重建的格式的过程。

在序列化中,对象被转换为可以存储的格式,以便能够稍后对其进行反序列化并从序列化格式重新创建原始对象。

Pickle

Pickling 是将 Python 对象层次结构转换为字节流(通常不是人类可读的)以写入文件的过程,这也称为序列化。Unpickling 是相反的操作,即将字节流转换回有效的 Python 对象层次结构。

Pickle 是操作上最简单的存储对象的方法。 Python Pickle 模块是一种面向对象的方法,可以将对象直接存储在特殊的存储格式中。

它能做什么?

  • Pickle 可以非常轻松地存储和重现字典和列表。
  • 存储对象属性并将其恢复到相同状态。

Pickle 不能做什么?

  • 它不保存对象代码。只保存它的属性值。
  • 它不能存储文件句柄或连接套接字。

简而言之,我们可以说,pickling 是一种将数据变量存储到文件中并从文件中检索数据变量的方法,其中变量可以是列表、类等。

要使用 Pickle 来处理某些东西,您必须 −

  • 导入 pickle
  • 将变量写入文件,例如
pickle.dump(mystring, outfile, protocol),

其中第三个参数 protocol 是可选的 要取消 pickle 必须 −

导入 pickle

将变量写入文件,例如

myString = pickle.load(inputfile)

方法

pickle 接口提供了四种不同的方法。

  • dump() − dump() 方法序列化为打开的文件(类似文件的对象)。

  • dumps() − 序列化为字符串

  • load() − 从类似打开的对象反序列化。

  • loads() − 从字符串反序列化。

基于上述过程,以下是"pickling"的示例。

Pickling

输出

My Cat pussy is White and has 4 legs
Would you like to see her pickled? Here she is!
b'\x80\x03c__main__
Cat
q\x00)\x81q\x01}q\x02(X\x0e\x00\x00\x00number_of_legsq\x03K\x04X\x05\x00\x00\x00colorq\x04X\x05\x00\x00\x00Whiteq\x05ub.'

因此,在上面的例子中,我们创建了一个 Cat 类的实例,然后对其进行了 pickle,将我们的"Cat"实例转换为一个简单的字节数组。

这样,我们可以轻松地将字节数组存储在二进制文件或数据库字段中,并在以后从我们的存储支持中将其恢复为原始形式。

此外,如果您想创建一个带有 pickle 对象的文件,您可以使用 dump() 方法(而不是 dumps*()* 方法),同时传递一个打开的二进制文件,然后 pickle 结果将自动存储在文件中。

[….]
binary_file = open(my_pickled_Pussy.bin', mode='wb')
my_pickled_Pussy = pickle.dump(Pussy, binary_file)
binary_file.close()

解封

将二进制数组转换为对象层次结构的过程称为解封。

解封过程是使用 pickle 模块的 load() 函数完成的,并从简单的字节数组返回完整的对象层次结构。

让我们使用前面示例中的 load 函数。

解封

输出

MeOw is black
Pussy is white

JSON

JSON(JavaScript 对象表示法)是 Python 标准库的一部分,是一种轻量级的数据交换格式。它易于人类阅读和编写。它易于解析和生成。

由于其简单性,JSON 是我们存储和交换数据的一种方式,这是通过其 JSON 语法实现的,并且在许多 Web 应用程序中使用。因为它是人类可读的格式,这可能是在数据传输中使用它的原因之一,除了它在使用 API 时的有效性之外。

JSON 格式的数据示例如下 −

{"EmployID": 40203, "Name": "Zack", "Age":54, "isEmployed": True}

Python 使使用 Json 文件变得简单。用于此目的的模块是 JSON 模块。此模块应包含在您的 Python 安装中(内置)。

那么让我们看看如何将 Python 字典转换为 JSON 并将其写入文本文件。

JSON 到 Python

读取 JSON 意味着将 JSON 转换为 Python 值(对象)。json 库将 JSON 解析为 Python 中的字典或列表。为了做到这一点,我们使用 loads() 函数(从字符串加载),如下所示 −

Json to Python

输出

Json to Python Output

下面是一个示例 json 文件,

data1.json
{"menu": {
   "id": "file",
   "value": "File",
   "popup": {
      "menuitem": [
         {"value": "New", "onclick": "CreateNewDoc()"},
         {"value": "Open", "onclick": "OpenDoc()"},
         {"value": "Close", "onclick": "CloseDoc()"}
      ]
   }
}}

以上内容(Data1.json)看起来像一本传统字典。我们可以使用 pickle 来存储此文件,但其输出不是人类可读的形式。

JSON(Java Script Object Notification)是一种非常简单的格式,这也是它受欢迎的原因之一。现在让我们通过以下程序查看 json 输出。

Java Script Object Notification

输出

Java Script Object Notification Output

上面我们打开 json 文件(data1.json)进行读取,获取文件处理程序并传递给 json.load 并返回对象。当我们尝试打印对象的输出时,它与 json 文件相同。尽管对象的类型是字典,但它以 Python 对象的形式出现。正如我们看到的这个 pickle 一样,写入 json 很简单。上面我们加载了 json 文件,添加了另一个键值对并将其写回到同一个 json 文件。现在,如果我们看到 data1.json,它看起来不同。即与我们之前看到的格式不同。

为了使我们的输出看起来相同(人类可读的格式),请在程序的最后一行中添加几个参数,

json.dump(conf, fh, indent = 4, separators = (‘,’, ‘: ‘))

与 pickle 类似,我们可以使用 dumps 打印字符串,使用 loads 加载。下面是一个例子,

String with Dumps

YAML

YAML 可能是所有编程语言中最人性化的数据序列化标准。

Python yaml 模块称为 pyaml

YAML 是 JSON 的替代品 −

  • 人类可读的代码 − YAML 是最人性化可读的格式,以至于连首页内容都以 YAML 显示以说明这一点。

  • 紧凑的代码 −在 YAML 中,我们使用空格缩进来表示结构,而不是括号。

  • 关系数据的语法 − 对于内部引用,我们使用锚点 (&) 和别名 (*)。

  • 它广泛使用的领域之一是查看/编辑数据结构 − 例如配置文件、调试期间的转储和文档标题。

安装 YAML

由于 yaml 不是内置模块,我们需要手动安装它。在 Windows 机器上安装 yaml 的最佳方法是通过 pip。在 Windows 终端上运行以下命令来安装 yaml,

pip install pyaml(Windows 计算机)
sudo pip install pyaml(*nix 和 Mac)

运行上述命令后,屏幕将根据当前最新版本显示以下内容。

Collecting pyaml
Using cached pyaml-17.12.1-py2.py3-none-any.whl
Collecting PyYAML (from pyaml)
Using cached PyYAML-3.12.tar.gz
Installing collected packages: PyYAML, pyaml
Running setup.py install for PyYAML ... done
Successfully installed PyYAML-3.12 pyaml-17.12.1

为了测试它,转到 Python shell 并导入 yaml 模块,导入 yaml,如果没有发现错误,那么我们可以说安装成功。

安装 pyaml 后,让我们看看下面的代码,

script_yaml1.py
Yaml

上面我们创建了三个不同的数据结构,字典、列表和元组。在每个结构上,我们执行 yaml.dump。重点是输出如何在屏幕上显示。

输出

Yaml 输出

字典输出看起来很干净。即。 key: value(键:值)。

空格分隔不同的对象。

列表用破折号 (-) 表示

元组首先用 !!Python/tuple 表示,然后采用与列表相同的格式。

加载 yaml 文件

假设我有一个 yaml 文件,其中包含

---
# An employee record
name: Raagvendra Joshi
job: Developer
skill: Oracle
employed: True
foods:
   - Apple
   - Orange
   - Strawberry
   - Mango
languages:
   Oracle: Elite
   power_builder: Elite
   Full Stack Developer: Lame
education:
   4 GCSEs
   3 A-Levels
   MCA in something called com

现在让我们编写一个代码,通过 yaml.load 函数加载此 yaml 文件。以下是相同的代码。

Yaml Load Function

由于输出看起来不太可读,我最后使用 json 对其进行了美化。比较我们得到的输出和我们拥有的实际 yaml 文件。

输出

Yaml3

软件开发最重要的方面之一是调试。在本节中,我们将看到使用内置调试器或第三方调试器进行 Python 调试的不同方法。

PDB – Python 调试器

PDB 模块支持设置断点。断点是程序的故意暂停,您可以在其中获取有关程序状态的更多信息。

要设置断点,请插入以下行

pdb.set_trace()

示例

pdb_example1.py
import pdb
x = 9
y = 7
pdb.set_trace()
total = x + y
pdb.set_trace()

我们在此程序中插入了几个断点。程序将在每个断点处暂停(pdb.set_trace())。要查看变量内容,只需输入变量名称即可。

c:\Python\Python361>Python pdb_example1.py
> c:\Python\Python361\pdb_example1.py(8)<module>()
-> total = x + y
(Pdb) x
9
(Pdb) y
7
(Pdb) total
*** NameError: name 'total' is not defined
(Pdb)

按 c 或继续执行程序,直到下一个断点。

(Pdb) c
--Return--
> c:\Python\Python361\pdb_example1.py(8)<module>()->None
-> total = x + y
(Pdb) total
16

最终,您将需要调试更大的程序 - 使用子程序的程序。有时,您试图查找的问题可能位于子程序中。请考虑以下程序。

import pdb
def squar(x, y):
   out_squared = x^2 + y^2
   return out_squared
if __name__ == "__main__":
   #pdb.set_trace()
   print (squar(4, 5))

现在运行上述程序,

c:\Python\Python361>Python pdb_example2.py
> c:\Python\Python361\pdb_example2.py(10)<module>()
-> print (squar(4, 5))
(Pdb)

我们可以使用 ? 来获取帮助,但箭头指示即将执行的行。此时,按 s 到 s 进入该行会很有帮助。

(Pdb) s
--Call--
>c:\Python\Python361\pdb_example2.py(3)squar()
-> def squar(x, y):

这是对函数的调用。如果您想要概览代码的执行情况,请尝试 l −

(Pdb) l
1 import pdb
2
3 def squar(x, y):
4 -> out_squared = x^2 + y^2
5
6 return out_squared
7
8 if __name__ == "__main__":
9 pdb.set_trace()
10 print (squar(4, 5))
[EOF]
(Pdb)

您可以按 n 前进到下一行。此时您位于 out_squared 方法内,并且可以访问函数内声明的变量,即 x 和 y。

(Pdb) x
4
(Pdb) y
5
(Pdb) x^2
6
(Pdb) y^2
7
(Pdb) x**2
16
(Pdb) y**2
25
(Pdb)

因此我们可以看到 ^ 运算符不是我们想要的,而是需要使用 ** 运算符来求平方。

这样我们就可以在函数/方法中调试程序了。

日志记录

自 Python 2.3 版以来,日志记录模块一直是 Python 标准库的一部分。由于它是一个内置模块,因此所有 Python 模块都可以参与日志记录,因此我们的应用程序日志可以包含您自己的消息以及来自第三方模块的消息。它提供了很大的灵活性和功能性。

日志记录的好处

  • 诊断日志记录 − 它记录与应用程序操作相关的事件。

  • 审计日志记录 −它记录事件以供业务分析。

消息以"严重性(severity)"级别编写和记录 &minu

  • DEBUG (debug()) − 用于开发的诊断消息。

  • INFO (info()) − 标准"进度"消息。

  • WARNING (warning()) − 检测到一个不严重的问题。

  • ERROR (error()) − 遇到错误,可能很严重。

  • CRITICAL (critical()) − 通常是致命错误(程序停止)。

让我们看看下面的简单程序,

import logging

logging.basicConfig(level=logging.INFO)

logging.debug('this message will be ignored') # This will not print
logging.info('This should be logged') # it'll print
logging.warning('And this, too') # It'll print

上面我们记录了严重性级别的消息。首先我们导入模块,调用 basicConfig 并设置日志级别。上面设置的级别是 INFO。然后我们有三个不同的语句:调试语句、信息语句和警告语句。

logging1.py 的输出

INFO:root:This should be logged
WARNING:root:And this, too

由于信息语句位于调试语句下方,我们无法看到调试消息。要在输出终端中也获取调试语句,我们只需更改 basicConfig 级别。

logging.basicConfig(level = logging.DEBUG)

在输出中我们可以看到,

DEBUG:root:this message will be ignored
INFO:root:This should be logged
WARNING:root:And this, too

此外,默认行为意味着如果我们不设置任何日志记录级别,则会出现警告。只需注释掉上述程序中的第二行并运行代码即可。

#logging.basicConfig(level = logging.DEBUG)

输出

WARNING:root:And this, too

Python 内置的日志级别实际上是整数。

>>> import logging
>>>
>>> logging.DEBUG
10
>>> logging.CRITICAL
50
>>> logging.WARNING
30
>>> logging.INFO
20
>>> logging.ERROR
40
>>>

我们还可以将日志消息保存到文件中。

logging.basicConfig(level = logs.DEBUG, filename = 'logging.log')

现在所有日志消息都将进入当前工作目录中的文件 (logging.log),而不是屏幕。这是一种更好的方法,因为它允许我们对收到的消息进行事后分析。

我们还可以为日志消息设置日期戳。

logging.basicConfig(level=logging.DEBUG, format = '%(asctime)s %(levelname)s:%(message)s')

输出将类似于,

2018-03-08 19:30:00,066 DEBUG:this message will be ignored
2018-03-08 19:30:00,176 INFO:This should be logged
2018-03-08 19:30:00,201 WARNING:And this, too

基准测试

基准测试或分析基本上是为了测试您的代码执行速度有多快以及瓶颈在哪里?这样做的主要原因是为了优化。

timeit

Python 附带一个名为 timeit 的内置模块。您可以使用它来计时小代码片段。timeit 模块使用特定于平台的时间函数,以便您获得尽可能准确的时间。

因此,它允许我们比较每个代码的两批代码,然后优化脚本以获得更好的性能。

timeit 模块有一个命令行界面,但也可以导入。

有两种方法可以调用脚本。让我们首先使用脚本,为此运行以下代码并查看输出。

import timeit
print ( 'by index: ', timeit.timeit(stmt = "mydict['c']", setup = "mydict = {'a':5, 'b':10, 'c':15}", number = 1000000))
print ( 'by get: ', timeit.timeit(stmt = 'mydict.get("c")', setup = 'mydict = {"a":5, "b":10, "c":15}', number = 1000000))

输出

by index: 0.1809192126703489
by get: 0.6088525265034692

上面我们使用了两种不同的方法,即通过下标和 get 来访问字典键值。我们执行了 100 万次语句,因为对于非常小的数据来说,它的执行速度太快了。现在我们可以看到索引访问比 get 快得多。我们可以多次运行代码,执行时间会有轻微的变化,以便更好地理解。

另一种方法是在命令行中运行上述测试。让我们开始吧,

c:\Python\Python361>Python -m timeit -n 1000000 -s "mydict = {'a': 5, 'b':10, 'c':15}" "mydict['c']"
1000000 loops, best of 3: 0.187 usec per loop

c:\Python\Python361>Python -m timeit -n 1000000 -s "mydict = {'a': 5, 'b':10, 'c':15}" "mydict.get('c')"
1000000 loops, best of 3: 0.659 usec per loop

以上输出可能因您的系统硬件以及系统中当前正在运行的所有应用程序而异。

如果我们想调用一个函数,下面我们可以使用 timeit 模块。因为我们可以在函数内添加多个语句进行测试。

import timeit

def testme(this_dict, key):
   return this_dict[key]

print (timeit.timeit("testme(mydict, key)", setup = "from __main__ import testme; mydict = {'a':9, 'b':18, 'c':27}; key = 'c'", number = 1000000))

输出

0.7713474590139164