Study/Python

[Python] 표준 라이브러리

the.Dev.Cat 2026. 3. 7. 23:35

 

동빈나 채널의 파이썬 문법 부수기 유튜브 강의를 참고하여 정리한 내용이다.

 

파이썬에는 기본적으로 제공하는 표준 라이브러리가 많다.

itertools, collections, heapq, bisect, math 정도는 익숙해두면 구현 시간을 확실히 줄일 수 있다.

 


 

내장 함수

import 없이 바로 쓸 수 있는 함수들이다.

앞서 게시물에서 다뤘지만, 표준 라이브러리와 함께 자주 등장하는 것들을 정리한다.

nums = [3, 1, 4, 1, 5, 9, 2, 6]

print(sum(nums))    # 31
print(min(nums))    # 1
print(max(nums))    # 9

# sorted는 새 리스트를 반환한다
print(sorted(nums))                         # [1, 1, 2, 3, 4, 5, 6, 9]
print(sorted(nums, reverse=True))           # [9, 6, 5, 4, 3, 2, 1, 1]
print(sorted(nums, key=lambda x: -x))      # [9, 6, 5, 4, 3, 2, 1, 1]

# 여러 기준으로 정렬
pairs = [(1, 3), (2, 1), (1, 2), (2, 3)]
print(sorted(pairs, key=lambda p: (p[0], p[1])))  # [(1, 2), (1, 3), (2, 1), (2, 3)]

 

eval()은 문자열을 파이썬 표현식으로 평가해서 결과를 반환한다.

print(eval("1 + 2"))      # 3
print(eval("10 * 5 - 3")) # 47
print(eval("[1, 2, 3]"))  # [1, 2, 3]

 


 

itertools

순열, 조합 계산할 때 직접 구현하면 버그가 생기기 쉬운데, itertools에 다 있다.

from itertools import permutations, combinations, product, combinations_with_replacement

 

permutations - 순열

순서가 중요한 경우. nPr.

from itertools import permutations

data = ['A', 'B', 'C']
result = list(permutations(data, 2))
print(result)
# [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]

# 전체 순열 (3P3)
result = list(permutations(data))
print(len(result))  # 6

 

combinations - 조합

순서가 없는 경우. nCr.

from itertools import combinations

data = ['A', 'B', 'C', 'D']
result = list(combinations(data, 2))
print(result)
# [('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]

print(len(result))  # 6 (4C2)

 

product - 중복 순열

같은 원소를 여러 번 선택할 수 있는 순열.

from itertools import product

data = ['A', 'B']
result = list(product(data, repeat=2))
print(result)
# [('A', 'A'), ('A', 'B'), ('B', 'A'), ('B', 'B')]

# 두 리스트의 카르테시안 곱
result = list(product([1, 2], ['x', 'y']))
print(result)
# [(1, 'x'), (1, 'y'), (2, 'x'), (2, 'y')]

 

combinations_with_replacement - 중복 조합

순서 없이 중복 허용.

from itertools import combinations_with_replacement

data = ['A', 'B', 'C']
result = list(combinations_with_replacement(data, 2))
print(result)
# [('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')]
함수 설명 개수
permutations(iter, r) 순열 (순서 O, 중복 X) nPr
combinations(iter, r) 조합 (순서 X, 중복 X) nCr
product(iter, repeat=r) 중복 순열 (순서 O, 중복 O) n^r
combinations_with_replacement(iter, r) 중복 조합 (순서 X, 중복 O) nHr

 


 

collections

Counter - 등장 횟수 세기

리스트나 문자열에서 각 원소의 등장 횟수를 딕셔너리처럼 반환한다.

from collections import Counter

data = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
counter = Counter(data)
print(counter)          # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(counter['apple']) # 3
print(counter['grape']) # 0 (없으면 0 반환, KeyError 없음)

# 가장 많이 등장한 원소 n개
print(counter.most_common(2))  # [('apple', 3), ('banana', 2)]
print(counter.most_common(1))  # [('apple', 3)]

# 문자열도 가능
s = "abracadabra"
char_count = Counter(s)
print(char_count)  # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})

 

Counter끼리 더하거나 뺄 수 있다.

c1 = Counter({'a': 3, 'b': 2})
c2 = Counter({'a': 1, 'b': 4, 'c': 1})

print(c1 + c2)  # Counter({'b': 6, 'a': 4, 'c': 1})
print(c1 - c2)  # Counter({'a': 2})  # 음수는 제외

 

deque - 덱 (양방향 큐)

리스트의 append()pop(0)을 쓰면 앞쪽 삭제가 O(n)이다. deque는 양쪽 삽입/삭제가 모두 O(1)이다.

from collections import deque

dq = deque([1, 2, 3])

dq.append(4)       # 오른쪽 추가: [1, 2, 3, 4]
dq.appendleft(0)   # 왼쪽 추가:  [0, 1, 2, 3, 4]
dq.pop()           # 오른쪽 제거: [0, 1, 2, 3]
dq.popleft()       # 왼쪽 제거:  [1, 2, 3]

print(list(dq))    # [1, 2, 3]

 

BFS에서 큐로 쓰는 게 전형적인 패턴이다.

from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)
    result = []

    while queue:
        node = queue.popleft()
        result.append(node)

        for next_node in graph[node]:
            if next_node not in visited:
                visited.add(next_node)
                queue.append(next_node)

    return result

 

rotate(n)으로 원소를 n칸 회전시킬 수도 있다.

dq = deque([1, 2, 3, 4, 5])
dq.rotate(2)   # 오른쪽으로 2칸: [4, 5, 1, 2, 3]
dq.rotate(-1)  # 왼쪽으로 1칸:  [5, 1, 2, 3, 4]

 

defaultdict - 기본값 있는 딕셔너리

없는 키를 조회해도 KeyError가 발생하지 않고 기본값을 반환한다.

from collections import defaultdict

# int: 기본값 0
counter = defaultdict(int)
for char in "hello":
    counter[char] += 1
print(dict(counter))  # {'h': 1, 'e': 1, 'l': 2, 'o': 1}

# list: 기본값 []
graph = defaultdict(list)
edges = [(1, 2), (1, 3), (2, 4), (3, 4)]
for u, v in edges:
    graph[u].append(v)
    graph[v].append(u)
print(dict(graph))  # {1: [2, 3], 2: [1, 4], 3: [1, 4], 4: [2, 3]}

 

그래프를 인접 리스트로 구현할 때 defaultdict(list)가 편하다.

 


 

heapq - 힙

파이썬의 heapq는 최솟값이 항상 맨 앞에 오는 최소 힙이다.

우선순위 큐가 필요할 때 쓴다. 삽입과 삭제가 O(log n)이다.

import heapq

heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 4)
heapq.heappush(heap, 1)
heapq.heappush(heap, 5)

print(heap)             # [1, 1, 4, 3, 5] (내부 배열, 정렬된 것처럼 보이지 않음)
print(heapq.heappop(heap))  # 1 (최솟값)
print(heapq.heappop(heap))  # 1
print(heapq.heappop(heap))  # 3

 

리스트를 힙으로 변환하려면 heapify()를 쓴다. O(n)이다.

import heapq

nums = [5, 3, 8, 1, 2]
heapq.heapify(nums)
print(nums)  # [1, 2, 8, 3, 5]

print(heapq.heappop(nums))  # 1
print(heapq.heappop(nums))  # 2

 

최대 힙

파이썬은 최소 힙만 제공한다. 최대 힙이 필요하면 값에 -1을 곱해서 넣는다.

import heapq

nums = [3, 1, 4, 1, 5, 9]
max_heap = []

for n in nums:
    heapq.heappush(max_heap, -n)

# 최댓값부터 꺼내기
while max_heap:
    print(-heapq.heappop(max_heap), end=' ')
# 9 5 4 3 1 1

 

nsmallest, nlargest

힙을 직접 쓰지 않고 가장 작은/큰 n개를 뽑을 수 있다.

import heapq

nums = [3, 1, 4, 1, 5, 9, 2, 6]
print(heapq.nsmallest(3, nums))  # [1, 1, 2]
print(heapq.nlargest(3, nums))   # [9, 6, 5]

 


 

bisect - 이진 탐색

정렬된 리스트에서 이진 탐색으로 삽입 위치를 찾는다. 직접 구현하지 않아도 된다.

from bisect import bisect_left, bisect_right
from bisect import bisect_left, bisect_right

a = [1, 2, 4, 4, 5, 7]

# bisect_left: 값이 들어갈 가장 왼쪽 위치
print(bisect_left(a, 4))   # 2
print(bisect_left(a, 3))   # 2

# bisect_right: 값이 들어갈 가장 오른쪽 위치
print(bisect_right(a, 4))  # 4
print(bisect_right(a, 3))  # 2

 

값이 특정 범위에 속하는 원소 개수

from bisect import bisect_left, bisect_right

def count_range(a, left, right):
    return bisect_right(a, right) - bisect_left(a, left)

a = [1, 2, 3, 3, 3, 4, 5]
print(count_range(a, 3, 4))   # 4 (3이 3개, 4가 1개)
print(count_range(a, 2, 3))   # 4 (2가 1개, 3이 3개)
print(count_range(a, 1, 1))   # 1

 

이진 탐색으로 값 존재 여부 확인

from bisect import bisect_left

def binary_search(a, target):
    idx = bisect_left(a, target)
    if idx < len(a) and a[idx] == target:
        return True
    return False

a = [1, 3, 5, 7, 9]
print(binary_search(a, 5))   # True
print(binary_search(a, 4))   # False

 


 

math - 수학 함수

import math
import math

# 팩토리얼
print(math.factorial(5))  # 120
print(math.factorial(0))  # 1

# 제곱근
print(math.sqrt(16))   # 4.0
print(math.sqrt(2))    # 1.4142135623730951

# 올림, 내림
print(math.ceil(3.2))   # 4
print(math.floor(3.9))  # 3

# 상수
print(math.pi)   # 3.141592653589793
print(math.inf)  # inf (무한대)
print(-math.inf) # -inf

# 무한대 활용: 초기 최솟값
min_val = math.inf
for n in [3, 1, 4, 1, 5]:
    min_val = min(min_val, n)
print(min_val)  # 1

 

gcd, lcm

최대공약수(GCD)와 최소공배수(LCM)이다.

import math

# GCD (파이썬 3.5+)
print(math.gcd(12, 8))   # 4
print(math.gcd(100, 75)) # 25

# LCM (파이썬 3.9+)
print(math.lcm(4, 6))    # 12
print(math.lcm(12, 8))   # 24

 

Python 3.9 미만이면 lcm이 없어서 직접 구현해야 한다.

import math

def lcm(a, b):
    return a * b // math.gcd(a, b)

print(lcm(4, 6))   # 12
print(lcm(12, 8))  # 24
# 여러 수의 GCD와 LCM (파이썬 3.9+)
nums = [12, 8, 4]
print(math.gcd(*nums))  # 4
print(math.lcm(*nums))  # 24

 


 

functools - 함수형 유틸리티

reduce

이터러블의 원소를 왼쪽부터 누적해서 하나의 값으로 만든다.

from functools import reduce

nums = [1, 2, 3, 4, 5]

# 누적 곱
result = reduce(lambda acc, x: acc * x, nums)
print(result)  # 120 (1*2*3*4*5)

# 초기값 지정
result = reduce(lambda acc, x: acc + x, nums, 10)
print(result)  # 25 (10+1+2+3+4+5)

 

lru_cache - 메모이제이션

재귀 함수에서 같은 인수로 반복 호출되는 경우 결과를 캐싱한다.

from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(50))   # 12586269025 (캐싱 없이는 매우 느림)
print(fib(100))  # 354224848179261915075

 

maxsize=None이면 캐시 크기 제한이 없다. @cache (Python 3.9+)가 같은 역할이다.

from functools import cache

@cache
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

 


 

sys

setrecursionlimit - 재귀 깊이 제한

파이썬 기본 재귀 깊이는 1000이다. DFS나 깊은 재귀가 필요한 문제에서 먼저 설정한다.

import sys
sys.setrecursionlimit(100000)

 

maxsize - 최대 정수값

무한대 대신 쓸 수 있는 큰 정수가 필요할 때.

import sys
print(sys.maxsize)  # 9223372036854775807 (2^63 - 1)

INF = sys.maxsize

 

float('inf') 또는 math.inf를 쓰는 경우가 더 많긴 하다. 정수 비교에서는 sys.maxsize가 편할 때가 있다.

 

stdin - 빠른 입력

대량의 입력을 받을 때 input() 대신 쓴다. 09-function.md의 입출력 최적화와 이어지는 내용이다.

import sys
input = sys.stdin.readline

n = int(input())
data = list(map(int, input().split()))

 


 

에러 상황

RecursionError - lru_cache와 재귀 깊이

lru_cache를 써도 재귀 깊이 제한은 그대로다. 스택 초과가 나면 setrecursionlimit도 함께 설정해야 한다.

from functools import lru_cache
import sys
sys.setrecursionlimit(100000)

@lru_cache(maxsize=None)
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

 

TypeError - bisect는 정렬된 리스트에서만

from bisect import bisect_left

a = [3, 1, 4, 1, 5]  # 정렬 안 됨
bisect_left(a, 3)     # 결과가 틀림 (에러는 안 남)

a.sort()
bisect_left(a, 3)     # 정상 동작

 

bisect는 리스트가 정렬되어 있다고 가정한다. 정렬 안 된 리스트에 쓰면 에러는 안 나지만 결과가 틀린다.

 

Counter.most_common 인수 생략

from collections import Counter

c = Counter("abracadabra")
print(c.most_common())   # 전체를 빈도순으로
print(c.most_common(2))  # 상위 2개

 

인수를 생략하면 전체를 빈도순으로 반환한다. None이 아니라 그냥 생략 할 수 있다.