如何将Python字典序列化到MySQL?

7 投票
3 回答
5566 浏览
提问于 2025-04-16 23:56

我在几个StackOverflow的问题中查找了如何将Python对象进行序列化(也就是“腌制”)并存储到数据库里的方法。我收集到的信息如下:

  • 首先要用 import pickle 或者 import cpickle。如果你对性能有要求,建议用后者。
  • 假设 dict 是一个Python字典(或者其他任何Python对象),你可以用 pickled = pickle.dumps(dict) 来进行序列化。
  • 然后把 pickled 存储到MySQL的BLOB列中,使用任何可以和数据库沟通的模块。
  • 想要取出来的时候,使用 pickle.loads(pickled) 来恢复成Python字典。

我只是想确认一下我理解得对不对。有没有什么重要的东西我漏掉了?会不会有什么副作用?这真的是这么简单吗?

背景信息:我想做的就是存储Google地理编码器的响应,这些响应在我这里是嵌套的Python字典。我只用到了响应对象的一小部分,不知道以后是否会需要更多的内容。所以我想到了存储这个响应,以免重复进行几百万次查询。

3 个回答

0

如果你有嵌套的字典,使用时要小心。大多数Python对象不能被“序列化”(也就是不能被转换成一种可以存储的格式),而且你可以把任何对象作为值放进一个dict里。更糟糕的是,能转换成字符串并存储在SQL里的Python对象更少。

不过,如果你使用klepto,那么序列化和存储到数据库的过程就会变得非常简单,而且大多数Python对象都能正常工作。

我们先在一个dict(或者多个字典)里创建一些典型的Python对象:

>>> class Foo(object):                                 
...   def bar(self, x):
...     return self.y + x
...   y = 1
... 
>>> d1 = {'a': min, 'b': lambda x:x**2, 'c': [1,2,3], 'd': Foo()}
>>> f = Foo(); f.y = 100
>>> d2 = {'a': max, 'b': lambda x:x**3, 'c': [2,1,3], 'd': f}

接下来,我们创建一个嵌套的dict,并将其存储到MYSQL数据库中。

>>> import klepto
>>> a = klepto.archives.sql_archive('mysql://user:pass@localhost/foo', dict={'d1':d1, 'd2':d2})
>>> a.dump()

然后,我们删除与数据库的连接……再建立一个新的连接。load命令会把所有对象加载到内存中。

>>> del a
>>> b = klepto.archives.sql_archive('mysql://user:pass@localhost/foo')
>>> b.load()

现在,我们可以访问内存中这些对象的副本。

>>> b['d1']
{'a': <built-in function min>, 'c': [1, 2, 3], 'b': <function <lambda> at 0x1037ccd70>, 'd': <__main__.Foo object at 0x103938ed0>}
>>> b['d1']['b'](b['d1']['d'].bar(1))
4
>>> b['d2']['b'](b['d2']['d'].bar(1))
1030301
>>> 

我们退出Python……然后重新启动一个新的会话。这次,我们决定使用cached=False,这样就可以直接与数据库进行交互。

dude@hilbert>$ python
Python 2.7.10 (default, May 25 2015, 13:16:30) 
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import klepto
>>> b = klepto.archives.sql_archive('mysql://user:pass@localhost/foo', cached=False)
>>> b['d2']['b'](b['d2']['d'].bar(1))
1030301
>>> b['d1']['b'](b['d1']['d'].bar(1))
4
>>> 

klepto利用了sqlalchemy,所以它可以在多个数据库后端之间工作……而且,它还提供了相同的基于dict的接口来存储在磁盘上(无论是文件还是目录)。

1

如果速度真的很重要,我刚刚测试了从一个大Python字典(35MB)加载数据和从MySQL表中选择所有键值的速度对比:

使用Pickle的方法:

import time, pickle
t1 = time.clock()
f = open('story_data.pickle','rb')
s = pickle.load(f)
print time.clock() - t1

使用MySQL的方法:

import database as db
t1 = time.clock()
data,msg = db.mysql(""" SELECT id,story from story_data;""")
data_dict = dict([(int(x),y.split(',')) for x,y in data])
print time.clock() - t1

输出结果:

使用Pickle的方法:32.0785171704秒

使用MySQL的方法:3.25916336479秒

如果速度提升十倍就足够了,那么数据库的结构可能就不那么重要了。值得注意的是,我把所有用逗号分隔的数据分成了列表,作为36,000个键的值,结果只花了3秒钟。所以我决定不再使用Pickle来处理大数据集,因为我用的其他400行代码也只花了大约3秒,而加载Pickle数据却花了32秒。

另外要注意:

cPickle的工作方式和Pickle一样,但速度快了超过50%。

不要尝试把一个装满字典的类用Pickle保存到MySQL里:它不能正确恢复,至少对我来说是这样的。

2

其实这真的很简单……只要你不需要数据库了解字典里的内容。如果你需要以某种结构化的方式访问字典里的数据,那你就得花更多的心思了。

另一个需要注意的地方是你打算放进字典里的东西。Python的pickle序列化功能很聪明,能处理大多数情况,不需要你额外添加支持。不过,当它不工作的时候,搞清楚问题出在哪里可能会很困难。所以如果可以的话,尽量把字典里的内容限制在Python自带的类型上。如果你开始添加自定义类的实例,最好保持这些类简单,不要搞一些复杂的属性存储或访问方式。同时,要小心添加来自插件的类或类型的实例。一般来说,如果你在序列化或反序列化时遇到难以理解的问题,先检查一下字典里是否有非内置类型的内容。

撰写回答