파이썬은 컴파일 언어에 비해 속도가 느리다는 인식이 널리 퍼져 있지만, 수많은 대형 기업들이 파이썬으로 방대한 워크로드를 안정적으로 처리하고 있습니다. 이는 파이썬 코드 역시 결코 느리지 않으며, 충분히 빠르고 효율적으로 작동할 수 있음을 보여주는 사례입니다. 이 글에서는 올바르게 작성된 파이썬 프로그램을 한층 더 빠르고 효율적으로 만들기 위해 반드시 알아두어야 할 최적화 팁들을 소개합니다.
팁 1: 내장 함수(Built-in Functions) 활용하기
파이썬으로 직접 효율적인 코드를 작성할 수 있지만, 내장 함수(내부적으로 C로 구현됨)보다 빠른 성능을 내기는 매우 어렵습니다. 복잡한 로직을 직접 구현하기보다 파이썬이 제공하는 내장 함수를 우선적으로 활용하는 것이 좋습니다.

팁 2: 다중 할당(Multiple Assignment)으로 변수 교환하기
두 변수의 값을 서로 바꿀 때 임시 변수를 사용하는 대신, 파이썬의 다중 할당 문법을 활용하면 더 간결하고 빠릅니다.
>>> # 비효율적인 방법 - 임시 변수 사용 >>> temp = x >>> x = y >>> y = temp >>> # 권장 방법 - 다중 할당 (훨씬 빠름) >>> x, y = y, x
팁 3: 전역 변수 대신 지역 변수 사용하기
파이썬은 지역 변수를 조회하는 속도가 전역 변수를 조회하는 속도보다 빠릅니다. 따라서 특별한 이유가 없다면 전역 변수 사용을 피하고 지역 변수를 활용하는 것이 성능에 유리합니다.
팁 4: 멤버십 검사에는 'in' 키워드 활용하기
요소의 포함 여부를 확인할 때는 'in' 키워드를 사용하는 것이 좋습니다. 코드가 깔끔해질 뿐만 아니라 실행 속도도 빠릅니다.
for key in sequence:
print("Hello ", key)팁 5: 무한 루프에는 'while 1' 사용하기
소켓 리스닝처럼 프로그램에서 무한 루프가 필요한 경우가 있습니다. 'while True'와 동일하게 동작하지만, 'while 1'은 단일 점프 연산으로 처리되어 약간 더 빠릅니다. 참고로 파이썬 3에서는 두 표현의 성능 차이가 거의 없어졌습니다.
>>> while 1:
# do something, faster with while 1
>>> while True:
# do something, same operation but slower than the previous one팁 6: 리스트 컴프리헨션(List Comprehension) 활용하기
파이썬 2.0부터 도입된 리스트 컴프리헨션은 많은 'for' 및 'while' 블록을 대체할 수 있습니다. 인터프리터가 반복 중 예측 가능한 패턴을 감지하도록 최적화되어 있어 일반 반복문보다 훨씬 빠르며, 가독성이 뛰어나고 카운트용 변수 하나를 아낄 수 있다는 장점도 있습니다.
예를 들어 1부터 25 사이의 짝수를 한 줄로 찾으려면 다음과 같이 작성할 수 있습니다.
>>> # 리스트 컴프리헨션 사용 - 권장 방법
>>> print([i for i in range(25) if i%2 == 0])
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24]
# 일반 반복문 사용 - 비교적 비효율적인 방법
i = 0
evens = []
while i < 25:
if i%2 == 0:
evens.append(i)
i += 1
print(evens)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24]팁 7: 제너레이터(Generator)로 필요한 값을 즉시 생성하기
파이썬 제너레이터를 사용하면 메모리를 절약하고 성능을 개선할 수 있습니다. 예를 들어 영상을 스트리밍할 때 전체 스트림을 한 번에 보내는 것이 아니라 바이트 청크(chunk) 단위로 전송할 수 있습니다.
>>> chunkBytes = (1000 * i for i in range(1000)) >>> next(chunkBytes) 0 >>> next(chunkBytes) 1000 >>> next(chunkBytes) 2000 >>> next(chunkBytes) 3000 >>>
팁 8: itertools 모듈 활용하기
itertools 모듈은 순회(iteration)와 조합(combination) 작업에서 매우 유용하고 효율적입니다.
예를 들어 리스트 [1, 2, 3, 4]의 모든 순열(permutation)을 몇 줄의 파이썬 코드로 생성할 수 있습니다.
>>> import itertools >>> iter1 = itertools.permutations([1, 2, 3, 4]) >>> list(iter1) [(1, 2, 3, 4), (1, 2, 4, 3), (1, 3, 2, 4), (1, 3, 4, 2), (1, 4, 2, 3), (1, 4, 3, 2), (2, 1, 3, 4), (2, 1, 4, 3), (2, 3, 1, 4), (2, 3, 4, 1), (2, 4, 1, 3), (2, 4, 3, 1), (3, 1, 2, 4), (3, 1, 4, 2), (3, 2, 1, 4), (3, 2, 4, 1), (3, 4, 1, 2), (3, 4, 2, 1), (4, 1, 2, 3), (4, 1, 3, 2), (4, 2, 1, 3), (4, 2, 3, 1), (4, 3, 1, 2), (4, 3, 2, 1)]
팁 9: bisect 모듈로 정렬 상태 유지하기
bisect 모듈은 별도 설치 없이 사용할 수 있는 이진 탐색(binary search) 구현체이자, 정렬된 시퀀스에 빠르게 삽입할 수 있는 도구입니다.
>>> import bisect >>> bisect.insort(list, element)
이렇게 하면 요소가 정렬된 위치에 자동으로 삽입되므로, 다시 sort()를 호출해 정렬 상태를 유지할 필요가 없습니다. 긴 시퀀스에서 sort()를 반복 호출하는 것은 매우 큰 비용이 들 수 있으므로 이 방법이 특히 유용합니다.
팁 10: 딕셔너리와 셋(Set)으로 멤버십 검사하기
딕셔너리와 셋은 해시 테이블(hash table)로 구현되어 있기 때문에, 특정 요소의 존재 여부를 확인하는 작업이 파이썬에서 매우 빠릅니다. 경우에 따라 조회 속도가 O(1)에 달하기도 합니다.
>>> lst = ['a', 'ab', 'abc'] # 느림 - 리스트로 멤버십 검사 >>> 'abc' in lst True >>> mySet = set(['a', 'ab', 'abc']) # 빠름 - 셋 또는 딕셔너리로 멤버십 검사 >>> 'abc' in mySet True
팁 11: 파이썬 데코레이터로 결과 캐싱하기
파이썬 데코레이터 기호는 '@'입니다. 데코레이터는 추적(tracing), 잠금(locking), 로깅(logging)뿐만 아니라, 함수가 나중에 필요로 하는 결과를 기억하도록 만들어주는 용도(메모이제이션, memoization)로도 활용할 수 있습니다.
>>> from functools import wraps
>>> def memo(f):
cache = {}
@wraps(f)
def wrap(*arg):
if arg not in cache:
cache[arg] = f(*arg)
return cache[arg]
return wrap이 데코레이터를 피보나치(Fibonacci) 함수에 적용해 보겠습니다.
>>> @memo
def fib(i):
if i < 2:
return 1
return fib(i-1) + fib(i-2)핵심 아이디어는 함수를 향상(데코레이트)시켜 이미 계산한 피보나치 항을 기억하도록 하는 것입니다. 계산 결과가 캐시에 이미 저장되어 있다면 다시 계산할 필요가 없으므로, 재귀 호출이 많은 연산에서 실행 속도가 크게 향상됩니다.