본문 바로가기

프로그래밍/Python

[파이썬 / 기본] Random 라이브러리

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 라이브러리는 간단하면서도 강력함

핵심은:

  1. 의사난수의 특성을 이해하고 적절히 사용하기
  2. 보안이 중요한 경우 secrets 사용
  3. 재현성이 필요하면 seed() 활용
  4. 각 함수의 특징을 파악해 상황에 맞게 선택

더 궁금한 점이 있다면 공식 문서를 참고

728x90
반응형