Python 为什么包装random.random的随机方法似乎会影响RNG?

Python 为什么包装random.random的随机方法似乎会影响RNG?,python,python-3.x,random,Python,Python 3.x,Random,我试图通过在random()周围放置一个包装器来打印内容,从而记录对它的调用。令人惊讶的是,我注意到我开始得到不同的随机值。我创建了一个小示例来演示这种行为: 脚本main.py: import random def not_wrapped(seed): """ not wrapped """ gen = random.Random() gen.seed(seed) # def wrappy(func

我试图通过在
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))
python3.7.5 main.py的输出(与python3.6.9相同)

因此,正如您看到的,随机实例
gen
的状态不同,这取决于我是否在
wrappy
中包装gen.Random

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

有人知道发生了什么事吗

Python 3.6在线示例:

在Python3.8.2的这个在线repl上,问题没有显示出来:


(这个问题被交叉发布在:)

这个答案是关于Python 3.6的

该状态仅由gen.randint(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)
    
  • random.random.randrange
    为,它使用
    random.random.\u randbown
    生成随机数

  • …并检查
    random
    方法是否已被重写

  • 看起来问题出在下面:

    ...
    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`!
    
    。。。
    从类型导入MethodType为_MethodType,从内置MethodType为_内置MethodType
    ...
    
    def _randbown(self,n,int=int,maxsize=1看起来您是在比较
    getstate()
    的输出,而不是实际的随机输出。在文档中没有任何内容表明应该检查
    getstate()
    getstate()
    的输出仅用作
    setstate()的输入
    call。例如,它可能包含在您的案例中没有实际使用的字段,但为方便起见设置了这些字段,例如,一天中的时间。我实际上不知道引擎盖下发生了什么。@PresidentJamesK.Polk我认为它不包含类似的内容。无论如何,将
    getstate
    替换为对
    random的调用de>产生相同的输出。所以它在更新的版本中被修复了?那么你担心什么呢?@AlexWeavers我不一定担心,我想知道它为什么会发生-这很有趣,你不认为吗?如果没有人知道,它可能是一个仍然存在或可能再次出现的大版本。而且不是每个人都运行最新的python版本。在我的办公室,我们是在Python3.6上。
    
    ...
    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`!