1. random偽隨機數生成器
random模塊基於Mersenne Twister算法提供了一個快速偽隨機數生成器。原來開發這個生成器是為了向蒙特卡洛模擬生成輸入,Mersenne Twister算法會生成大周期近均勻分布的數,因此適用於大量不同類型的應用。
1.1 生成隨機數
random()函數從所生成的序列返回下一個隨機的浮點值。返回的所有值都落在0<=n<1.0區間內。
import random for i in range(5): print('%04.3f' % random.random(), end=' ') print()
重復運行這個程序會產生不同的數字序列。
要生成一個指定數值區間內的數,則要使用uniform()。
import random for i in range(5): print('{:04.3f}'.format(random.uniform(1, 100)), end=' ') print()
傳入最小值和最大值,uniform()會使用公式min+(max-min)*random()來調整random()的返回值。
1.2 指定種子
每次調用random()都會生成不同的值,並且在一個非常大的周期之后數字才會重復。這對於生成唯一值或變化的值很有用,不過有些情況下可能需要提供相同的數據集,從而以不同的方式處理。對此,一種技術是使用一個程序生成隨機值,並保存這些隨機值,以便在另一個步驟中再做處理。不過,這對於量很大的數據來說可能並不實用,所以random包含了一個seed()函數,可以用來初始化偽隨機數生成器,使它能生成一個期望的值集。
import random random.seed(1) for i in range(5): print('{:04.3f}'.format(random.random()), end=' ') print()
種子(seed)值會控制由公式生成的第一個值,該公式可用來生成偽隨機數。由於公式是確定的,所以改變種子后便設置了將生成的整個序列。seed()的參數可用是任意的可散列對象。默認為使用一個平台特定的隨機源(如果有的話)。但如果沒有這樣一個隨機源,則使用當前時間。
1.3 保存狀態
random()使用的偽隨機算法的內部狀態可以保存,並用於控制后續生成的隨機數。如果在繼續生成隨機數之前恢復前一個狀態,則會減少出現重復的可能性,即避免出現之前輸入中重復的值或值序列。getstate()函數會返回一些數據,以后可以借助setstate()利用這些數據重新初始化偽隨機數生成器。
import random import os import pickle if os.path.exists('state.dat'): # Restore the previously saved state print('Found state.dat, initializing random module') with open('state.dat', 'rb') as f: state = pickle.load(f) random.setstate(state) else: # Use a well-known start state print('No state.dat, seeding') random.seed(1) # Produce random values for i in range(3): print('{:04.3f}'.format(random.random()), end=' ') print() # Save state for next time with open('state.dat', 'wb') as f: pickle.dump(random.getstate(), f) # Produce more random values print('\nAfter saving state:') for i in range(3): print('{:04.3f}'.format(random.random()), end=' ') print()
getstate()返回的數據是一個實現細節,所以這個例子用pickle將數據保存到一個文件;否則,它會把偽隨機數生成器當作一個黑盒。如果程序開始時這個文件存在,則加載原來的狀態並繼續。每次運行時都會在保存狀態之前和之后生成一些數,以展示恢復狀態會使生成器再次生成同樣的值。
第一次:
第二次:
1.4 隨機整數
random()將生成浮點數。可以把結果轉換為整數,不過直接使用randint()生成整數會更方便。
import random print('[1, 100]:', end=' ') for i in range(3): print(random.randint(1, 100), end=' ') print('\n[-5, 5]:', end=' ') for i in range(3): print(random.randint(-5, 5), end=' ') print()
randint()的參數是值的閉區間的兩端。這些數可以是正數或負數,不過第一個值要小於第二個值。
randrange()是從區間選擇值的一種更一般的形式。
import random for i in range(3): print(random.randrange(0, 101, 5), end=' ') print()
除了開始值(start)和結束值(stop),randrange()還支持一個步長(step)參數,所以它完全等價於從range(start,stop,step)選擇一個隨機值。不過randrange更高效,因為它並沒有真正構造區間。
1.5 選擇隨機元素
隨機數生成器有一種常見用法,即從一個枚舉值序列中選擇元素,即使這些值並不是數字。random包括一個choice()函數,可以從一個序列中隨機選擇。下面這個例子模擬硬幣10000此來統計多少次面朝上,多少次面朝下。
import random import itertools outcomes = { 'heads': 0, 'tails': 0, } sides = list(outcomes.keys()) for i in range(10000): outcomes[random.choice(sides)] += 1 print('Heads:', outcomes['heads']) print('Tails:', outcomes['tails'])
由於只允許兩個結果,所以不必使用數字然后再進行轉換,這里對choice()使用了單詞“heads”(表示面朝上)和“tails”(表示面朝下)。結果以表格形式存儲在一個字典中,使用結果名作為鍵。
1.6 排列
要模擬一個撲克牌游戲,需要把一副牌混起來,然后向玩家發牌,同一張牌不能多次使用。使用choice()可能導致同一張牌被發出兩次,所以,可以用shuffle()來洗牌,然后在發各張牌時刪除所發的牌。
import random import itertools FACE_CARDS = ('J', 'Q', 'K', 'A') SUITS = ('H', 'D', 'C', 'S') def new_deck(): return [ # Always use 2 places for the value, so the strings # are a consistent width. '{:>2}{}'.format(*c) for c in itertools.product( itertools.chain(range(2, 11), FACE_CARDS), SUITS, ) ] def show_deck(deck): p_deck = deck[:] while p_deck: row = p_deck[:13] p_deck = p_deck[13:] for j in row: print(j, end=' ') print() # Make a new deck, with the cards in order deck = new_deck() print('Initial deck:') show_deck(deck) # Shuffle the deck to randomize the order random.shuffle(deck) print('\nShuffled deck:') show_deck(deck) # Deal 4 hands of 5 cards each hands = [[], [], [], []] for i in range(5): for h in hands: h.append(deck.pop()) # Show the hands print('\nHands:') for n, h in enumerate(hands): print('{}:'.format(n + 1), end=' ') for c in h: print(c, end=' ') print() # Show the remaining deck print('\nRemaining deck:') show_deck(deck)
這些撲克牌被表示為字符串,包括面值和一個表示花色的字母。要創建發出的“一手牌”,可以一次向4個列表分別增加一張牌,然后從這副牌中將發出的牌刪除,使這些牌不會再次發出。
1.7 采樣
很多模擬需要從大量輸入值中得到隨機樣本。sample()函數可以生成無重復值的樣本,並且不會修改輸入序列。下面的例子會打印系統字典中單詞的一個隨機樣本。
words.txt
pear
apricot
grape
pineapple
apple
peach
banana
plum
watermelon
lemon
orange
mango
strawberry
Demo.py
import random with open('words.txt', 'rt') as f: words = f.readlines() words = [w.rstrip() for w in words] for w in random.sample(words, 5): print(w)
第一次:
第二次:
1.8 多個並發生成器
除了模塊級函數,random還包括一個Random類以管理多個隨機數生成器的內部狀態。之前介紹的所有函數都可以作為Random實例的方法得到,並且每個實例都可以被單獨初始化和使用,而不會干擾其他實例返回的值。
import random import time print('Default initializiation:\n') r1 = random.Random() r2 = random.Random() for i in range(3): print('{:04.3f} {:04.3f}'.format(r1.random(), r2.random())) print('\nSame seed:\n') seed = time.time() r1 = random.Random(seed) r2 = random.Random(seed) for i in range(3): print('{:04.3f} {:04.3f}'.format(r1.random(), r2.random()))
如果系統上設置了很好的原生隨機值種子,那么實例會有獨特的初始狀態。不過,如果沒有一個好的平台隨機值生成器,那么不同實例往往會以當前時間作為種子,並因此生成相同的值。
1.9 SystemRandom
有些操作系統提供了一個隨機數生成器,可以訪問更多能引入生成器的信息源。random通SystemRandom類提供了這個特性,該類與Random的API相同,不過使用os.urandom()生成值,該值會構成所有其他算法的基礎。
import random import time print('Default initializiation:\n') r1 = random.SystemRandom() r2 = random.SystemRandom() for i in range(3): print('{:04.3f} {:04.3f}'.format(r1.random(), r2.random())) print('\nSame seed:\n') seed = time.time() r1 = random.SystemRandom(seed) r2 = random.SystemRandom(seed) for i in range(3): print('{:04.3f} {:04.3f}'.format(r1.random(), r2.random()))
SystemRandom產生的序列是不可再生的,因為其隨機性來自系統,而不是來自軟件狀態(實際上,seed()和setstate()根本不起作用)。