728x90
반응형
파이썬의 Random 이란
- Python에서 랜덤 기능을 구현해야 할 때마다 random 라이브러리를 사용
- 주사위 게임, 데이터 셔플, 시뮬레이션 등.
import random # 이것만 있으면 시작 가능
랜덤의 진실: Pseudorandom이란?
- 컴퓨터가 생성하는 랜덤은 사실 "진짜" 랜덤이 아님
- 이를 pseudorandom(의사 난수)이라고 부르는데
- 컴퓨터는 결정론적(deterministic) 기계이기 때문에 완전한 무작위를 만들어낼 수 없음.
Khan Academy 영상에서 배우는 랜덤의 역사
이 영상에서는 1946년 John von Neumann이 군사 계산을 위해 빠른 랜덤 숫자가 필요했지만 당시 컴퓨터의 메모리 한계로 긴 랜덤 시퀀스를 저장할 수 없어 middle-squares 방법이라는 최초의 의사난수 생성 알고리즘을 개발한 과정을 다룸
핵심 개념:
- Seed(시드): 랜덤 생성의 시작점이 되는 값. 일반적으로 현재 시간(Unix time)을 사용
- Period(주기): 의사난수 시퀀스가 반복되기 전까지의 길이
- Deterministic vs Non-deterministic: 기계는 예측 가능하지만, 진짜 랜덤은 예측 불가능
random.seed(42) # 같은 시드를 사용하면
print(random.random()) # 항상 같은 결과 (재현성)
보안과 랜덤
- 일반적인 용도에는 random 모듈이 충분하지만, 암호화나 보안이 중요한 경우에는 절대 사용해선 안됨.
- 의사난수는 예측 가능
import secrets # 보안용 랜덤은 이걸 쓰세요
token = secrets.token_hex(16)
주요 함수 비교표
- 실무에서 자주 쓰는 함수들을 빠르게 비교
| 함수 | 용도 | 입력 | 출력 예시 | 특징 |
| random() | 0~1 사이 소수 | - | 0.37444... | 모든 랜덤의 기본 |
| randint(a, b) | a~b 정수 (양 끝 포함) | 1, 10 | 7 | 주사위 굴리기 |
| randrange(start, stop, step) | range처럼 뽑기 | 0, 10, 2 | 4 | 짝수만 뽑기 |
| choice(seq) | 시퀀스에서 하나 | ['a', 'b'] | 'b' | 리스트 랜덤 선택 |
| choices(pop, k) | 여러 개 뽑기(중복 O) | ['red'], k=3 | ['red', 'red', 'red'] | 가중치 적용 가능 |
| shuffle(x) | 리스트 섞기(in-place) | [1,2,3] | [3,1,2] | 원본 변경 주의 |
| sample(pop, k) | 여러 개 뽑기(중복 X) | [1,2,3], k=2 | [3,1] | 샘플링 |
함수 상세 분석
1. random.random() - 기본 중의 기본
- 0.0 이상 1.0 미만의 float를 반환
- 다른 모든 랜덤 함수의 기반이 되는 함수
import random
# 확률 구현
if random.random() < 0.3: # 30% 확률
print("크리티컬 히트!")
# 0~100 사이 소수
score = random.random() * 100
print(f"점수: {score:.2f}")
2. random.randint(a, b) - 정수 뽑기
- a부터 b까지의 정수 중 하나를 반환
- 양 끝이 모두 포함되는 점이 중요
# 주사위 게임
dice = random.randint(1, 6)
print(f"주사위: {dice}")
# OTP 생성 (실제로는 secrets 써야 함)
otp = random.randint(100000, 999999)
print(f"인증번호: {otp}")
3. random.randrange(start, stop, step) - 범위 지정
- range() 함수처럼 동작하며, step을 지정할 수 있어 유연
# 0~9 중 하나
num = random.randrange(10)
# 0~100 사이 짝수
even = random.randrange(0, 101, 2)
# 10~100 사이 10의 배수
multiple_of_10 = random.randrange(10, 101, 10)
print(f"결과: {multiple_of_10}") # 10, 20, 30, ..., 100
randint vs randrange: randint(1, 10)은 randrange(1, 11)과 동일합니다.
4. random.choice(seq) - 시퀀스에서 선택
- 리스트, 튜플, 문자열 등에서 무작위로 하나를 선택
fruits = ['apple', 'banana', 'cherry', 'durian']
selected = random.choice(fruits)
print(f"오늘의 과일: {selected}")
# 문자열에서도 가능
char = random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
print(f"랜덤 알파벳: {char}")
주의: 빈 시퀀스를 넣으면 IndexError가 발생합니다.
5. random.choices(population, weights, k) - 가중치 있는 선택
- 중복을 허용하면서 여러 개를 뽑고, 가중치를 줄 수 있음
# 룰렛 시뮬레이션 (빨강:18, 검정:18, 초록:2)
colors = ['red', 'black', 'green']
weights = [18, 18, 2]
results = random.choices(colors, weights=weights, k=100)
print(f"빨강: {results.count('red')}")
print(f"검정: {results.count('black')}")
print(f"초록: {results.count('green')}")
실무 활용: A/B 테스트에서 트래픽 분배, 확률 기반 아이템 드롭 등
6. random.shuffle(x) - 리스트 섞기
- 리스트를 제자리에서(in-place) shuffle
- 원본이 변경
deck = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
random.shuffle(deck)
print(deck) # 섞인 덱
# 원본 보존하려면
original = [1, 2, 3, 4, 5]
shuffled = original.copy()
random.shuffle(shuffled)
활용 사례: 카드 게임, 데이터 셔플링, 배치 순서 무작위화
7. random.sample(population, k) - 중복 없이 뽑기
- 중복 없이 k개를 샘플링
- k가 population 크기보다 크면 에러가 발생
# 로또 번호 생성 (1~45에서 6개)
lotto = random.sample(range(1, 46), 6)
print(f"로또 번호: {sorted(lotto)}")
# 추첨 대상자 선정
participants = ['Alice', 'Bob', 'Charlie', 'David', 'Eve']
winners = random.sample(participants, k=2)
print(f"당첨자: {winners}")
Python의 랜덤 알고리즘: Mersenne Twister
- Python의 random 모듈은 Mersenne Twister 알고리즘을 사용
- 이는 주기가 매우 길고(2^19937 - 1) 통계적으로 우수한 의사난수 생성기
import random
# 시드 설정으로 재현 가능한 랜덤
random.seed(12345)
print([random.randint(1, 100) for _ in range(5)])
# 항상 같은 결과: [41, 13, 79, 82, 49]
실무 팁과 주의사항
1. 재현성(Reproducibility)
- 데이터 과학이나 머신러닝에서는 실험 재현이 중요
import random
import numpy as np
def set_seed(seed=42):
random.seed(seed)
np.random.seed(seed) # NumPy도 시드 설정
set_seed(42) # 모든 랜덤 동작이 재현 가능해짐
2. 큰 리스트 섞기의 한계
- shuffle()은 Fisher-Yates 알고리즘을 사용하지만, 매우 큰 리스트에서는 모든 순열을 생성 X.
- 의사난수 생성기의 주기 한계
3. 스레드 안전성
- random 모듈은 스레드 안전 X
- 멀티스레딩 환경에서는 각 스레드마다 별도의 Random 인스턴스를 사용
import random
import threading
def worker():
local_random = random.Random() # 각 스레드마다 별도 인스턴스
print(local_random.randint(1, 100))
threads = [threading.Thread(target=worker) for _ in range(5)]
4. 성능 최적화
- 대량의 랜덤 숫자가 필요하다면 NumPy를 고려
import numpy as np
# 백만 개의 랜덤 숫자 생성
numbers = np.random.randint(1, 100, size=1000000) # 훨씬 빠름
실전 예제: 간단한 가챠 시스템
import random
class GachaSystem:
def __init__(self):
self.items = {
'Common': {'weight': 70, 'items': ['Iron Sword', 'Wooden Shield']},
'Rare': {'weight': 25, 'items': ['Steel Sword', 'Magic Ring']},
'Epic': {'weight': 4, 'items': ['Dragon Blade', 'Phoenix Armor']},
'Legendary': {'weight': 1, 'items': ['Excalibur', 'Infinity Gauntlet']}
}
def pull(self, count=1):
results = []
rarities = list(self.items.keys())
weights = [self.items[r]['weight'] for r in rarities]
for _ in range(count):
rarity = random.choices(rarities, weights=weights)[0]
item = random.choice(self.items[rarity]['items'])
results.append((rarity, item))
return results
# 10연차
gacha = GachaSystem()
pulls = gacha.pull(10)
for rarity, item in pulls:
print(f"[{rarity}] {item}")
random 라이브러리는 간단하면서도 강력함
핵심은:
- 의사난수의 특성을 이해하고 적절히 사용하기
- 보안이 중요한 경우 secrets 사용
- 재현성이 필요하면 seed() 활용
- 각 함수의 특징을 파악해 상황에 맞게 선택
더 궁금한 점이 있다면 공식 문서를 참고
728x90
반응형
'프로그래밍 > Python' 카테고리의 다른 글
| [파이썬 / 기본] Logging (0) | 2025.10.24 |
|---|---|
| [파이썬 / 기본] Errors and Exceptions (오류와 예외처리) (0) | 2025.10.23 |
| [파이썬 / 기본] Lambda Function (0) | 2025.10.22 |
| [파이썬 / 기본] itertools (0) | 2025.10.21 |
| [파이썬 / 기본] collections (0) | 2025.10.20 |