在单元测试中提取哈希种子

Mis*_*agi 4 python hash unit-testing python-3.x

我需要获取python使用的随机哈希种子来复制失败的单元测试.

如果PYTHONHASHSEED设置为非零整数,sys.flags.hash_randomization则可靠地提供它:

$ export PYTHONHASHSEED=12345
$ python3 -c 'import sys, os;print(sys.flags.hash_randomization, os.environ.get("PYTHONHASHSEED"))'
12345 12345
Run Code Online (Sandbox Code Playgroud)

但是,如果哈希是随机的,它只是指出种子使用,而不是其中:

$ export PYTHONHASHSEED=random
$ python3 -c 'import sys, os;print(sys.flags.hash_randomization, os.environ.get("PYTHONHASHSEED"))'
1 random
Run Code Online (Sandbox Code Playgroud)

信息sys.hash_info从不包括取决于种子的数据.使用python3.4之后哈希函数,尝试从给定的哈希重构种子似乎也是不可行的.


上下文:在微调算法时,我们已经看到了依赖于set/dict迭代次序的heisenbug.复制它们需要测试种子,最糟糕的是所有4294967295,但即使我们平均约100次测试也很长.

我们一直考虑将PYTHONHASHSEED外部设置为随机但已知的值,但我们希望避免使用这个额外的层.

Mar*_*ers 10

不,随机值被分配给unionuc字段,但是这从未暴露给Python代码.那是因为可能值的数量远远大于设置可以产生的值._Py_HashSecretPYTHONHASHSEED

当您未设置PYTHONHASHSEED或设置它时random,Python会生成一个随机的24字节值以用作种子.如果设置PYTHONHASHSEED为整数,那么该数字将通过线性同余生成器传递以生成实际种子(请参阅lcg_urandom()函数).问题是PYTHONHASHSEED仅限于4个字节.种子值比可以PYTHONHASHSEED单独设置的256**20倍.

可以在访问内部哈希值_Py_HashSecret使用结构ctypes:

from ctypes import (
    c_size_t,
    c_ubyte,
    c_uint64,
    pythonapi,
    Structure,
    Union,
)


class FNV(Structure):
    _fields_ = [
        ('prefix', c_size_t),
        ('suffix', c_size_t)
    ]


class SIPHASH(Structure):
    _fields_ = [
        ('k0', c_uint64),
        ('k1', c_uint64),
    ]


class DJBX33A(Structure):
    _fields_ = [
        ('padding', c_ubyte * 16),
        ('suffix', c_size_t),
    ]


class EXPAT(Structure):
    _fields_ = [
        ('padding', c_ubyte * 16),
        ('hashsalt', c_size_t),
    ]


class _Py_HashSecret_t(Union):
    _fields_ = [
        # ensure 24 bytes
        ('uc', c_ubyte * 24),
        # two Py_hash_t for FNV
        ('fnv', FNV),
        # two uint64 for SipHash24
        ('siphash', SIPHASH),
        # a different (!) Py_hash_t for small string optimization
        ('djbx33a', DJBX33A),
        ('expat', EXPAT),
    ]


hashsecret = _Py_HashSecret_t.in_dll(pythonapi, '_Py_HashSecret')
hashseed = bytes(hashsecret.uc)
Run Code Online (Sandbox Code Playgroud)

但是,您实际上无法对此信息执行任何操作.你不能设置_Py_HashSecret.uc一个新的Python进程,因为这样做会破坏大多数字典密钥集,然后你可以从Python代码(Python内部结构严重依赖字典)这样做,并且你的哈希值等于256*之一*4个可能的LCG值非常小.

你想在PYTHONHASHSEED任何地方设置一个已知值是一个更可行的方法.