Python 检查pickle转储是否存在依赖项

Python 检查pickle转储是否存在依赖项,python,pickle,Python,Pickle,假设我编写以下代码: 导入pickle def foo(): 返回“foo” def bar(): 返回“bar”+foo() pickle.dump(条形,打开('bar.bin','wb')) 此时,我有了一个二进制转储(当然没有全局范围内的foo依赖项)。现在,如果我运行以下行 temp=pickle.load(打开('bar.bin','rb')) 我得到了下面的错误,在阅读之后它是完全有意义的 错误:AttributeError:无法在上获取属性“bar” 这当然是一个很小的例子,但

假设我编写以下代码:

导入pickle
def foo():
返回“foo”
def bar():
返回“bar”+foo()
pickle.dump(条形,打开('bar.bin','wb'))
此时,我有了一个二进制转储(当然没有全局范围内的
foo
依赖项)。现在,如果我运行以下行

temp=pickle.load(打开('bar.bin','rb'))

我得到了下面的错误,在阅读之后它是完全有意义的

错误:AttributeError:无法在上获取属性“bar”

这当然是一个很小的例子,但我很好奇是否有一种通用的方法可以检查正确取消pickle转储所需的依赖项。一个简单的解决方案是处理属性错误(如上图所示),但我能以编程方式执行吗?

您可以使用生成一个反汇编操作流,这将允许您收集有关酸洗数据需要访问哪些模块和名称的信息。我会在这里使用这个函数

现在,该模块针对的是在pickle库上工作的核心开发人员,因此关于该模块发出的操作码的文档只在中找到,而且许多都与协议的特定版本相关,但这里有一些有趣的操作码。在
GLOBAL
的情况下,加载的名称是操作码参数,在另一种情况下,您需要查看堆栈。堆栈比push和pop操作稍微复杂一些,但是,由于可变长度的项(列表、dict等)使用标记对象来允许取消勾选器检测此类对象何时完成,并且有一个记忆功能来避免重复命名流中的项

模块代码详细说明了堆栈、备忘录和各种操作码的工作方式,但如果您只需要知道引用了哪些名称,通常可以忽略其中的大部分内容

因此,对于您的流,假设流总是格式良好的,
dis()
函数的以下简化将允许您提取
GLOBAL
STACK\u GLOBAL
操作码引用的所有名称:

导入工具
def get_名称(流):
“”“从pickle流生成(模块、qualname)元组”“”
堆栈,标记堆栈,备忘录=[],[],[]
mo=pickletools.markobject
对于pickletools.genops(流)中的op、arg和pos:
#到目前为止,模拟pickle堆栈和标记方案
#必须允许我们检索STACK_GLOBAL使用的名称
before,after=op.stack\u before,op.stack\u after
numtopop=len(之前)
如果op.name==“全局”:
产量元组(参数拆分(1,无))
elif op.name==“STACK_GLOBAL”:
产量(堆栈[-2],堆栈[-1])
elif mo在或之前(op.name==“POP”,堆栈和堆栈[-1]为mo):
markpos=markstack.pop()
当堆栈[-1]不是mo时:
stack.pop()
stack.pop()
尝试:
numtopop=before.index(mo)
除值错误外:
numtopop=0
elif op.name在{“PUT”、“BINPUT”、“LONG_BINPUT”、“MEMOIZE”}中:
如果op.name==“备忘录化”:
memo.append(堆栈[-1])
其他:
备注[arg]=堆栈[-1]
numtopop,在=0之后,[]#memoize和put不弹出堆栈
{“GET”、“BINGET”、“LONG_BINGET”中的elif op.name:
arg=备忘录[arg]
如果是numtopop:
del stack[-numtopop:]
如果mo在以下时间之后输入:
markstack.append(pos)
如果len(after)==1且op.arg不是None:
stack.append(arg)
其他:
stack.extend(之后)
并为您的示例输入提供一个简短的演示:

pickle\u bar=pickle.dumps(条) >>>对于mod,get_name(pickled_条)中的qualname: ... 打印(f“模块:{mod},名称:{qualname}”) ... 模块:\uuuuu main\uuuuuuuu,名称:bar 或者是一个稍微复杂一些的例子,其中有一个相同的例子:

导入检查 >>>pickled_sig_set=pickle.dumps({inspect.signature(bar)}) >>>对于mod,get_name中的qualname(pickled_sig_set): ... 打印(f“模块:{mod},名称:{qualname}”) ... 模块:检查,名称:签名 模块:检查,名称:\ u空
后者利用回忆录将
inspect
名称重新用于
inspect.Signature.empty
引用,以及跟踪集合元素开始位置的标记。

无论如何,请务必阅读整个。除了关于pickle的功能及其局限性的优秀参考之外,底部还有一个see-allow部分,它告诉您关于pickle的内容,允许您以编程方式反汇编pickle数据格式。然后,您可以从反汇编中了解需要存在哪些模块才能加载pickle。当然,这不会找到可传递的依赖项-pickle中没有任何提示表明,无论
bar
是什么,都取决于
foo
函数。再多的pickle检查也无法告诉您有关
foo
@user2357112:nope的信息,而且也无法保证以后加载pickle时导入的对象定义与酸洗时使用的对象定义相同。许多基于Zope的项目在加载pickle时都会通过将东西插入
sys.modules
或创造性地使用
\uuuu setstate\uuu
来运行升级!