对包装的部分函数进行序列化

11 投票
1 回答
6827 浏览
提问于 2025-04-17 13:51

我正在尝试创建一个可以被“序列化”的装饰器,使用的是部分函数。不过,每次尝试时,我总是遇到序列化错误。

第一个简单的例子如下:

def decorator(func):
  def wrapper(**kwargs):
    return partial(func, **kwargs)
  return wrapper

@decorator
def decorated(x, y=1, z=2):
  return x+y+z

y5 = decorated(y=5)
pickle.dumps(y5)

这里的 partial 是从 functools 这个模块里来的。

稍微复杂一点的尝试是在 def wrapper 之前加上 @wraps,但这并没有解决问题。

我不太明白序列化到底是怎么回事。

1 个回答

11

问题出在你的装饰器上,而不是partial。一个partial对象应该可以正常被序列化:

>>> from pickle import *
>>> from functools import *
>>> f = partial(pow, 2)
>>> p = dumps(f)
>>> g = loads(p)
>>> g(5)
32

所以,你代码中的问题在于装饰器。它没有保留原始函数的名称。试试这个:

import pickle
from functools import *

def decorator(func):
    def wrapper(**kwargs):
        return partial(func, **kwargs)
    return wrapper

def decorated(x, y=1, z=2):
    return x+y+z

dd = decorator(decorated)

y5 = dd(y=5)
pickle.dumps(y5)

使用dd的修改应该能让序列化逻辑通过名称找到底层函数。这就是序列化的工作原理。

要查看序列化中的函数名称,可以查看dumps的输出:

>>> print pickle.dumps(y5)
cfunctools
partial
p0
(c__main__
decorated
p1
tp2
Rp3
(g1
(t(dp4
S'y'
p5
I5
sNtp6
b.

单词“decorated”需要能够被找到,应该和底层函数相等,而不是被装饰器隐藏。记住,当函数被序列化时,只有它们的名称会被存储。函数的实际内容不会在序列化中。

有一些变通方法,但并不优雅。你可以使用__setstate__()来保存函数名称和源代码。然后添加一个__getstate__()方法,通过执行源代码来恢复函数。

另外,你也可以提取函数对象中的字节码并保存它们。在恢复时,编译代码对象并执行它。

简而言之,你使用@符号的装饰器目标与函数序列化的工作方式直接冲突。为了实现你的目标,你需要自定义函数的序列化,让它保存函数的实际操作,而不仅仅是名称。

撰写回答