为什么包装random.Random的random方法好像对RNG有影响?

Mob*_*erg 5 python random python-3.x

我试图random()通过在它周围放置一个包装器来记录调用以打印内容。令人惊讶的是,我注意到我开始获得不同的随机值。我创建了一个小示例来演示该行为:

脚本main.py:

import random

def not_wrapped(seed):
    """ not wrapped """
    gen = random.Random()
    gen.seed(seed)

    # def wrappy(func):
    #     return lambda: func()
    # gen.random = wrappy(gen.random)

    gen.randint(1, 1)
    return gen.getstate()

def wrapped(seed):
    """ wrapped """
    gen = random.Random()
    gen.seed(seed)

    def wrappy(func):
        return lambda: func()
    gen.random = wrappy(gen.random)

    gen.randint(1, 1)
    return gen.getstate()

for s in range(20):
    print(s, not_wrapped(s) == wrapped(s))
Run Code Online (Sandbox Code Playgroud)

输出python3.7.5 main.py(与 python 3.6.9 相同)

0 True
1 False
2 False
3 False
4 False
5 True
6 False
7 False
8 False
9 False
10 True
11 False
12 False
13 False
14 False
15 True
16 False
17 True
18 False
19 True
Run Code Online (Sandbox Code Playgroud)

所以正如你看到的 Random 实例的状态gen是不同的,这取决于我是否包装了 gen.random wrappy

如果我调用randint()两次,则所有种子 0-19 的测试都将失败。对于 Python 2.7.17,wrappednot_wrapped函数每次都返回相同的随机值(为每个种子打印 True)。

有谁知道发生了什么?

Python 3.6 在线示例:http : //tpcg.io/inbKc8hK

在此在线 repl 上的 Python 3.8.2 上,问题显示:https : //repl.it/@DavidMoberg/ExemplaryTeemingDos

(此问题已交叉发布在:https : //www.reddit.com/r/learnpython/comments/i597at/is_there_a_bug_in_random/

For*_*Bru 4

这个答案是关于Python 3.6的。

状态只会通过gen.randint(1, 1)调用而改变,所以让我们看看它在幕后调用了什么。

  1. 是以下的实现random.Random.randint

    def randint(self, a, b):
        """Return random integer in range [a, b], including both end points.
        """
    
        return self.randrange(a, b+1)
    
    Run Code Online (Sandbox Code Playgroud)
  2. random.Random.randrange在上面,它使用random.Random._randbelow...生成随机数

  3. ...它在下面randint实现,并检查该方法是否random已被覆盖!

看起来_randbelow是这个问题:

...
from types import MethodType as _MethodType, BuiltinMethodType as _BuiltinMethodType
...


def _randbelow(self, n, int=int, maxsize=1<<BPF, type=type,
               Method=_MethodType, BuiltinMethod=_BuiltinMethodType):
    "Return a random int in the range [0,n).  Raises ValueError if n==0."

    random = self.random
    getrandbits = self.getrandbits

    # CHECKS IF random HAS BEEN OVERRIDDEN!

    # If not, this is the default behaviour:

    # Only call self.getrandbits if the original random() builtin method
    # has not been overridden or if a new getrandbits() was supplied.
    if type(random) is BuiltinMethod or type(getrandbits) is Method:
        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

    # OTHERWISE, it does something different!

    # There's an overridden random() method but no new getrandbits() method,
    # so we can only use random() from here.

    # And then it goes on to use `random` only, no `getrandbits`!
Run Code Online (Sandbox Code Playgroud)

(供参考,这里getrandbits是用C实现的,这里也是用C实现的。)random

所以存在差异:该randint方法最终的行为会有所不同,具体取决于random是否已被重写。