Molti programmi hanno bisogno di valori casuali (all'apparenza) per simulare eventi del mondo reale.
Ecco alcuni esempi comuni e familiari:
('Heads', 'Tails').Generare valori veramente casuali con un computer è una sfida tecnica sorprendentemente difficile, perciò potresti vedere questi risultati chiamati «pseudocasuali».
In pratica, una libreria ben progettata come il modulo random della libreria standard di Python è veloce, flessibile e offre risultati più che sufficienti per la maggior parte delle applicazioni di modellazione, simulazione e giochi.
Il resto di questa pagina elencherà alcune delle funzioni più comuni di random.
Ti invitiamo a esplorare la documentazione completa di random: le opzioni sono molte di più di quelle che trattiamo qui.
Il modulo random NON dovrebbe essere usato per applicazioni di sicurezza o crittografiche.
Al suo posto, Python fornisce il modulo secrets.
È ottimizzato appositamente per la sicurezza crittografica.
Alcuni dei problemi precedenti e delle ragioni per cui è stato creato il modulo secrets si trovano nella PEP 506.
Prima di poter usare gli strumenti del modulo random, devi importarlo:
>>> import random
# Choose random integer from a range
>>> random.randrange(1000)
360
>>> random.randrange(-1, 500)
228
>>> random.randrange(-10, 11, 2)
-8
# Choose random integer between two values (inclusive)
>>> random.randint(5, 25)
22
Per evitare di digitare il nome del modulo, puoi importare funzioni specifiche per nome:
>>> from random import choice, choices
# Using choice() to pick Heads or Tails 10 times
>>> tosses = []
... for side in range(10):
... tosses.append(choice(['H', 'T']))
>>> print(tosses)
['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H']
# Using choices() to pick Heads or Tails 8 times
>>> picks = []
... picks.extend(choices(['H', 'T'], k=8))
... print(picks)
['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T']
La funzione randrange() ha tre forme, per selezionare un valore casuale da range(start, stop, step):
randrange(stop) restituisce un numero intero n tale che 0 <= n < stop
randrange(start, stop) restituisce un numero intero n tale che start <= n < stop
randrange(start, stop, step) restituisce un numero intero n tale che start <= n < stop e n appartiene alla sequenza start, start + step, start + 2*step...
Nel caso comune in cui step == 1, la funzione randint(a, b) può essere più comoda e leggibile.
I possibili risultati di randint() includono il limite superiore, quindi randint(a, b) equivale a usare randrange(a, b+1):
>>> import random
# Select one number at random from the range 0, 499
>>> random.randrange(500)
219
# Select 10 numbers at random between 0 and 9 two steps apart.
>>> numbers = []
... for integer in range(10):
... numbers.append(random.randrange(0, 10, 2))
>>> print(numbers)
[2, 8, 4, 0, 4, 2, 6, 6, 8, 8]
# roll a die
>>> random.randint(1, 6)
4
Le funzioni di questa sezione presuppongono che tu parta da una sequenza o da un altro contenitore.
Di solito sarà una list o, con alcune limitazioni, una tuple o un set (una tuple è immutabile e un set non è ordinato).
choice() e choices()
La funzione choice() restituisce un elemento scelto a caso da una sequenza data.
Nel caso più semplice, può essere il lancio di una moneta:
# This will pick one of the two values in the list at random 5 separate times
>>> [random.choice(['H', 'T']) for _ in range(5)]
['T', 'H', 'H', 'T', 'H']
Potremmo ottenere essenzialmente la stessa cosa con la funzione choices(), passando un argomento con parola chiave per la lunghezza dell'array:
>>> random.choices(['H', 'T'], k=5)
['T', 'H', 'T', 'H', 'H']
Negli esempi precedenti abbiamo dato per scontata una moneta equilibrata, con la stessa probabilità di testa o croce, ma è anche possibile specificare dei pesi. Per esempio, se un sacchetto contiene 10 palline rosse e 15 palline verdi e vogliamo estrarne una a caso:
>>> random.choices(['red', 'green'], [10, 15])
['red']
sample()L'esempio con choices() qui sopra presuppone quello che gli statistici chiamano «campionamento con reinserimento».
Ogni estrazione o scelta non ha alcun effetto sulla probabilità delle scelte future, e la distribuzione delle scelte possibili resta la stessa da un'estrazione all'altra.
Nell'esempio con le palline rosse e verdi: dopo ogni scelta, rimettiamo la pallina nel sacchetto e scuotiamo bene prima dell'estrazione successiva. Questo è in contrasto con una situazione in cui estraiamo una pallina rossa e quella resta fuori. Non rimettere la pallina significa che nel sacchetto ci sono ora meno palline rosse, e la scelta successiva ha meno probabilità di essere rossa.
Per simulare questo «campionamento senza reinserimento», il modulo random fornisce la funzione sample().
La sintassi di sample() è simile a quella di choices(), tranne per il fatto che aggiunge un parametro con parola chiave counts:
>>> random.sample(['red', 'green'], counts=[10, 15], k=10)
['green', 'green', 'green', 'green', 'green', 'red', 'red', 'red', 'red', 'green']
I campioni vengono restituiti nell'ordine in cui sono stati scelti.
shuffle()Sia choices() che sample() restituiscono nuovi array quando k > 1.
Al contrario, shuffle() randomizza l'ordine di un array sul posto, e l'ordine originale va perso:
>>> my_list = [1, 2, 3, 4, 5]
>>> random.shuffle(my_list)
>>> my_list
[4, 1, 5, 2, 3]
Finora ci siamo concentrati su casi in cui tutti gli esiti sono ugualmente probabili.
Per esempio, random.randrange(100) ha la stessa probabilità di restituire qualsiasi numero intero da 0 a 99.
Molte situazioni del mondo reale sono molto meno semplici di così.
Di conseguenza, gli statistici hanno creato un'ampia varietà di distributions per descrivere matematicamente i risultati del «mondo reale».
Per i numeri interi, randrange() e randint() si usano quando tutte le probabilità sono uguali.
Questa è chiamata distribuzione uniform.
Esistono equivalenti in virgola mobile per randrange() e randint().
random() restituisce un valore float x tale che 0.0 <= x < 1.0.
uniform(a, b) restituisce x tale che a <= x <= b.
>>> [round(random.random(), 3) for _ in range(5)]
[0.876, 0.084, 0.483, 0.22, 0.863]
>>> [round(random.uniform(2, 5), 3) for _ in range(5)]
[2.798, 2.539, 3.779, 3.363, 4.33]
Chiamata anche distribuzione «normale» o curva «a campana», è un modo molto comune per descrivere l'imprecisione nei valori misurati.
Per esempio, supponi che la fabbrica in cui lavori abbia appena comprato 10.000 bulloni che dovrebbero essere identici.
Vuoi configurare il robot della fabbrica per gestirli, quindi pesi un campione di 100 bulloni e scopri che hanno un peso medio (o mean) di 4,731 g.
È estremamente improbabile che questo significhi che pesino tutti esattamente 4,731 g.
Forse scopri che i valori vanno da 4,627 a 4,794 g, ma si concentrano intorno a 4,731 g.
Questa è la Gaussian distribution, per la quale le probabilità hanno un picco in corrispondenza della media e diminuiscono simmetricamente su entrambi i lati (da cui «a campana»).
Per simulare questo in un software, abbiamo bisogno di un modo per specificare l'ampiezza della curva (di solito, i bulloni costosi si concentreranno più strettamente intorno alla media di quelli economici!).
Per convenzione, questo si fa con la standard deviation: valori piccoli per una curva stretta e appuntita, valori grandi per una curva larga e piatta.
I matematici adorano le lettere greche, quindi usiamo μ ('mu') per rappresentare la media e σ ('sigma') per rappresentare la deviazione standard.
Quindi, se leggi che «il 95% dei valori è entro 2σ da μ» o che «il bosone di Higgs è stato rilevato con una confidenza di 5 sigma», questi commenti riguardano la deviazione standard.
>>> mu = 4.731
>>> sigma = 0.316
>>> [round(random.gauss(mu, sigma), 3) for _ in range(5)]
[4.72, 4.957, 4.64, 4.556, 4.968]