惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
Apple Machine Learning Research
Apple Machine Learning Research
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
SegmentFault 最新的问题
博客园 - 聂微东
罗磊的独立博客
W
WeLiveSecurity
博客园_首页
Scott Helme
Scott Helme
V
Visual Studio Blog
T
The Exploit Database - CXSecurity.com
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
Latest news
Latest news
L
Lohrmann on Cybersecurity
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
A
About on SuperTechFans
F
Full Disclosure
Y
Y Combinator Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 司徒正美
博客园 - Franky
C
CXSECURITY Database RSS Feed - CXSecurity.com
F
Fortinet All Blogs
Blog — PlanetScale
Blog — PlanetScale
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
阮一峰的网络日志
阮一峰的网络日志
S
Schneier on Security
雷峰网
雷峰网
博客园 - 【当耐特】
P
Privacy International News Feed
C
Cyber Attacks, Cyber Crime and Cyber Security
Engineering at Meta
Engineering at Meta
aimingoo的专栏
aimingoo的专栏
MongoDB | Blog
MongoDB | Blog
J
Java Code Geeks
T
Tor Project blog
V
V2EX
爱范儿
爱范儿
C
Check Point Blog
T
Threatpost
Project Zero
Project Zero
量子位
V
Vulnerabilities – Threatpost
Know Your Adversary
Know Your Adversary
I
Intezer
G
GRAHAM CLULEY
P
Privacy & Cybersecurity Law Blog
GbyAI
GbyAI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

hsfzxjy 的博客

解决 VSCode + CMake + MSVC 编译器信息乱码的问题 使用 3090 部署 1.58bit 动态量化版 DeepSeek R1 671b 如何在 VS Code DevContainer 中配置 HTTP 代理 如何在跳板机背后的服务器上使用 VS Code Remote - Containers Cohesive Digests for Ints and Floats Rust 中的隐匿概念 —— Place(位置) 美术馆 一尺之槌,日取其半,1075日而竭 老生常谈:使用 Cloudflare 自选 IP 加速站点访问 辩义 State、Nation 与 Country 将 Base64 编码的数据快速转换为 Uint8Array 折腾 NPU·第1章 —— 搭建 Level Zero 开发环境 折腾 NPU·第0章 —— Intel NPU 概述与 Level-Zero 新增域名 monad.run CSS 中为特定字符设置不同字体 Arbitary Lifetime Transmutation via Rust Unsoundness Dijkstra 算法的延伸 Manacher 回文计数算法 硬卧 Go Fact: Zero-sized Field at the Rear of a Struct Has Non-zero Size Display *big.Rat Losslessly and Smartly in Golang 代码的仪式 Building Electron From Scratch 中式亲属称谓研究之一:构建半群 Some Notes on Kotlin Coroutines Git sparse-checkout and partial clones for Mega-Repos 辩义“封建” Diving from the CUDA Error 804 into a bug of libnvidia-container Modern Cryptography, GPG and Integration with Git(hub) Move the Root Partition of Ubuntu A New Programmer Kicks a Roadblock Git-based Dependencies in Dart and Go Reversy Naming 人类一败涂地 Invalid Golang Pointers Can Bite You Even If You Don't Dereference Side Project(副业) A Flaw of Promoting Complex Trait Bounds in Rust Initialize Process Pool Worker with Individual Value Rust - Python FFI From Scratch [Extending Hexo For My Site] Part 1 [Extending Hexo For My Site] Part 0 Debug a 'torch.tensor(1).cuda()' hanging 不自由的互联网 Retrieve Contents over HTTP without curl or wget [Unravelling mocona] Part 1 - Verbosity or Anti-Pattern [Unravelling mocona] Part 0 - Preface Rough Notes on Deploying Vaultwarden & NextCloud Bookmarks 语言狂热者与实用主义者 Demystify the randomness in CUDA kernels Performant Bulk Mutations in IndexedDB Auto Rebuild .pyx Files with pyximport Cython and Threads Obtain a Random Available TCP Port with Bash Information Theory: KL Divergence Information Theory: Entropy and Mutual Information 铁板烧 西郊线 Proof of the Gumbel Max Trick Option::as_ref Rc, RefCell and Interior Mutability Visualizing Correlation 三月十日杂感 三月一日杂感 二月十一日杂感 一月二十六日杂感 SS Configuration 一月七日杂感 四月·病 Haskell 笔记:State Monad Haskell 笔记:Monad 引论 Haskell 笔记:Applicative Haskell 笔记:Category Theory and Functor Haskell 笔记:data, type, newtype Haskell 笔记:folds 使用 Aria2 在 Ubuntu 中下载百度云资源 从伪并行的 Python 多线程说起 一个 Reentrant Error 引发的对 Python 信号机制的探索和思考 Linux 文件权限 HSFZMUN 4.0 部署小记 午后雨·科大 最后的雨夜·广州 揭秘·变态的平方根倒数算法 神坑·Python 装饰类无限递归 Python“黑魔法”之 Encoding & Decoding Ubuntu 重新映射键盘布局 为什么我要翻墙 Python“黑魔法”之 Generator Coroutines 数学美 之 判断线段相交的最简方法 除夕杂感 17 行代码实现的简易 Javascript 字符串模板 Python“黑魔法”之 Meta Classes 诗集 生活,需要被“发现” 家书·十八岁成人礼 炫技?还是需求? 【译】响应式图片的现状 【译】“为什么有这么多的编程语言?” Wisecity 商赛总结——也谈前端自动化测试 记一次 DoS 诈骗网站的经历 那一年,我们望向星空
Understanding pickle in Python
2021-08-14 · via hsfzxjy 的博客

The module pickle shipped in Python could be used for generic-purpose object serialization and de-serialization. It’s been widely adopted or recommended as backend in scenarios like persisting states or IPC.

Employed by many famous frameworks, though, the magic behind it still seems to be vague for daily users, especially guys fresh to the language. People come across “unpicklable” errors from time to time, but don’t know the reason; or re-invent state persistence by themselves, even if pickle could be competent. People sometimes write error-prone codes, merely because they are afraid of or unaware of pickle.

This post thus attempts to clarify the usage of pickle module in an easy understanding way, by answering three questions.

What kind of object is picklable?

Before stepping in, let’s take a swift overview for the design of pickle module.

pickle separates the logic of serialization / de-serialization into two parts, using some kind of intermediate representation (IR). During serialization, an object is firstly transformed into IR, and then dumped into byte stream by pickle. De-serialization operates conversely. For most of the time, we users should only care about the first aspect, that is, the conversion between Python objects and IR.

To ease the burden of using pickle, the module set up a few principles that could generalize the concept “picklable” automatically to most user-defined objects. Users then need not to communicate with IR. The principles could be roughly summarized into

  1. Most built-in Python types and their instances are picklable.
  2. Containers like lists, dicts or sets with only picklable elements are picklable.
  3. Top-level classes or functions of a module are picklable (if configured properly).
  4. Objects with picklable __dict__ and picklable type are picklable.
  5. Objects holding external resources (open(), socket.socket(), etc.) are usually NOT picklable.

Principle 1 and 2 ensures that we can easily pickle built-in types 1, values 2 and their composition 3, which covers a large variety of daily-used objects.

If you want to pickle custom objects, Principle 3 and 4 come into play. If you define your class at the top-level of a module (and usually you should), the class is automatically picklable. An object instantiated from that class, if holding only picklable attributes (stored in __dict__), is also picklable.

How convenient! Nothing is needed but a simple pickle.load or pickle.dump to enjoy pickle services .

Principle 3 comes with a remark “if configured properly”. Technically, module-level values are picklable if they have the correct __qualname__. __qualname__ dictates the name of variable that holds the value. This is automatically done if the value is a class or function:

class C: pass
C.__qualname__
def f(): pass
f.__qualname__

which enables pickling for user-defined classes or functions.

But this does not apply to lambdas. A lambda by default would have __qualname__ == "<lambda>", which does not match the variable name that holds it. pickle thus cannot handle it normally

>>> f = lambda: 1
>>> pickle.dumps(f)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
_pickle.PicklingError: Cant pickle <function <lambda> at 0x7f7ed81ad3a0>: attribute look up <lambda> on __main__ failed
>>> f.__qualname__
'<lambda>'

To make it possible, one could assign __qualname__ for it manually

>>> f.__qualname__ = 'f'
>>> pickle.dumps(f)
b'\x80\x04\x95\x12\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x01f\x94\x93\x94.'

Principle 5 might be wierd at the first sight, but is indeed reasonable.

Let’s consider if a socket object could be dumped, and we load it at some point afterwards. There exist so many causes that could fail the de-serialization. Peer host malfunctions, link layer breaks, or out of system resources. Even if it succeeds, the loaded object is not identical to the original one, since it references to external resource with different internal state.

pickle therefore disables the stuff – but if you wish, you could control it by customizing the serialization / de-serialization behavior. Here we come to

What if my object is not picklable?

From now you would need to learn something about the IR, as well as the object protocol of pickling.

Fundamentally, an object’s __reduce__() would be called when it’s being serialized. The method’s expected behavior is described by the documentation in detail. Unoffically, we could regard the return value as kind of IR, which dictates how to locate the object in specific module, or necessary information to restore the object from scratch.

According to the doc, __reduce__() should return either a string or a tuple. We are going to take apart the two cases in the next moment.

If a string returned, it should be the name of a local variable relative to the object’s module. Remember the __qualname__ trick above? They do the same job. pickle treats the returned string as __qualname__ of the object, storing it along with the module name __module__ at serialization. The de-serializer afterwards would look up an attribute in the module with that string as name, and retrieve the object directly without creating from scratch.

It is useful for singleton object, i.e., object that be instantiated once and survives the whole program lifetime, for example, a database connection pool. We thus would not struggle on how to record the internal states for these objects.

When a tuple returned, well, it’s being a bit complicated. The tuple should contain 2~6 elements 4. The meaning of the elements would be described after, but before which, let’s get some understanding of how an object is created.

In the assumption of pickle module, an object is made up of a skeleton, and states. A skeleton is an initial version of an object, which is returned by a callable named “constructor”. Usually the constructor is the object’s __new__() method, but not necessarily. It could also be some factory function. States refer to all other attributes or elements that the object holds, which could be simple Python objects or external resources. At de-serialization, pickle would first set up a skeleton by calling constructor, then filling up the states. Either step can be customized to accord with your application logic.

Now we could talk about the scheme of returned tuple.

The 1-st and 2-nd elements describe how to set up a skeleton. The 1-st element is the constructor callable, and the 2-nd be a tuple of positional arguments, which the constructor takes. The two elements are both required. If no argument is needed, one should leave an empty tuple.

The remaining elements are optional, describing the states. pickle employs various strategies of restoring states. If the 6-th element provided, it should be a callable with signature (obj, state), which performs state updating with the object and the 3-rd element as arguments. If not provided, pickle would look up a method named __setstate__ on the object, which shares the same signature, and if found, it is served as the state updater. Otherwise, pickle expects the 3-rd element to be a dict, which would then be added to the object’s __dict__. The 4-th and 5-th elements are specialized for list- or dict-like object and less used. If supplied, they should be a list and a dict, which update the object through .extend() and .update() methods, respectively.

Directly implementing __reduce__() could be error-prone. pickle thus provides other object protocols to simplify the task. A list of the special methods could be found here. Users can implement some of them to serve the same purpose, e.g., __getnewargs_ex__() or __getnewargs__() for the 2-nd element, and __getstate__() for the 3-rd element.

Now let’s back to the title – what if my object is not picklable? The answer is, implementing your own pickling / unpickling logics via __reduce__() or other special methods. This section in the docs showcases a good example, where an object maintains a file which should be re-opened and re-sought at de-serialization.

Here we discussed the scenario of serializing our customized types. Now what if the object to be pickled is out of our control?

What if the object is out of my control?

In some cases, one might have the demand to alter the pickling behavior for a specific type, either it is not supported, or the serialized byte stream is not efficient enough. The type is maintained by some libaries, and out of your control, so you could not change its __reduce__() to fit your requirement. pickle introduces some other interfaces to mitigate the problem from different aspects.

Dispatch Tables

This is the recommended way to pickle objects without disturbing any other external codes. Aside from looking up special methods on object, pickle also relies on module copyreg to seek reducers. The function copyreg.pickle(type, reducer) associates callable reducer as the reducer function of type type. reducer should accept arguments and return IR just like a __reduce__() method, and it shadows the original __reduce__() on type. The documentation showcases the stuff with a simple example.

Persistent ID

Sometimes you would like to persist external objects that could be uniquely identified by some IDs. Think about entries of a database table, where foreign keys act as those IDs. Here storing IDs directly would be better and more efficient than turning the objects into IR. pickle have persistent ID for this purpose.

Unlike dispatch tables, resolution of persistent IDs is not defined in pickle. One should sub-class Pickler and Unpickler classes and overwrite persistent_id() and persistent_load(), respectively. An example of pickling table entries with persistent IDs could be found here.

reducer_override()

With only types given, users might still not able to decide the pickling behavior for some objects. If you want to pickle user-defined classes (not their instances) via dispatch tables, all reducings would be delegated to the same method, since in Python user-defined classes share a same base class type. Python 3.8 introduces the reducer_override() method on Pickler class to handle custom pickling in arbitary conditions. You might check technical details and examples at this link.

Personally, I found dispatch tables the most useful. You can always ensemble persistent IDs using IR, and dealing several different objects within a same reducer function is actually not that bad. In contrast, dispatch tables could enable the customization in a global manner, which is useful at some time.


Conclusion

It’s such a long way here, but we made it!

We’ve seen some basic usage of pickle, the fundamental object protocols of pickling, as well as some other interfaces for customization. Hope the stuffs might give you a rough idea on this module, and be helpful for dealing with pickling problems.


Author: hsfzxjy.
Link: .
License: CC BY-NC-ND 4.0.
All rights reserved by the author.
Commercial use of this post in any form is NOT permitted.
Non-commercial use of this post should be attributed with this block of text.