为什么pickle协议2允许我序列化打开的文件对象?

16 投票
2 回答
2831 浏览
提问于 2025-04-16 23:52

考虑一下:

>>> import pickle
>>> thing = open('foobar.txt','w')
>>> pickle.dumps(thing)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/pickle.py", line 1366, in dumps
    Pickler(file, protocol).dump(obj)
  File "/usr/lib/python2.6/pickle.py", line 224, in dump
    self.save(obj)
  File "/usr/lib/python2.6/pickle.py", line 306, in save
    rv = reduce(self.proto)
  File "/usr/lib/python2.6/copy_reg.py", line 70, in _reduce_ex
    raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle file objects

这看起来完全合理——当然我不能把一个打开的文件句柄进行序列化。但是:

>>> pickle.dumps(thing, 2)
'\x80\x02c__builtin__\nfile\nq\x00)\x81q\x01.'
>>> pickle.loads(pickle.dumps(thing, 2))
<closed file '<uninitialized file>', mode '<uninitialized file>' at 0x7ff3c078>

显然我可以序列化一个打开的文件,只是没什么用。

这是故意这样设计的吗?这让我在代码中隐藏了一个错误,我错误地序列化了一个拥有文件的对象。在某些情况下,这个对象还持有一个 pyodbc 游标,结果也是一样。

我在 PEP 307 中没有看到任何相关内容。这是一个疏忽,还是有什么重要的事情我没注意到,能让我在使用协议2进行序列化时得到我想要的异常?

我正在使用 Python 2.6.5。我知道,我知道,但这是我这个版本自带的。

2 个回答

0

如果你想要更好的表现,可以使用 dill。它不会对像文件这样的对象进行序列化,但它知道如何处理文件句柄。具体来说,如果文件存在,dill 会把反序列化后的文件句柄指向这个文件;如果文件不存在,那么这个文件句柄就会被关闭。

Python 2.7.8 (default, Jul  3 2014, 05:59:29) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>>        
>>> thing = open('foobar.txt', 'w')
>>> thing
<open file 'foobar.txt', mode 'w' at 0x10e3c2c00>
>>> dill.loads(dill.dumps(thing))
<open file 'foobar.txt', mode 'w' at 0x10e3c2c90>
>>> 

你可以在这里获取 dillhttps://github.com/uqfoundation

10

Python Wiki上说:

你不能对打开的文件对象、网络连接或数据库连接进行序列化(也就是“腌制”)。想想其实很合理——因为序列化无法保证在你反序列化(也就是“解腌”)的时候,这些连接还能存在。而创建这些连接的过程也超出了序列化能自动处理的范围。如果你真的想序列化一个有问题属性的对象,可以看看序列化文档中的__getstate____setstate____getinitargs__,通过使用这些方法,你可以排除掉那些有问题的属性。

不过,我发现了这个错误报告,里面提到其实你是可以序列化文件对象的。这似乎是个意外的情况。在Python 3.2中已经修复了这个问题。

如果你想避免这个问题,可以看看能否把这个补丁适配到Python 2.6上。否则,你只需要小心你要序列化的内容就可以了。

撰写回答