Python random.seed 行为异常

Python random.seed behaved strangely

我调用了 random.seed(234),然后调用了 random.randint(0, 99) 并收到了 92。当我再次重复这个过程几次时,我收到了 86。当我调用 random.randint 第二次然后它 return 92。我期待第一个值是 86 而不是 92。为什么是 92?

完整的日志输出如下。我已经包含了所有内容,以防之前的一些操作可以解释看似错误的行为:

In [1]: import random

In [2]: import string

In [3]: string.letters
Out[3]: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'

In [4]: string.ascii_letters
Out[4]: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

In [5]: string.printable
Out[5]: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~ \t\n\r\x0b\x0c'

In [6]: len(string.printable)
Out[6]: 100

In [7]: [string.printable[random.randint(0,99)] for i in range(20)]
Out[7]: 
['{',
'+',
'[',
'\r',
'R',
'Z',
'v',
'|',
'v',
'e',
'T',
'x',
'\',
'}',
'0',
'>',
'V',
'\n',
'`',
'`']

In [8]: ''.join([string.printable[random.randint(0,99)] for i in range(20)])
Out[8]: '%Z\%mx4Z53uUZIa5KHe*'

In [9]: ''.join([string.printable[random.randint(0,99)] for i in range(20)])
Out[9]: 'Fg\nDHW+oV?-9``}\x0by%xD'

In [10]: import os

In [11]: os.urandom(1)
Out[11]: '('

In [12]: os.urandom(1)
Out[12]: '8'

In [13]: os.urandom(1)
Out[13]: '\xb1'

In [14]: os.urandom(1)
Out[14]: ')'

In [15]: os.urandom(1)
Out[15]: '\x8c'

In [16]: os.urandom(1)
Out[16]: '^'

In [17]: os.urandom(1)
Out[17]: '{'

In [18]: os.urandom(1)
Out[18]: '\x8f'

In [19]: ''.join(os.urandom(10))
Out[19]: '{t\x8dR\x1d\x83\xef\xd6N\xbd'

In [20]: ''.join(os.urandom(10))
Out[20]: '\x96\\xf6\xe3\xf4/\x1f\xc7\x90\x02'

In [21]: from random import SystemRandom

In [22]: crypt = SystemRandom()

In [23]: ''.join([string.printable[crypt.randrange(100)] for i in range(20)])
Out[23]: "WoDVH\r1!?1+djB'f<;nW"

In [24]: ''.join([string.printable[crypt.randrange(100)] for i in range(20)])
Out[24]: '\rf?zo`7^{Y_Zx^[SYw7c'

In [25]: ''.join([string.printable[crypt.randrange(100)] for i in range(20)])
Out[25]: "3k*uGVIP'~^{P*~bserk"

In [26]: ''.join([string.printable[crypt.randrange(100)] for i in range(20)])
Out[26]: '~lkM/a&#_F&D\n<sC&i\r\n'

In [27]: random.seed(234)

In [28]: random.randint(0,99)
Out[28]: 92

In [29]: random.seed(234)

In [30]: random.randint(0,99)
Out[30]: 86

In [31]: random.seed(234)

In [32]: random.randint(0,99)
Out[32]: 86

In [33]: random.seed(234)

In [34]: random.randint(0,99)
Out[34]: 86

In [35]: random.randint(0,99)
Out[35]: 92

In [36]: random.randint(0,99)
Out[36]: 48

In [37]: random.seed(234)

In [38]: random.randint(0,99)
Out[38]: 86

In [39]: import sys

In [40]: sys.version_info
Out[40]: sys.version_info(major=2, minor=7, micro=13, releaselevel='final', serial=0)

In [41]: sys.version
Out[41]: '2.7.13 (default, Dec 17 2016, 23:03:43) \n[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)]'

** 编辑,奇怪:"same" 看似错误的行为重复出现 ** 在同一个终端 window 我关闭了之前的 ipython 会话。我做了一些命令行 activity,然后我再次打开 ipython。我做了一些不同的工作。然后我又试了一次:

In [37]: import random

In [38]: random.seed(234)

In [39]: random.randint(0, 99)
Out[39]: 85

In [40]: random.randint(0, 99)
Out[40]: 50

In [41]: random.seed(234)

In [42]: random.randint(0, 99)
Out[42]: 86

In [43]: random.randint(0, 99)
Out[43]: 92

我的 Python 2.7.5 无法重现相同的行为,但文档 (https://docs.python.org/2/library/random.html)说

If a is not None or an int or a long, then hash(a) is used instead. Note that the hash values for some types are nondeterministic when PYTHONHASHSEED is enabled.

我会(只是认为)说这可能是由散列函数的不确定行为引起的。你的 PYTHONHASHSEED 启用了吗?

这里发生的事情是 IPython 系统中的某些东西正在使用 random 模块,因此使用核心 Mersenne Twister 生成器提供的随机流中的数字。这意味着,如果您还使用 random 模块,您只能从流中看到不可预测的数字子集,因为 IPython 得到其余部分。

我可以通过在 random.randint(尽管实际上我为简单起见使用 random.random)。这是一个示例会话,在 macOS 10.12.6 上使用 Python 3.6.2 和 IPython 6.2.0。

In [1]: import random

In [2]: random.seed(234)

In [3]: 

In [3]: 

In [3]: random.random()
Out[3]: 0.8579160018299248

In [4]: random.random()
Out[4]: 0.5055065431394443

In [5]: random.seed(234)

In [6]: random.random()
Out[6]: 0.26476014305349627

In [7]: random.random()
Out[7]: 0.8579160018299248

In [8]: random.random()
Out[8]: 0.5055065431394443

为了验证我的假设,我通过将以下方法添加到 Random class:

def random(self):
    print("random being called")
    import traceback; traceback.print_stack()
    return super(Random, self).random()

现在启动 IPython,嘿嘿!很多回溯。我不会完整地重现回溯(它们很长),但这是其中一个的尾端:

  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/IPython/terminal/interactiveshell.py", line 376, in prompt_for_code
    pre_run=self.pre_prompt, reset_current_buffer=True)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/prompt_toolkit/interface.py", line 415, in run
    self.eventloop.run(self.input, self.create_eventloop_callbacks())
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/prompt_toolkit/eventloop/posix.py", line 157, in run
    random.shuffle(tasks)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/random.py", line 278, in shuffle
    j = randbelow(i+1)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/random.py", line 250, in _randbelow
    r = random()
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/random.py", line 98, in random
    import traceback; traceback.print_stack()

如您所见,prompt_toolkit library, which is used by IPython, uses the random module to shuffle its tasks (though this change was recently removed, according to the CHANGELOG).

如果您需要可靠的可重现随机流,请创建一个显式 random.Random 实例并使用它:

In [1]: from random import Random

In [2]: my_random = Random()

In [3]: my_random.seed(234)

In [4]: my_random.randint(0, 99)
Out[4]: 43

In [5]: my_random.randint(0, 99)
Out[5]: 33

In [6]: my_random.seed(234)

In [7]: my_random.randint(0, 99)
Out[7]: 43

In [8]: my_random.randint(0, 99)
Out[8]: 33