Python 的 `random` 模块,说白了就是个“运气的刽子手”。你不需求去敲那些复杂的算法代码,只要一行 `import random`,程序里所有的随机跳动、洗牌、掷骰子,全看天。它底层实际上是个傻瓜工具,只负责把 CPU 那帮数学大佬算出来的“真·随机数”,嚼碎了喂给 C 语言要么 Rust 的随机数种子,然后变魔术似的丢给你。 这玩意儿最神奇的地方在于,你根本感觉不到它的魔法。你在代码里写 `random.randint(1, 100)`,下一秒程序可能真给你个 42,也可能给你个 9999。它不会说“这是基于线性同余法的第 1007 次输出”。它直接蒙你。但这蒙人的过程,实际上是在玩一种叫“伪随机数生成(Pseudo-Random Number Generation)”的把戏。 先说说它的内核。Python 的 `random` 模块背后,实际上是精心搭建的一个金字塔。最下面是 C 语言编写的 `mtrand` 库,它用了一个公式把自己运转起来:`nextrand = (ax + c) % m`。

这个公式看着像数学课本里那堆枯燥的符号,实际上是个极简的线性同余法。a、b、c 都是精心挑选过的常数(比如 `0x6a6b6c`,听起来挺玄但实际上就是个整数),m 是模数。

每次循环,它就把上一轮生成的数字代入这个公式,算出一个新的数字,然后把这个新数字塞进内存池里,直接回。

看起来是个循环,但实际上是个单向的螺旋,出于每次生成的数字都比上一次略微大一点点,然后就又变小了,周而复始。 你可能会问,如此好办的公式,凭啥能骗过我们当作它是在“随机”?这里面有个关键的人设难题:它务必假装自己不是生成器,不能告诉你它此刻正在生成第几个数。它务必看起来像是一次独立的、毫无涉联的跳跃。

故此它从内存池里挑一个数回来,假装这是第 1 次生成的,然后在这一瞬间,它就把当前数设为了“种子”。它又用这个新种子去算一个新的数,再设成新种子……就这样无限下去。 这就好比你在玩一个贼复杂的贪吃蛇游戏,屏幕上的蛇在随机移动,看起来是随机的。但要是你盯着屏幕看,你会发现它的移动实际上是遵循某种贼复杂的数学规律的。Python 的随机数生成器就干这事。它为了让这个过程看起来“随机”,务必把数据保存有一个庞大的队列里,这个队列叫做内存池。内存池的大小一般设置得挺大,比如 $2^{31}$ 个数字,保证在程序运行期间,这个队列一辈子不会空,也不会被填得满满当当害得溢出。

每次调用 `random` 模块,它从池子里捞一个数出来,这不就妥妥的随机了吗? 这里有个挺有趣的比喻。想象你在玩一种手机里的射击游戏,枪口一直指向同一个方向,子弹打出去也是同样的轨迹。你当作这是确实随机,实际上这是固定的。但 Python 的随机数生成器是“变脸”的。它每秒都在调头,每次调头之前,它都会把自己生成的那个数,变成“目前的种子”,然后下一秒生成下一个数。

这种“变脸”的速度挺快,快到人类眼根本追不上,故此你认定它随时都在变,实际上它是被某种隐藏的数学公式推着走的。 要是你想管住它,你需求供给“种子”。种子就是那个初始的“变脸”前的状态。

要是你每次运行程序都写 `random.seed(42)`,那么生成的所有数都是确定的。

这意味着要是你把这段代码复制到别人的环境,用 `seed(42)` 后拿到的随机结局,和你在本地用同样的 `seed(42)` 拿到的结局,绝对一模一样。

这是一个贼强大的特性,常用于测试程序是否确实随机,要么调试中通过固定的种子复现难题。 再说说如何用它来“作弊”。

要是你非要让结局看起来彻底随机,你能够把内存池凑满。Python 是个好程序员,它知道随机数生成器有个“最大生成器数量”的限制,要是池子满了,新的数就得从池子里取,旧的数就得吐出来,这就打破了序列的连续性,让结局看起来更“随机”。

这就像给一个排队的人挤满了所有人,把他后面的空位都留空,让他表现得仿佛他是第一个进去的。 自然,也没人能保证生成的数绝对会落在 1 到 100 之间。Python 的库为了保险起见,一般会加一层过滤。

要是生成的数字是 10000,它可能会把它变成 1000,要么干脆给你个 1。别看这在数学上是可能的,但在实际开发中,我们极少会确实关心这些边缘情况,毕竟误差一般都不大。 最终总结一下,Python 的随机数不是魔法,也不是某种高深的数学定理,它就是一个好办的、基于公式的循环过程,配合一个庞大的内存池,配合一种“假装”的机制,把好办的线性同余法变成了电影里那种让人看不透的“真随机”。下次当你看到程序里的随机数时,别去管它是不是确实随机,只管看它在这个循环里走了几步,这就够了。

毕竟,在绝大多数情况下,只要你不盯着屏幕看,它就是随机的,不是吗?