如何散列 class 或函数定义?

How to hash a class or function definition?

背景

在试验机器学习时,我经常重用以前通过 pickling/unpickling 训练的模型。 然而,在处理特征提取部分时,不混淆不同的模型是一个挑战。 因此,我想添加一项检查,以确保模型是使用与测试数据完全相同的特征提取过程进行训练的。

问题

我的想法如下: 与模型一起,我将在泡菜转储中包含一个哈希值,该哈希值对特征提取过程进行指纹识别。

在训练模型或将其用于 prediction/testing 时,模型包装器被赋予符合特定协议的特征提取 class。 当然,在 class 上使用 hash() 是行不通的,因为它不是跨调用持久的。 所以我想我也许可以找到定义 class 的源文件,并从该文件中获取哈希值。

但是,可能有一种方法可以直接从 class 的内存内容中获取稳定的哈希值。 这会有两个好处: 如果找不到源文件,它也可以工作。 而且它可能会忽略对源文件的不相关更改(例如,修复模块文档字符串中的拼写错误)。 classes 有可以在这里使用的代码对象吗?

您要找的只是一个散列过程,其中包含 class 定义的所有重要细节。 (可以通过递归地包含 它们的 定义来包含基本 classes。)为了最大限度地减少错误匹配,基本思想是对 序列化 你的class。所以从 pickle 开始:它支持比 hash 更多的类型,并且当它使用身份时,它使用基于名称的 可重现的 身份。这使其成为递归策略基本情况的良好候选者:处理函数和 classes 其 contents 很重要,并让它处理任何引用的辅助对象。

所以按案例定义序列化。调用一个对象 special 如果它属于下面的任何情况,但最后一个。

  • 对于被视为包含特殊对象的 tuple
    1. 人物t
    2. len
    3. 的序列化
    4. 每个元素的序列化,顺序
  • 对于被视为包含特殊对象的 dict
    1. 字符d
    2. len
    3. 的序列化
    4. 每个名称和值的序列化,按排序顺序
  • 对于定义显着的 class:
    1. 字符C
    2. __bases__
    3. 的序列化
    4. vars
    5. 的序列化
  • 对于定义显着的函数:
    1. 字符f
    2. __defaults__
    3. 的序列化
    4. __kwdefaults__ 的序列化(在 Python 3 中)
    5. __closure__的序列化(但使用单元格而不是单元格本身)
    6. vars
    7. 的序列化
    8. __code__
    9. 的序列化
  • 对于代码对象(因为 pickle 根本不支持它们):
    1. 字符c
    2. co_argcountco_nlocalsco_flagsco_codeco_constsco_namesco_freevars的连载, 和 co_cellvars, 按此顺序; none 其中非常特别
  • 对于静态或 class 方法对象:
    1. 字符sm
    2. __func__
    3. 的序列化
  • 对于 属性:
    1. 字符p
    2. fgetfsetfdel 的序列化顺序
  • 对于任何其他对象:pickle.dumps(x,-1)

(你实际上从来没有 store 所有这些:只需在顶级函数中创建一个你选择的 hashlib 对象,并在递归部分 update它与序列化的每一部分依次进行。)

类型标签是为了避免冲突,特别是没有前缀。二元泡菜已经没有前缀了。您可以根据对其内容的确定性分析(即使是启发式分析)或上下文来决定是否使用容器,只要您保持一致即可。

一如既往,平衡误报与漏报是一门艺术:对于一个函数,您可以包含 __globals__(修剪已经序列化的对象以避免大量序列化,即使不是无限序列化)或只是其中找到的任何 __name__。省略 co_varnames 会忽略重命名局部变量,这很好,除非自省很重要; co_filenameco_name.

类似

您可能需要支持更多类型:寻找不正确 pickle 的静态属性和默认参数(因为它们 包含 对特殊类型的引用)或根本。当然,请注意某些类型(如文件对象)是不可修改的 因为 很难或不可能序列化它们(尽管与 pickle 不同,一旦你可以像处理任何其他函数一样处理 lambda已完成 code 个对象)。冒着错误匹配的风险,您可以选择仅序列化此类对象的 type(一如既往,以字符 ? 为前缀,以区别于实际具有的类型位置)。