【问题标题】:Difference between Python 2 and 3 for shuffle with a given seedPython 2 和 3 对给定种子进行随机播放的区别
【发布时间】:2016-08-14 14:09:41
【问题描述】:

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

Python 2.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 编写相同的测试代码?

【问题讨论】:

  • 另外,快速浏览 Py2 implementationPy3 implementation 表明 shuffle 本身有不止一种实现(尽管它们可能是等效的 - 我希望应该有一个单元测试引入了修改)。
  • 尝试random.seed(42, version=1)(或者可能是版本=2;但设置此参数),如记录的here
  • [random.random() for _ in range(20)] 确实在两个版本上返回相同的结果 - 所以看起来这是 random.shuffle 的实现更改...
  • @sascha 在直接使用 int 作为种子时不相关。
  • @sascha 在 Py3 下输出相同,unexpected keyword argument 在 Py2 下输出。

标签: python python-2.7 python-3.x shuffle random-seed


【解决方案1】:

在 Python 3.2 中,对 random 模块进行了一些重构,以使输出在架构之间保持一致(给定相同的种子),请参阅 issue #7889。将shuffle() 方法切换为使用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]

【讨论】:

  • @Marijn Pieters 这很好,作为奖励,我可以使用我已经为 Python 2 编写的相同测试。不过我会阅读这个问题。
  • 实际上,我的测试似乎在两个版本下都不起作用。具体来说,Py3版本有时会通过,有时会失败,这证明我的种子不足以使其具有确定性。我猜我使用的其他random 函数(即randrangechoice)也有同样的问题。确实很奇怪。
  • 是的,choicerandrange 也进行了重构,参见 this patch
  • 这两个函数都可以根据_randbelow() 的提取部分轻松向后移植,我将其作为本地函数包含在我的答案中。只需提取那个,然后从3.5 source code of random.py 中提取您需要的功能。
  • 是的,我肯定会迭代字典。这可以解释很多事情,cf。 stackoverflow.com/questions/30585108/… 了解更多信息。先生,您不仅回答了我的确切问题,而且在途中学到了很多有趣的东西。再次感谢您的时间和专业知识。
【解决方案2】:

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

  • 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 中测试相同随机代码的人有用!

【讨论】:

    【解决方案3】:

    如果我错了,有人可能会纠正我,但似乎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

    兼容性保证一个固定的种子和一系列固定的调用 使用相同参数的“RandomState”方法总是会产生 舍入误差的结果相同,除非值是 不正确。不正确的值将被修复,NumPy 版本在 所做的修复将在相关文档字符串中注明。 扩展现有参数范围并添加新参数 只要之前的行为保持不变,就允许使用参数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-23
      • 1970-01-01
      • 2014-12-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多