I called random.seed(234), then called random.randint(0, 99) and received 92. When I repeated this process again several times I received 86. When I called random.randint a second time then it return 92. I was expecting the first value to be 86 not 92. Why was it 92?
The full log output is below. I've included all of it incase there was some previous action that can explain the seemingly buggy behaviour:
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)]'
** edit, weird: "same" seemingly buggy behaviour repeated ** In the same terminal window I closed the previous ipython session. I did some command line activity, then I opened ipython again. I did some different work. Then I tried this again:
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
What's happening here is that something in the IPython system is making use of the random
module, and so consuming numbers from the random stream provided by the core Mersenne Twister generator. That means that if you're also using the random
module, you only see an unpredictable subset of the numbers from the stream, since IPython gets the rest.
I can reproduce the effect you're seeing reliably (on both Python 2 and Python 3) by hitting the <Enter>
key a few times randomly between invocations of random.randint
(though actually I'm using random.random
for simplicity). Here's one example session, with Python 3.6.2 and IPython 6.2.0 on macOS 10.12.6.
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
To check my hypothesis, I hacked in an override to the Random.random
method in the random.py
file in the standard library, by adding the following method to the Random
class:
def random(self):
print("random being called")
import traceback; traceback.print_stack()
return super(Random, self).random()
Now launch IPython, and hey presto! Lots of tracebacks. I won't reproduce the tracebacks in full (they're long), but here's the tail end of one of them:
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()
As you can see, the 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).
If you need a reliably reproducible random stream, create an explicit random.Random
instance and use that:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With