`random` — 유사 난수 생성
random — 유사 난수 생성
random 모듈은 다양한 분포를 위한 유사 난수 생성기를 구현해요.
정수에 대해서는 범위에서 균일한 선택이 있어요. 시퀀스에 대해서는 무작위 요소의 균일 선택, 목록을 제자리에서 무작위로 섞는 함수, 그리고 비복원 무작위 표본을 만드는 함수가 있어요. 실수 직선에서는 균일, 정규(Gaussian), 로그정규, 음지수, 감마, 베타 분포를 계산하는 함수가 있어요. 각도 분포를 만들려면 폰 미제스(von Mises) 분포를 쓸 수 있어요.
거의 모든 모듈 함수는 기본 함수 random()에 의존해요. 이 함수는 반개방 구간 0.0 <= X < 1.0에서 균일하게 무작위 float를 생성해요. Python은 핵심 생성기로 메르센 트위스터(Mersenne Twister)를 써요. 이는 53비트 정밀도 float를 만들고 주기가 2**19937-1이에요. C로 된 기본 구현은 빠르고 스레드에 안전해요. 메르센 트위스터는 현존하는 가장 광범위하게 테스트된 난수 생성기 중 하나예요. 하지만 완전히 결정적이어서 모든 목적에 적합하지 않고, 암호화 목적에는 전혀 부적합해요.
이 모듈이 제공하는 함수는 실제로는 random.Random 클래스의 숨겨진 인스턴스의 바인딩된 메서드예요. 상태를 공유하지 않는 생성기가 필요하면 Random의 자체 인스턴스를 만들 수 있어요. 다른 기본 생성기를 쓰고 싶다면 Random 클래스를 서브클래싱할 수도 있어요. random 모듈은 또한 운영체제가 제공하는 소스에서 os.urandom() 시스템 함수로 난수를 생성하는 SystemRandom 클래스도 제공해요.
경고
이 모듈의 유사 난수 생성기는 보안 목적으로 사용하면 안 돼요. 보안 또는 암호화 사용에는
secrets모듈을 보세요.
참고
전역 난수 생성기와
Random인스턴스는 스레드에 안전해요. 하지만 자유 스레딩 빌드에서는 전역 생성기나 같은Random인스턴스에 대한 동시 호출이 경쟁(contention)과 낮은 성능을 만날 수 있어요. 대신 스레드마다 별도의Random인스턴스를 쓰는 걸 고려하세요.
출처: Python 표준 라이브러리
본문
기록 유지 함수 (Bookkeeping functions)
random.seed(*a=None*, *version=2*)
난수 생성기를 초기화해요. a가 생략되거나 None이면 현재 시스템 시간을 사용해요. 운영체제가 난수 소스를 제공하면 시스템 시간 대신 그 소스를 사용해요(가용성은 os.urandom() 함수 참고). a가 int면 그 절댓값을 직접 사용해요. version 2(기본값)에서는 str, bytes, bytearray 객체를 int로 변환해 모든 비트를 사용해요. version 1(이전 Python 버전의 난수 시퀀스를 재현하기 위해 제공)에서는 str과 bytes의 알고리즘이 더 좁은 시드 범위를 생성해요. 버전 3.2 변경: 문자열 시드의 모든 비트를 쓰는 version 2 방식으로 전환. 버전 3.11 변경: seed는 None, int, float, str, bytes, bytearray 중 하나여야 함.
random.getstate()
생성기의 현재 내부 상태를 포착한 객체를 반환해요. 이 객체를 setstate()에 넘겨 상태를 복원할 수 있어요.
random.setstate(*state*)
state는 이전 getstate() 호출에서 얻어야 해요. setstate()는 생성기의 내부 상태를 getstate()가 호출된 시점으로 복원해요.
바이트 함수 (Functions for bytes)
random.randbytes(*n*)
n개의 무작위 바이트를 생성해요. 이 메서드는 보안 토큰을 만드는 데 쓰면 안 돼요. 대신 secrets.token_bytes()를 쓰세요. 버전 3.9에서 추가.
정수 함수 (Functions for integers)
random.randrange(*stop*), random.randrange(*start*, *stop*[, *step*])
range(start, stop, step)에서 무작위로 선택된 요소를 반환해요. 이는 대략 choice(range(start, stop, step))와 동등하지만 임의로 큰 범위를 지원하고 흔한 경우에 최적화돼 있어요. 위치 인자 패턴은 range() 함수와 일치해요. 키워드 인자는 예기치 않게 해석될 수 있으니 쓰지 말아야 해요. 예를 들어 randrange(start=100)은 randrange(0, 100, 1)로 해석돼요. 버전 3.2 변경: 균등하게 분포된 값을 만드는 데 더 정교해짐. 버전 3.12 변경: 비정수 타입의 자동 변환을 더 이상 지원하지 않으며, randrange(10.0)과 randrange(Fraction(10, 1)) 같은 호출은 이제 TypeError를 발생시킴.
random.randint(*a*, *b*)
a <= N <= b인 무작위 정수 N을 반환해요. randrange(a, b+1)의 별칭이에요.
random.getrandbits(*k*)
k개의 무작위 비트를 가진 음이 아닌 Python 정수를 반환해요. 이 메서드는 메르센 트위스터 생성기와 함께 제공되며, 일부 다른 생성기도 API의 선택 부분으로 제공할 수 있어요. 사용 가능하면 getrandbits()는 randrange()가 임의로 큰 범위를 다루게 해요. 버전 3.9 변경: k에 0을 허용하게 됨.
시퀀스 함수 (Functions for sequences)
random.choice(*seq*)
비어 있지 않은 시퀀스 seq에서 무작위 요소를 반환해요. seq가 비어 있으면 IndexError를 발생시켜요.
random.choices(*population*, *weights=None*, *, *cum_weights=None*, *k=1*)
population에서 복원 추출로 선택된 요소의 k 크기 목록을 반환해요. population이 비어 있으면 IndexError를 발생시켜요. weights 시퀀스가 지정되면 상대 가중치에 따라 선택해요. 대안으로 cum_weights 시퀀스가 주어지면 누적 가중치(itertools.accumulate()로 계산할 수 있음)에 따라 선택해요. 예를 들어 상대 가중치 [10, 5, 30, 5]는 누적 가중치 [10, 15, 45, 50]과 동등해요. 내부적으로 상대 가중치는 선택 전에 누적 가중치로 변환되므로, 누적 가중치를 주면 작업을 절약해요. 둘 다 지정하지 않으면 동일 확률로 선택해요. 가중치 시퀀스는 population 시퀀스와 같은 길이여야 해요. weights와 cum_weights 둘 다 지정하는 것은 TypeError예요. 가중치는 random()이 반환하는 float 값과 상호 운용되는 어떤 숫자 타입도 쓸 수 있어요(정수, float, 분수를 포함하지만 decimal은 제외). 가중치는 음이 아니고 유한하다고 가정해요. 모든 가중치가 0이면 ValueError가 발생해요. 주어진 시드에 대해 같은 가중치의 choices() 함수는 보통 choice()를 반복 호출한 것과 다른 시퀀스를 만드는데, choices()는 내부 일관성과 속도를 위해 부동소수점 산술을 쓰고 choice()의 알고리즘은 반올림 오차의 작은 편향을 피하기 위해 정수 산술을 쓰기 때문이에요. 버전 3.6에서 추가, 버전 3.9 변경: 모든 가중치가 0이면 ValueError 발생.
random.shuffle(*x*)
시퀀스 x를 제자리에서 섞어요. 불변 시퀀스를 섞고 새로 섞인 목록을 반환하려면 대신 sample(x, k=len(x))를 쓰세요. 작은 len(x)에서도 x의 전체 순열 수는 대부분의 난수 생성기의 주기보다 빠르게 커질 수 있다는 점에 주의하세요. 즉 긴 시퀀스의 대부분 순열은 결코 생성될 수 없어요. 예를 들어 길이 2080의 시퀀스가 메르센 트위스터 난수 생성기의 주기에 들어갈 수 있는 가장 큰 것이에요. 버전 3.11 변경: 선택 인자 random 제거됨.
random.sample(*population*, *k*, *, *counts=None*)
population 시퀀스에서 선택된 고유 요소의 k 길이 목록을 반환해요. 비복원 무작위 표본 추출에 쓰여요. 원래 population을 바꾸지 않고 population의 요소를 담은 새 목록을 반환해요. 결과 목록은 선택 순서대로라 모든 하위 슬라이스도 유효한 무작위 표본이 돼요. 이는 추첨 당첨자(표본)를 대상/2등 당첨자(하위 슬라이스)로 나눌 수 있게 해줘요. population의 구성원은 해시 가능하거나 유일할 필요가 없어요. population이 반복을 포함하면 각 발생이 표본에서 선택 후보가 돼요. 반복 요소는 한 번에 하나씩 또는 선택적 키워드 전용 counts 매개변수로 지정할 수 있어요. 예를 들어 sample(['red', 'blue'], counts=[4, 2], k=5)는 sample(['red', 'red', 'red', 'red', 'blue', 'blue'], k=5)와 동등해요. 정수 범위에서 표본을 고르려면 range() 객체를 인자로 쓰세요. 큰 population에서 표본을 고를 때 특히 빠르고 공간 효율적이에요. sample(range(10000000), k=60)처럼요. 표본 크기가 population 크기보다 크면 ValueError가 발생해요. 버전 3.9 변경: counts 매개변수 추가. 버전 3.11 변경: population은 시퀀스여야 하며 집합의 목록 자동 변환은 더 이상 지원하지 않음.
이산 분포 (Discrete distributions)
random.binomialvariate(*n=1*, *p=0.5*)
이항 분포(Binomial distribution)예요. 각 시행의 성공 확률이 p일 때 n개의 독립 시행에서 성공 횟수를 반환해요. 수학적으로 다음과 동등해요:
sum(random() < p for i in range(n))
시행 수 n은 음이 아닌 정수여야 하고, 성공 확률 p는 0.0 <= p <= 1.0 사이여야 해요. 결과는 0 <= X <= n 범위의 정수예요. 버전 3.12에서 추가.
실수 값 분포 (Real-valued distributions)
random.random()
0.0 <= X < 1.0 범위의 다음 무작위 부동소수점 수를 반환해요.
random.uniform(*a*, *b*)
a <= b일 때 a <= N <= b, b < a일 때 b <= N <= a인 무작위 부동소수점 수 N을 반환해요. 끝점 값 b는 식 a + (b-a) * random()의 부동소수점 반올림에 따라 범위에 포함될 수도 있고 아닐 수도 있어요.
random.triangular(*low*, *high*, *mode*)
low <= N <= high이고 그 경계 사이에 지정된 mode를 가진 무작위 부동소수점 수 N을 반환해요. low와 high 경계는 기본값이 0과 1이에요. mode 인자는 기본값이 경계 사이의 중간점이라 대칭 분포를 줘요.
random.betavariate(*alpha*, *beta*)
베타 분포(Beta distribution)예요. 매개변수 조건은 alpha > 0이고 beta > 0이에요. 반환 값은 0과 1 사이예요.
random.expovariate(*lambd=1.0*)
지수 분포(Exponential distribution)예요. lambd는 원하는 평균값으로 1.0을 나눈 값이에요. 0이 아니어야 해요. 반환 값은 lambd가 양수면 0에서 양의 무한대, lambd가 음수면 음의 무한대에서 0까지예요. 버전 3.12 변경: lambd의 기본값 추가.
random.gammavariate(*alpha*, *beta*)
감마 분포(Gamma distribution)예요. (감마 함수가 아니에요!) 형태와 척도 매개변수 alpha, beta는 양수여야 해요. 확률 분포 함수는:
x ** (alpha - 1) * math.exp(-x / beta)
pdf(x) = --------------------------------------
math.gamma(alpha) * beta ** alpha
random.gauss(*mu=0.0*, *sigma=1.0*)
정규 분포(Normal distribution), 즉 가우스 분포예요. mu는 평균, sigma는 표준 편차예요. 아래 normalvariate() 함수보다 약간 빨라요. 멀티스레딩 참고: 두 스레드가 동시에 이 함수를 호출하면 같은 반환 값을 받을 수 있어요. 세 가지 방법으로 피할 수 있어요: 1) 각 스레드가 다른 난수 생성기 인스턴스를 쓰기, 2) 모든 호출에 락 걸기, 3) 더 느리지만 스레드에 안전한 normalvariate() 함수 쓰기. 버전 3.11 변경: mu와 sigma에 기본 인자가 생김.
random.lognormvariate(*mu*, *sigma*)
로그정규 분포(Log normal distribution)예요. 이 분포의 자연로그를 취하면 평균 mu, 표준 편차 sigma인 정규 분포를 얻어요. mu는 어떤 값이든 될 수 있고 sigma는 0보다 커야 해요.
random.normalvariate(*mu=0.0*, *sigma=1.0*)
정규 분포예요. mu는 평균, sigma는 표준 편차예요. 버전 3.11 변경: mu와 sigma에 기본 인자가 생김.
random.vonmisesvariate(*mu*, *kappa*)
mu는 0과 2pi 사이 라디안으로 표현된 평균 각도이고, kappa는 0 이상이어야 하는 집중 매개변수예요. kappa가 0이면 이 분포는 0에서 2pi 범위의 균일한 무작위 각도로 줄어들어요.
random.paretovariate(*alpha*)
파레토 분포(Pareto distribution)예요. alpha는 형태 매개변수예요.
random.weibullvariate(*alpha*, *beta*)
와이불 분포(Weibull distribution)예요. alpha는 척도 매개변수, beta는 형태 매개변수예요.
대안 생성기 (Alternative Generator)
class random.Random([*seed*])
random 모듈이 쓰는 기본 유사 난수 생성기를 구현하는 클래스예요. 버전 3.11 변경: 이전에는 seed가 어떤 해시 가능한 객체든 될 수 있었는데, 이제 None, int, float, str, bytes, bytearray로 제한됨.
다른 기본 생성기를 쓰려면 Random의 하위 클래스가 다음 메서드를 재정의해야 해요: seed(), getstate(), setstate(), random(). 선택적으로 사용자 지정 생성기 하위 클래스는 getrandbits()와 randbytes() 메서드도 제공할 수 있어요.
class random.SystemRandom([*seed*])
운영체제가 제공하는 소스에서 os.urandom() 함수로 난수를 생성하는 클래스예요. 모든 시스템에서 사용 가능하지 않아요. 소프트웨어 상태에 의존하지 않고 시퀀스를 재현할 수 없어요. 따라서 seed() 메서드는 효과가 없고 무시돼요. getstate()와 setstate() 메서드는 호출되면 NotImplementedError를 발생시켜요.
재현성에 관한 참고 (Notes on Reproducibility)
유사 난수 생성기가 주는 시퀀스를 재현할 수 있으면 유용할 때가 있어요. 시드 값을 재사용하면 여러 스레드가 실행되지 않는 한 실행마다 같은 시퀀스를 재현할 수 있어야 해요. random 모듈의 대부분 알고리즘과 시딩 함수는 Python 버전마다 바뀔 수 있지만, 두 가지 측면은 바뀌지 않는 것이 보장돼요: 새 시딩 메서드가 추가되면 하위 호환 가능한 시더(seeder)가 제공될 것, 그리고 호환 시더에 같은 시드가 주어지면 생성기의 random() 메서드가 같은 시퀀스를 계속 만든다는 것.
예시 (Examples)
기본 예시:
>>> random() # Random float: 0.0 <= x < 1.0
0.37444887175646646
>>> uniform(2.5, 10.0) # Random float: 2.5 <= x <= 10.0
3.1800146073117523
>>> expovariate(1 / 5) # Interval between arrivals averaging 5 seconds
5.148957571865031
>>> randrange(10) # Integer from 0 to 9 inclusive
7
>>> randrange(0, 101, 2) # Even integer from 0 to 100 inclusive
26
>>> choice(['win', 'lose', 'draw']) # Single random element from a sequence
'draw'
>>> deck = 'ace two three four'.split()
>>> shuffle(deck) # Shuffle a list
>>> deck
['four', 'two', 'ace', 'three']
>>> sample([10, 20, 30, 40, 50], k=4) # Four samples without replacement
[40, 10, 50, 30]
시뮬레이션:
>>> # Six roulette wheel spins (weighted sampling with replacement)
>>> choices(['red', 'black', 'green'], [18, 18, 2], k=6)
['red', 'green', 'black', 'black', 'red', 'black']
>>> # Deal 20 cards without replacement from a deck
>>> # of 52 playing cards, and determine the proportion of cards
>>> # with a ten-value: ten, jack, queen, or king.
>>> deal = sample(['tens', 'low cards'], counts=[16, 36], k=20)
>>> deal.count('tens') / 20
0.15
>>> # Estimate the probability of getting 5 or more heads from 7 spins
>>> # of a biased coin that settles on heads 60% of the time.
>>> sum(binomialvariate(n=7, p=0.6) >= 5 for i in range(10_000)) / 10_000
0.4169
>>> # Probability of the median of 5 samples being in middle two quartiles
>>> def trial():
... return 2_500 <= sorted(choices(range(10_000), k=5))[2] < 7_500
...
>>> sum(trial() for i in range(10_000)) / 10_000
0.7958
레시피 (Recipes)
이 레시피들은 itertools 모듈의 조합 반복기에서 효율적으로 무작위 선택을 하는 방법을 보여줘요.
import random
def random_product(*iterables, repeat=1):
"Random selection from itertools.product(*iterables, repeat=repeat)"
pools = tuple(map(tuple, iterables)) * repeat
return tuple(map(random.choice, pools))
def random_permutation(iterable, r=None):
"Random selection from itertools.permutations(iterable, r)"
pool = tuple(iterable)
r = len(pool) if r is None else r
return tuple(random.sample(pool, r))
def random_combination(iterable, r):
"Random selection from itertools.combinations(iterable, r)"
pool = tuple(iterable)
n = len(pool)
indices = sorted(random.sample(range(n), r))
return tuple(pool[i] for i in indices)
def random_combination_with_replacement(iterable, r):
"Choose r elements with replacement. Order the result to match the iterable."
# Result will be in set(itertools.combinations_with_replacement(iterable, r)).
pool = tuple(iterable)
n = len(pool)
indices = sorted(random.choices(range(n), k=r))
return tuple(pool[i] for i in indices)
def random_derangement(iterable):
"Choose a permutation where no element stays in its original position."
seq = tuple(iterable)
if len(seq) < 2:
if not seq:
return ()
raise IndexError('No derangments to choose from')
perm = list(range(len(seq)))
start = tuple(perm)
while True:
random.shuffle(perm)
if all(p != q for p, q in zip(start, perm)):
return tuple([seq[i] for i in perm])
기본 random()은 0.0 ≤ x < 1.0 범위에서 2⁻⁵³의 배수를 반환해요. 그러한 숫자는 모두 균등한 간격이고 Python float로 정확히 표현돼요. 하지만 그 구간의 다른 많은 표현 가능한 float는 선택 후보가 아니에요. 예를 들어 0.05954861408025609는 2⁻⁵³의 정수 배수가 아니에요.
random()보다 더 세밀한 float를 생성하는 방법에 대한 Allen B. Downey의 "Generating Pseudo-random Floating-Point Values" 논문도 참고할 만해요.
명령줄 사용 (Command-line usage)
버전 3.13에서 추가. random 모듈은 명령줄에서 실행할 수 있어요.
python -m random [-h] [-c CHOICE [CHOICE ...] | -i N | -f N] [input ...]
다음 옵션을 받아요:
-h,--help: 도움말 메시지를 보여주고 종료.-c CHOICE [CHOICE ...],--choice CHOICE [CHOICE ...]:choice()로 무작위 선택을 출력.-i <N>,--integer <N>:randint()로 1과 N 사이(포함)의 무작위 정수 출력.-f <N>,--float <N>:uniform()으로 0과 N 사이(포함)의 무작위 부동소수점 수 출력.
옵션이 없으면 입력에 따라 출력이 달라져요: 문자열 또는 여러 개면 --choice와 같고, 정수면 --integer와 같으며, float면 --float와 같아요.
더 알아보기
- M. Matsumoto and T. Nishimura, "Mersenne Twister: A 623-dimensionally equidistributed uniform pseudorandom number generator", ACM Transactions on Modeling and Computer Simulation Vol. 8, No. 1, January pp.3–30 1998.
- Peter Norvig의 Economics Simulation 및 A Concrete Introduction to Probability (using Python) 튜토리얼과 Jake Vanderplas의 Statistics for Hackers 영상 튜토리얼.
secrets모듈: 보안 목적의 난수 생성.