使用给定种子随机播放 Python 2 和 3 之间的差异

Difference between Python 2 and 3 for shuffle with a given seed

我正在编写一个与 Python 2.7 和 3.5 兼容的程序。它的某些部分依赖于随机过程。我的单元测试使用任意种子,这会导致跨执行和语言的相同结果......除了使用 random.shuffle.

的代码

Python2.7 中的示例:

In[]:   import random
        random.seed(42)
        print(random.random())
        l = list(range(20))
        random.shuffle(l)
        print(l)
Out[]:  0.639426798458
        [6, 8, 9, 15, 7, 3, 17, 14, 11, 16, 2, 19, 18, 1, 13, 10, 12, 4, 5, 0]

Python 3.5 中的相同输入:

In []:  import random
        random.seed(42)
        print(random.random())
        l = list(range(20))
        random.shuffle(l)
        print(l)
Out[]:  0.6394267984578837
        [3, 5, 2, 15, 9, 12, 16, 19, 6, 13, 18, 14, 10, 1, 11, 4, 17, 7, 8, 0]

注意伪随机数是一样的,只是打乱后的列表不一样。正如预期的那样,重新执行单元不会改变它们各自的输出。

如何为 Python 的两个版本编写相同的测试代码?

在 Python 3.2 中,对随机模块进行了一些重构,以使跨体系结构的输出统一(给定相同的种子),请参阅 issue #7889shuffle() 方法已切换为使用 Random._randbelow().

但是,_randbelow()方法进行了调整,所以简单地复制shuffle()的3.5版本不足以解决这个问题。

也就是说,如果您传入自己的 random() 函数,Python 3.5 中的实现与 2.7 版本 没有变化,因此让你绕过了这个限制:

random.shuffle(l, random.random)

但是请注意,与现在相比,您受制于 #7889 试图解决的旧的 32 位与 64 位架构差异。

忽略几个优化和特殊情况,如果你包含 _randbelow() 3.5 版本可以向后移植为:

import random
import sys

if sys.version_info >= (3, 2):
    newshuffle = random.shuffle
else:
    try:
        xrange
    except NameError:
        xrange = range

    def newshuffle(x):
        def _randbelow(n):
            "Return a random int in the range [0,n).  Raises ValueError if n==0."
            getrandbits = random.getrandbits
            k = n.bit_length()  # don't use (n-1) here because n can be 1
            r = getrandbits(k)          # 0 <= r < 2**k
            while r >= n:
                r = getrandbits(k)
            return r

        for i in xrange(len(x) - 1, 0, -1):
            # pick an element in x[:i+1] with which to exchange x[i]
            j = _randbelow(i+1)
            x[i], x[j] = x[j], x[i]

它在 2.7 上为您提供与 3.5 相同的输出:

>>> random.seed(42)
>>> print(random.random())
0.639426798458
>>> l = list(range(20))
>>> newshuffle(l)
>>> print(l)
[3, 5, 2, 15, 9, 12, 16, 19, 6, 13, 18, 14, 10, 1, 11, 4, 17, 7, 8, 0]

详细阐述了 Martijn Pieters 的出色回答和评论,并在此 上,我终于找到了一个解决方法,可以说它不能回答我的问题,但同时不需要进行深度更改.总结:

  • random.seed 实际上使每个 random 函数都具有确定性,但不一定在各个版本中产生相同的输出;
  • PYTHONHASHSEED 设置为 0 会禁用字典和集合的哈希随机化,这在默认情况下会在 Python 3.
  • 中引入不确定性因素

因此,在启动 Python 3 个测试的 bash 脚本中,我添加了:

export PYTHONHASHSEED=0

然后,我临时更改了我的测试函数,以便暴力破解一个整数种子,该种子将在 Python 3 中重现 Python 2 中预期的结果。最后,我恢复了我的更改和替换行:

seed(42)

通过类似的方式:

seed(42 if sys.version_info.major == 2 else 299)

没什么可吹嘘的,但俗话说,实用胜于纯粹;)

这个快速解决方法可能对想要在 Python 的不同版本中测试相同随机代码的人有用!

如果我错了,有人可能会纠正我,但似乎 numpy.random 模块在 python 2 和 3 之间没有变化。

>>> import numpy as np
>>> l = list(range(20))
>>> np.random.RandomState(42).shuffle(l)
>>> l
[0, 17, 15, 1, 8, 5, 11, 3, 18, 16, 13, 2, 9, 19, 4, 12, 7, 10, 14, 6]

我在 Python 2.7(np 1.12.1)和 3.7(np 1.14.5)中得到了相同的结果。

该文档还指出生成的数字 should be the same between versions

Compatibility Guarantee A fixed seed and a fixed series of calls to ‘RandomState’ methods using the same parameters will always produce the same results up to roundoff error except when the values were incorrect. Incorrect values will be fixed and the NumPy version in which the fix was made will be noted in the relevant docstring. Extension of existing parameter ranges and the addition of new parameters is allowed as long the previous behavior remains unchanged.