random.random()究竟在做什么
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 个回答
第二个参数是一个函数,用来生成随机数,这些随机数会用来打乱第一个参数中的序列。如果你不提供自己的函数,默认会使用 random.random。
如果你想要自定义打乱的方式,可以提供这个参数。
而你自定义的函数需要返回的数字范围是 [0.0, 1.0) - 也就是说,0.0 包含在内,1.0 不包含在内。
第二个参数是用来指定你想用哪个随机数生成器的。如果你需要一个比random.random
更“好”的生成器,这个参数就很有用。比如说,涉及安全的应用可能需要使用一个加密安全的随机数生成器。
random.random
和random.random()
之间的区别在于,前者是指向一个生成简单随机数的函数,而后者则是实际调用这个函数来生成随机数。
如果你有另一个想用的随机数生成器,你可以这样写:
random.shuffle(x, my_random_number_function)
至于random.random
(默认的生成器)在做什么,它使用了一种叫做梅森旋转算法的算法,来生成一个看起来随机的浮点数,这个数在0到1之间(不包括1),而这个区间内的所有数字出现的概率是一样的。
这个区间从0到1只是一个约定。
现有的回答很好地解决了问题的具体内容,但我觉得有必要提一下一个相关的问题:为什么你特别可能想要给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
。