random.random()究竟在做什么

1 投票
6 回答
4588 浏览
提问于 2025-04-15 17:21
    random.shuffle(lst_shuffle, random.random)

我知道后面的部分是一个可选的参数。但是它到底是干什么的呢?我不太明白这是什么意思。这是文档中的内容。

random.random()¶ 返回下一个随机的浮点数 在范围 [0.0, 1.0) 内。

我还看到这个,范围 0,0, 1,0 是什么意思呢?

Pseudorandom number generators
Most, if not all programming languages have libraries that include a pseudo-random
number generator. This generator usually returns a random number between 0 and 1 (not
including 1). In a perfect generator all numbers have the same probability of being selected but
in the pseudo generators some numbers have zero probability.

6 个回答

1

第二个参数是一个函数,用来生成随机数,这些随机数会用来打乱第一个参数中的序列。如果你不提供自己的函数,默认会使用 random.random。

如果你想要自定义打乱的方式,可以提供这个参数。

而你自定义的函数需要返回的数字范围是 [0.0, 1.0) - 也就是说,0.0 包含在内,1.0 不包含在内。

4

第二个参数是用来指定你想用哪个随机数生成器的。如果你需要一个比random.random更“好”的生成器,这个参数就很有用。比如说,涉及安全的应用可能需要使用一个加密安全的随机数生成器。

random.randomrandom.random()之间的区别在于,前者是指向一个生成简单随机数的函数,而后者则是实际调用这个函数来生成随机数。

如果你有另一个想用的随机数生成器,你可以这样写:

random.shuffle(x, my_random_number_function)

至于random.random(默认的生成器)在做什么,它使用了一种叫做梅森旋转算法的算法,来生成一个看起来随机的浮点数,这个数在0到1之间(不包括1),而这个区间内的所有数字出现的概率是一样的。

这个区间从0到1只是一个约定。

6

现有的回答很好地解决了问题的具体内容,但我觉得有必要提一下一个相关的问题:为什么你特别可能想要给shuffle传递一个替代的“随机生成器”,而不是random模块中的其他函数。引用一下文档

请注意,即使对于相对较小的 len(x),x的所有排列组合总数也大于大多数随机数生成器的周期;这意味着大多数长序列的排列组合永远无法生成。

这里提到的“随机数生成器”其实是指那些更严格意义上称为-随机数生成器的东西——这些生成器能够很好地模拟随机性,但完全是基于算法的,因此被认为不是“真正的随机”。任何这样的算法方法都会有一个“周期”——最终它会开始重复。

Python的random模块使用了一种特别好且经过充分研究的伪随机生成器,叫做梅森旋转算法,它的周期是2**19937-1——这个数字写出来有超过6000位。当我在我的笔记本电脑上运行时,我每秒可以生成大约500万个这样的数字:

$ python -mtimeit -s'import random' 'random.random()'
1000000 loops, best of 3: 0.214 usec per loop

假设有一台更快的机器,能够每秒生成十亿个这样的数字,那么这个周期大约需要105985年才能重复——而目前对宇宙年龄的最佳估计是不到1.5*1012年。因此,要达到重复的点,几乎需要一个不可想象的宇宙生命周期的数量;-)。即使你能在宇宙中的每个原子上运行这样的十亿每秒的生成器,仍然需要超过105800个宇宙生命周期才能开始重复。

所以,你可能会合理地怀疑,这种对重复的担忧有点理论化,而不是实际问题;-)。

然而,阶乘(用于计算长度为N的序列的排列组合)增长得也很快。例如,梅森旋转算法可能能够生成长度为2080的序列的所有排列,但绝对不能生成长度为2081或更长的序列。如果不是因为“宇宙的生命周期”这个问题,文档中对“即使是相对较小的len(x)”的担忧是有道理的——我们知道,当序列长度合理长时,许多可能的排列组合永远无法通过这样的伪随机生成器进行洗牌,因此人们可能会担心,即使是几次洗牌,我们实际上引入了什么样的偏差!:-)

os.urandom提供了对操作系统提供的任何物理随机源的访问——在Windows上是CryptGenRandom,在Linux上是/dev/urandom等。os.urandom生成字节序列,但借助struct模块,我们可以很容易地将它们转换为随机数字

>>> n = struct.calcsize('I')
>>> def s2i(s): return struct.unpack('I', s)[0]
... 
>>> maxi = s2i(b'\xff'*n) + 1
>>> maxi = float(s2i(b'\xff'*n) + 1)
>>> def rnd(): return s2i(os.urandom(n))/maxi

现在我们可以调用random.shuffle(somelist, rnd),并减少对偏差的担忧;-)。

不幸的是,测量显示,这种随机数生成器的方法比调用random.random()慢大约50倍——如果我们需要很多随机数,这可能是一个重要的实际考虑(如果我们不需要,那么对可能偏差的担忧可能是多余的;-)。os.urandom的方法在可预测、可重复的方式下使用也很困难(例如,用于测试目的),而使用random.random()时,只需在测试开始时提供一个固定的初始random.seed,就能保证可重复的行为。

因此,在实际应用中,os.urandom仅在你需要“加密质量”的随机数时使用——即那些一个决心坚定的攻击者无法预测的随机数——因此愿意为使用它而付出实际的代价,而不是使用random.random

撰写回答