每次程序都是不同的 cloudpickle 运行

Different cloudpickle everytime the program is run

考虑以下 python 代码:

import cloudpickle


class Foo:
    def __init__(self, num):
        self.num = num


def outer(num):
    return Foo(num)


print(cloudpickle.dumps(outer))

每次您 运行 代码时都会产生不同的 pickle。使用 pickletools 分析 pickle 文件显示以下差异:

144c144
<   552: \x8c         SHORT_BINUNICODE '2e3db4572bb349268962a75a8a6f034c'
---
>   552: \x8c         SHORT_BINUNICODE '89ee770de9b745c4bbe83c353f1debba'

现在,我了解到 cloudpickle 不保证 运行pickle 文件的确定性。 (link),但我很好奇为什么这两个 pickle 文件不同。看起来上面的差异是因为 Foo class.

的某种不同哈希

请注意,我运行 python 程序固定 PYTHONHASHSEED

PS: 这足以重现问题:

import pickletools
import cloudpickle


class Foo:
    def __init__(self, num):
        self.num = num


pickletools.dis(cloudpickle.dumps(Foo))

所以似乎每个 class 都有一个 属性 被烤到 cloudpickle 中,但我不知道那个 属性 是什么。

好奇!

我深入研究了源代码,发现它不是 class 的 属性,甚至不是计算的哈希值,它是 just a random identifier generated with uuid4() per class

该函数被 _class_getnewargs() here, which is called by _dynamic_class_reduce() here 调用,其中包含评论

Save a class that can't be stored as module global. This method is used to serialize classes that are defined inside functions, or that otherwise can't be serialized as attribute lookups from global modules.

如果 class 不在 __main__ 模块中,事情就会简单得多(因为从最终的拆包者的角度来看,__main__ 可以是任何东西);如果你做 from b import outer 和 cloudpickle that outer,你会得到

    0: \x80 PROTO      5
    2: \x95 FRAME      15
   11: \x8c SHORT_BINUNICODE 'b'
   14: \x94 MEMOIZE    (as 0)
   15: \x8c SHORT_BINUNICODE 'outer'
   22: \x94 MEMOIZE    (as 1)
   23: \x93 STACK_GLOBAL
   24: \x94 MEMOIZE    (as 2)
   25: .    STOP

因为泡菜而不是所有的巫毒 cloudpickle 会泡菜 __main__ 中的东西。