我正在编写一个兼容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编写相同的测试代码?
答案 0 :(得分:16)
在Python 3.2中,对随机模块进行了一些重构,以使输出在架构之间保持一致(给定相同的种子),请参阅issue #7889。 shuffle()
方法已切换为使用Random._randbelow()
。
但是,_randbelow()
方法也进行了调整,因此仅仅复制3.5版shuffle()
还不足以解决此问题。
也就是说,如果你传入自己的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]
答案 1 :(得分:1)
在Martijn Pieters上阐述了极好的答案和评论,在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测试相同随机代码的人来说,这种快速解决方法可能会有用!
答案 2 :(得分:0)
如果我错了,可能有人会纠正我,但似乎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版本位于 修复程序将在相关文档字符串中注明。 扩展现有参数范围并添加新参数 只要先前的行为保持不变,就可以使用这些参数。