检查 pickle 转储的依赖关系

2023-12-13

假设我写了下面的代码:

import pickle
def foo():
    return 'foo'

def bar():
    return 'bar' + foo()

pickle.dump(bar, open('bar.bin', 'wb'))

此时,我有一个二进制转储(当然不依赖于foo从全球范围来看)。现在,如果我运行以下行

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

我收到以下错误,阅读后这是完全合理的this.

错误:AttributeError:无法在 main'(内置)>

这当然是一个最小的例子,但我很好奇是否有一种通用的方法可以检查正确解封pickle转储所需的依赖项。一个简单的解决方案是处理属性错误(如上面的情况),但我可以通过编程来完成吗?


您可以使用pickletools module生成反汇编操作流,这将让您收集有关腌制数据需要访问哪些模块和名称的信息。我会用pickletools.genops()在这里发挥作用。

现在,该模块是aimed在从事 pickle 库工作的核心开发人员中,因此有关其发出的操作码的文档只能在模块源代码,并且许多都与协议的特定版本相关,但是GLOBAL and STACK_GLOBAL opcodes这里有一些有趣的操作码。如果是GLOBAL,加载的名称是操作码参数,在其他情况下,您需要查看堆栈。然而,堆栈比压入和弹出操作稍微复杂一些,因为可变长度项(列表、字典等)使用标记对象来允许 unpickler 检测此类对象何时完成,并且有记忆功能,以避免必须重复命名流中的项目。

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

因此,对于您的流,并假设该流是总是格式良好,以下简化dis()函数可以让你提取所有引用的名称GLOBAL and STACK_GLOBAL操作码:

import pickletools

def get_names(stream):
    """Generates (module, qualname) tuples from a pickle stream"""

    stack, markstack, memo = [], [], []
    mo = pickletools.markobject

    for op, arg, pos in pickletools.genops(stream):
        # simulate the pickle stack and marking scheme, insofar
        # necessary to allow us to retrieve the names used by STACK_GLOBAL

        before, after = op.stack_before, op.stack_after
        numtopop = len(before)

        if op.name == "GLOBAL":
            yield tuple(arg.split(1, None))
        elif op.name == "STACK_GLOBAL":
            yield (stack[-2], stack[-1])

        elif mo in before or (op.name == "POP" and stack and stack[-1] is mo):
            markpos = markstack.pop()
            while stack[-1] is not mo:
                stack.pop()
            stack.pop()
            try:
                numtopop = before.index(mo)
            except ValueError:
                numtopop = 0
        elif op.name in {"PUT", "BINPUT", "LONG_BINPUT", "MEMOIZE"}:
            if op.name == "MEMOIZE":
                memo.append(stack[-1])
            else:
                memo[arg] = stack[-1]
            numtopop, after = 0, []  # memoize and put do not pop the stack
        elif op.name in {"GET", "BINGET", "LONG_BINGET"}:
            arg = memo[arg]
    
        if numtopop:
            del stack[-numtopop:]
        if mo in after:
            markstack.append(pos)
    
        if len(after) == 1 and op.arg is not None:
            stack.append(arg)
        else:
            stack.extend(after)

以及示例输入的简短演示:

>>> pickled_bar = pickle.dumps(bar)
>>> for mod, qualname in get_names(pickled_bar):
...     print(f"module: {mod}, name: {qualname}")
...
module: __main__, name: bar

或者一个稍微复杂一点的例子inspect.Signature()实例对于相同的:

>>> import inspect
>>> pickled_sig_set = pickle.dumps({inspect.signature(bar)})
>>> for mod, qualname in get_names(pickled_sig_set):
...     print(f"module: {mod}, name: {qualname}")
...
module: inspect, name: Signature
module: inspect, name: _empty

后者利用记忆来重新使用inspect的名称inspect.Signature.empty参考,以及跟踪集合元素开始位置的标记。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

检查 pickle 转储的依赖关系 的相关文章

随机推荐