为什么即使 PYTHONHASHSEED=0,Python 集合的顺序也不确定?

Pao*_*ila 8 python hash set python-3.x

我正在开发一个基于代理的模型,其中我使用不同类型的代理类,这些类的实例被分配给不同类型的对象,例如学校、公司、家庭等。我遇到的问题是我在调试时无法强制执行运行的再现性,由于模型的复杂性,这使得任务变得非常困难。经过长时间的调查,我意识到问题与集合的顺序有关(当然应用了内置的随机和 numpy 随机种子)。即使我设置了 PYHTONHASHSEED=0,我也观察到每次运行时集合的顺序是随机的。当代理移动时,这使得我的模型的每次运行都不同。

当然,我知道套装并不意味着有订单。当从对象中移除代理时,我想使用它们使模型尽可能轻巧和快速。我希望它们的行为是随机的,除非我需要调试引发异常的特定运行。

我添加了以下代码,以便可以验证我的声明。在启动代码之前,我总是通过导出从命令行设置 PYTHONHASHSEED。我从代码打印 PYTHONHASHSEED 值以检查该值是否确实已更新

import os
import random
import numpy as np

print('PYTHON HASH SEED IS', os.environ['PYTHONHASHSEED'])

random.seed(1)
np.random.seed(2)

class S:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __repr__(self):
        return "".join([type(self).__name__, "_{0.a!r}_",
                        "School", "_{0.b!r}" ]).format(self)

list1 = np.random.randint(1, 100,size=40)
list2 = np.random.randint(1, 10,size=40)
d1 = dict()
s1 = set()
d1['students'] = s1
# assign students to d1
for s_id, sch_id in zip(list1, list2):
    d1['students'].add(S(s_id, sch_id))

print(s1)
Run Code Online (Sandbox Code Playgroud)

奇怪的是,如果我使用整数作为集合成员而不是类实例,则无法检测到随机性。问题是否与集合成员是类实例这一事实有关?为什么 ?

当然,我可以重塑代理分配给模型对象的方式,并用列表替换集合,但如果可能的话,我想了解这个问题。我使用的版本是python 3.5.4

Sha*_*ger 9

您存储的(类型S)对象来自没有覆盖__eq__和的类__hash__,因此它们使用默认实现,即基于对象标识

用户定义的类默认有__eq__()__hash__()方法;与它们相比,所有对象都比较不相等(除了它们自己)并x.__hash__()返回一个适当的值,以便同时x == y暗示x is yhash(x) == hash(y)

对象标识(作为 CPython 的一个实现细节)等同于分配对象的内存地址(原始指针值),并且分配器将在每次运行时返回不同的地址,因此每次的排序都会不同。ints 没有这个问题,因为它们具有基于非身份的平等和散列;它们基于值而不是身份进行散列,因此精确的内存地址无关紧要。

要使用固定种子为您的自定义类获得一致的排序,您需要定义特殊的相等和散列方法,例如:

def __hash__(self):
    return hash((self.a, self.b))

def __eq__(self, other):
    if not isinstance(other, S):
        return NotImplemented
    return self.a == other.a and self.b == other.b
Run Code Online (Sandbox Code Playgroud)