시작하며:
개발을 하다 보면 평소에 쓰던 기능만 계속 쓰게 되는 경향이 있습니다. 파이썬은 배우기 쉽고 활용도가 높은 언어인 만큼, 다양하고 편리한 기능이 기본적으로 제공되고 있습니다. 이 글에서는 평소에는 놓치고 있던 파이썬의 기능을 다시 한번 짚어보고, 실제 개발 과정이나 코딩테스트에서 유용하게 활용할 수 있는 팁을 소개하고자 합니다. "알아두면 은근히 쓸 곳이 많은" 파이썬 기능들을 함께 살펴보며, 파이썬 활용 능력을 한 단계 업그레이드해 보아요.
1. 반복 작업의 마법사, itertools
반복적인 작업을 효율적으로 처리하고 싶다면 itertools 모듈을 적극 활용해 보세요. 다양한 반복 패턴을 생성하는 함수들을 제공하여, 복잡한 반복 로직을 간결하고 직관적으로 만들 수 있습니다. 순열(permutations), 조합(combinations), 곱집합(product) 등을 쉽게 생성하여 특정 조건에 맞는 데이터를 필터링하거나, 모든 경우의 수를 탐색하는 데 활용할 수 있습니다.
특히 combinations()와 permutations()를 잘 알아두면 유용합니다. 아래 예시를 살펴보겠습니다.
주어진 리스트 arr에서 2개의 원소를 선택하여 그 합이 소수인 경우의 수를 구하는 것인데, combinations()는 중복되지 않는 조합을 만들어주어 편리하게 사용할 수 있습니다.
from itertools import combinations
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
arr = [3, 1, 6, 4]
answer = 0
for items in combinations(arr, 2): # 2개 원소 선택
num = sum(items) # 합 구하기
if is_prime(num): # 소수 판별
answer += 1 # 소수 개수 증가
print(answer)
2. 효율적인 데이터 관리, collections
collections 모듈은 특수한 목적을 가진 다양한 자료구조를 제공하여, 데이터를 효율적으로 관리하고 처리하는 데 도움을 줍니다.
- deque: 양방향 큐(double-ended queue)로, 리스트의 append()와 pop() 연산보다 빠르며, FIFO(First-In, First-Out) 및 LIFO(Last-In, First-Out) 큐 구현에 유용합니다.
- OrderedDict: 삽입 순서를 기억하는 딕셔너리로, 순서가 중요한 데이터를 다룰 때 유용합니다. Python 3.7부터 일반 dict도 삽입 순서를 유지하지만, 이전 버전에서는 OrderedDict가 필요합니다.
- Counter: 해시 가능한 객체의 개수를 세는 딕셔너리 서브클래스로, 빈도 분석이나 통계 처리 등에 활용할 수 있습니다.
Counter를 활용한 예시를 살펴보면 다음과 같습니다.
from collections import Counter
record = [2,1,2,6,2,4,3,3]
record_count = Counter(record)
# Counter({2:3, 3:2, 1:1, 6:1, 4:1})
그다음으로는 OrderdDict와 deque를 활용하여 LRU 캐시를 구현하는 예시를 살펴봅시다.
LRU(Least Recently Used) 캐시란?
가장 최근에 사용되지 않은 항목을 먼저 제거하는 캐시 기법입니다. functools 모듈의 @lru_cache 데코레이터를 사용하여 간단하게 구현할 수 있습니다.
# deque 사용하여 LRU cache 구현하기
from collections import deque
cache = deque([], maxlen=cache_size)
for key in searches:
if key in cache:
cache.remove(key) # 기존 위치에서 제거
cache.append(key) # 가장 최근 위치(맨 끝)로 이동
else:
cache.append(key) # 새로운 검색어 추가 (캐시 크기 초과 시 자동 제거)
deque를 사용하여 LRU cache 구현할 때에는 `maxlen=cache_size` 설정을 통해 삭제 연산이 자동으로 구현되어 편리합니다. 또한 deque의 특성상 O(1) 연산으로 검색어를 추가하거나 삭제할 수 있습니다. 하지만 `cache.remove(key)` 를 수행할 때 O(n) 연산이 발생합니다. 이를 개선하려면 OrderedDict를 사용하여 모든 연산을 O(1) 시간 복잡도로 구현하는 것이 더 효율적입니다.
# OrderedDict 사용하여 LRU cache 구현하기
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity # 캐시의 최대 크기
self.cache = OrderedDict()
def get(self, key: str) -> str:
if key not in self.cache:
return -1 # 키가 존재하지 않으면 -1 반환
else:
self.cache.move_to_end(key, last=True)
# 키가 존재할 경우 해당 키를 가장 최근으로 이동 후 반환
return self.cache[key]
def put(self, key: str, value: str) -> None:
if key in self.cache:
# 캐쉬에 키 존재하는 경우 값 업데이트 후 가장 최근으로 이동
self.cache[key] = value
self.cache.move_to_end(key, last=True)
else:
if len(self.cache) == self.capacity:
# 캐시가 가득 찬 경우 가장 오래된 항목 제거 후 새 키 추가
self.cache.popitem(last=False)
self.cache[key] = value
3. 숫자 변환의 달인: 정수 <-> 이진수, 알파벳 <-> 숫자
프로그래밍을 하다 보면 정수와 이진수 간의 변환, 또는 알파벳과 숫자 간의 변환이 필요한 경우가 종종 있습니다. 비트맵을 활용한 문제(예: 게임 맵, 암호 해독)에서 자주 활용됩니다.
- 정수 <-> 이진수: bin(), int(binary_string, 2) 함수를 사용하여 손쉽게 변환할 수 있습니다.
- 알파벳 <-> 숫자: ord(), chr() 함수를 활용하여 ASCII 코드 값을 얻거나 문자로 변환할 수 있습니다.
bin(9) # 9를 2진수로 변환한 결과인 '0b1001'을 반환합니다.
# 비트 2진연산 하는법
# 9 → 1001 (2진수)
# 30 → 11110 (2진수)
9 | 30 # 비트 OR 연산 → 0b11111 (2진수) -> 10진수 31
bin(9|30)[2:] # 0b 접두사 제거 -> 11111
bin(3|5)[2:] # '0b111' -> '111'
bin(3|5)[2:].zfill(5)
# zfill(5)은 문자열 길이를 5로 맞추고 앞을 0으로 채움 -> '00111'
bin(3|5)[2:].zfill(5).replace('1', '#').replace('0', ' ')
# 1을 #로 변환, 0을 공백( )으로 변환 -> ' ###'
ord('a') # 97
ord('b') # 98
ord('c') # 99
def alphabet_index(ch):
return ord(ch) - ord('a') + 1
# a -> 1, b -> 2, ...
4. 문자열 다루기의 기본: String method
문자열은 프로그래밍에서 가장 많이 다루는 데이터 타입 중 하나입니다. 파이썬은 다양한 문자열 처리 메서드를 제공하며, 그중 join() 메서드는 여러 문자열을 특정 구분자를 기준으로 연결할 때 매우 유용합니다.
- join(): 리스트나 튜플의 문자열 요소들을 특정 구분자(예: 쉼표, 공백 등)를 사용하여 하나의 문자열로 합칠 수 있습니다.
words = ["Hello", "World"]
print(" ".join(words)) # Hello World
- strip() 은 문자열의 공백을 제거해 줍니다. lstrip()이나 rstrip()으로 왼쪽 또는 오른쪽의 공백을 제거할 수 있습니다.
- replace()를 활용하여 특정 문자열을 치환할 수 있습니다.
- 그 외에도 find(), index(), count()등으로 특정 문자열을 찾거나, 문자 개수를 세는데 활용할 수 있습니다.
- rjust(), ljust(), center()를 활용하여 문자열을 오른쪽, 왼쪽, 또는 가운데로 정렬하거나 공백을 채울 수 있습니다.
text = "Hello"
print(text.rjust(10)) # 기본값(공백)
print(text.rjust(10, '-')) # '-'로 채우기
# 출력 결과
# ` Hello`
# `-----Hello`
5. 리스트 활용의 묘미: zip()
리스트는 데이터를 저장하고 관리하는 데 필수적인 자료구조입니다. 파이썬의 리스트 메서드 중 zip() 함수는 여러 개의 리스트를 묶어 튜플의 리스트로 만들어주는 기능을 합니다. 두 개 이상의 리스트를 병렬적으로 순회하거나, 두 리스트의 요소를 짝지어 처리할 때 유용합니다.
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
zipped = zip(list1, list2)
print(list(zipped))
# [(1, 'a'), (2, 'b'), (3, 'c')]
또한 zip()을 사용하여 쉽게 dictionary를 만들 수 있습니다.
keys = ["name", "age", "city"]
values = ["Alice", 25, "Berlin"]
my_dict = dict(zip(keys, values))
print(my_dict)
# {'name': 'Alice', 'age': 25, 'city': 'Berlin'}
zip(*zipped)을 사용하면 다시 원래 리스트로 분해할 수 있습니다.
zipped = [(1, 'a'), (2, 'b'), (3, 'c')]
list1, list2 = zip(*zipped)
print(list1) # (1, 2, 3)
print(list2) # ('a', 'b', 'c')
행렬에서 zip(*matrix)를 사용하면 행(row)과 열(column)이 바뀝니다.
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
transposed = list(zip(*matrix))
print(transposed)
# [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
6. 간결하고 효율적인 데이터 변환: map()
map() 함수는 주어진 함수를 리스트나 다른 이터러블의 각 요소에 적용하여 새로운 이터러블을 생성하는 기능을 제공합니다. 리스트의 모든 숫자를 제곱하거나, 문자열 리스트의 각 요소를 정수로 변환하는 등, 간결하고 효율적인 데이터 변환에 활용할 수 있습니다.
map()의 기본적인 사용 방법은 다음과 같습니다.
map(함수, iterable)
첫 번째 인자는 적용할 함수이고, 두 번째 인자는 반복 가능한(iterable) 객체 (리스트, 튜플 등)입니다. 이때 map()의 반환값은 map 객체이므로, list()나 tuple()로 변환해야 볼 수 있습니다.
문자열을 정수로 변환하는 예시를 살펴봅시다.
string_numbers = ['1', '2', '3', '4', '5']
int_numbers = list(map(int, string_numbers))
print(int_numbers) # 출력:[1, 2, 3, 4, 5]
다음은 리스트의 모든 요소에 함수를 적용하는 예시입니다.
def square(x):
return x * x
numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(square, numbers))
print(squared_numbers) # 출력: [1, 4, 9, 16, 25]
이때 굳이 square() 함수를 따로 정의하지 않고, lambda 함수를 활용하면 더 간결하게 표현할 수 있습니다.
numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(lambda x: x * x, numbers))
print(squared_numbers) # 출력: [1, 4, 9, 16, 25]
lambda 대신 리스트 컴프리헨션 방식을 사용하는 경우입니다.
numbers = [1, 2, 3, 4, 5]
squared_numbers = [x * x for x in numbers]
print(squared_numbers) # 출력: [1, 4, 9, 16, 25]
두 가지 리스트를 사용하는 예시입니다.
numbers1 = [1, 2, 3]
numbers2 = [4, 5, 6]
sum_numbers = list(map(lambda x, y: x + y, numbers1, numbers2))
print(sum_numbers) # 출력: [5, 7, 9]
filter()와 조합하여 조건에 맞는 데이터 변환 시에도 사용할 수 있습니다.
# 양수만 제곱하여 변환하는 프로그램
numbers = [-2, -1, 0, 1, 2]
positive_squared = list(map(lambda x: x ** 2, filter(lambda x: x > 0, numbers)))
# 리스트 컴프리헨션 스타일
# positive_squared = [x ** 2 for x in numbers if x > 0]
print(positive_squared) # 출력: [1, 4]
7. 간결한 함수 정의: 람다 펑션
람다 함수는 익명 함수를 간결하게 정의할 수 있는 방법입니다. 간단한 연산이나 함수를 일시적으로 사용할 때 유용합니다. map(), filter(), sorted() 등과 함께 사용하여 코드를 더욱 간결하게 만들 수 있습니다.
calculator = {
'S': lambda x: x, # Single power (그대로)
'D': lambda x: x ** 2, # Double power (제곱)
'T': lambda x: x ** 3 # Triple power (세제곱)
}
symbol = 'D'
number = 4
result = calculator[symbol](number)
print(result) # 출력: 16 (4²)
lambda를 활용하면 튜플 리스트 정렬을 쉽게 커스텀할 수 있습니다.
students = [("Alice", 25), ("Bob", 20), ("Charlie", 23)]
# 나이 기준으로 정렬 (두 번째 요소를 기준으로 정렬)
sorted_students = sorted(students, key=lambda x: x[1])
print(sorted_students)
# 출력: [('Bob', 20), ('Charlie', 23), ('Alice', 25)]
8. 중복 없는 데이터 관리: set()
set() 자료형은 중복된 요소를 허용하지 않고, 순서가 없는 데이터 집합을 표현하는 데 사용됩니다. 리스트에서 중복된 요소를 제거하거나, 두 집합 간의 합집합, 교집합, 차집합 등을 쉽게 구할 수 있습니다.
- isdisjoint(): 공통된 요소가 없다면 True를 리턴합니다. (교집합이 공집합일 때)
- issubset(other) (<=): 모든 요소가 other에 있는지 확인합니다.
- issuperset(other) (>=): other의 모든 요소가 집합에 있는지 확인합니다.
- union(|), intersection(&), difference(-), symmetric_difference(^)로 집합 연산을 수행할 수 있습니다,
- update, intersection_update, difference_update, symmetric_difference_update를 통해 손쉽게 집합에 데이터를 추가하거나 제거할 수 있습니다.
- remove()와 discard()는 집합에서 요소를 삭제하는 같은 기능을 하지만, remove()는 찾는 요소가 없을 때 KeyError를 발생시킵니다.
9. 강력한 행렬 처리: numpy Array
수치 계산 및 과학 기술 분야에서 널리 사용되는 numpy 라이브러리는 다차원 배열(행렬)을 효율적으로 처리할 수 있는 기능을 제공합니다.
- np.zeros(shape), np.ones(shape), np.full(shape, value) 등을 사용하여 0, 1 또는 원하는 값으로 채워진 배열을 생성할 수 있습니다. 또한 배열 인덱싱 및 슬라이싱이 보다 쉽게 가능합니다.
import numpy as np
s = [['C', 'C', 'B', 'D', 'E'],
['A', 'A', 'A', 'D', 'E'],
['A', 'A', 'A', 'B', 'F'],
['C', 'C', 'B', 'B', 'F']]
s = np.array(s)
col = 5 # 열
row = 4 # 행
for i in range(row-1):
for j in range(col-1):
print(s[i:i+2, j:j+2]) # 2x2 사각형 출력
# 모든 문자가 똑같은 사각형 찾기
if np.all(s[i:i+2, j:j+2] == 'A'): # true or false
print("Square found")
# np array에서 원소 개수 구하기
num_of_a = (s=='A').sum()
num_of_a = np.count_nonzero(s == 'A')
10. 데이터 분석의 핵심: pandas DataFrame
pandas 라이브러리는 데이터 분석 및 조작, 전처리를 위한 강력한 도구인 DataFrame을 제공합니다. 엑셀 스프레드시트와 유사한 형태로 데이터를 구조화하고 분석하는 데 유용합니다. 모든 요소가 동일한 데이터 타입을 가져야 하는 NumPy Array와는 달리, DataFrame은 각 열(column)마다 서로 다른 데이터 타입을 가질 수 있습니다.
import pandas as pd
data = pd.DataFrame()
# 빈 데이터 프레임에 데이터 채우기
data = pd.DataFrame({
0: [1, 2, 3, 4, 5],
1: [1, 2, 3, 3, 4]
})
data.value_counts()
# 각 행을 하나의 "튜플"로 간주하고, 그 "튜플"이 데이터프레임에서 몇 번 나타나는지 세어줍니다.
# (1, 1) 1
# (2, 2) 1
# (3, 3) 1
# (4, 4) 1
# (5, 4) 1
data[0].value_counts()
# 첫 번째 열에서 중복되는 값의 개수를 확인
if len(data[1]) == len(data[1].value_counts()):
# 두 번째 열에서 중복되는 아이템이 있는지 확인하기
print("no duplication")
11. 패턴 매칭의 힘: 정규표현식
정규표현식(Regular Expression)은 문자열에서 특정 패턴을 검색하고 조작하는 강력한 도구입니다. 복잡한 문자열 처리 작업을 효율적으로 수행할 수 있습니다. 텍스트 데이터에서 특정 패턴을 찾거나, 문자열을 파싱 하고, 유효성을 검사하는 등 다양한 작업에 활용할 수 있습니다.
import re
testcase = ['1S2D*3T', '1D2S#10S']
pattern = re.compile(r'([0-9]|10)([SDT])([\\*\\#]?)')
# 그룹핑 설명
# ([0-9]|10): 숫자(0부터 9까지의 숫자) 또는 10을 첫 번째 그룹으로 캡처합니다. 이 패턴은 1자리 숫자 또는 2자리 숫자(10)를 포함할 수 있습니다.
# ([SDT]): 문자 'S', 'D', 'T' 중 하나를 두 번째 그룹으로 캡처합니다.
# ([\\*\\#]?): ?는 해당 기호가 있을 수도 없을 수도 있다는 의미입니다. * 또는 # 기호가 있을 수도, 없을 수도 있음을 나타냅니다.
pattern.findall(testcase[0])
# findall은 문자열에서 정규 표현식과 일치하는 모든 항목을 찾아 리스트로 반환합니다.
# [('1', 'S', ''), ('2', 'D', '*'), ('3', 'T', '')]
pattern = re.compiler(r'[a-z]{2}')
# [a-z]: 소문자 알파벳(a부터 z까지)을 의미합니다.
# {2}: 바로 앞의 패턴이 2번 반복되는 것을 의미합니다.
# 즉, 이 정규 표현식은 소문자 알파벳 두 개가 연속적으로 나타나는 부분을 찾습니다.
12. 나눗셈 및 숫자 연산:
파이썬에서 연산을 할 때, 주의해야 할 점과 그에 관련된 함수들을 살펴보겠습니다. math 패키지는 다양한 수학 연산을 위한 함수를 제공하며, 복잡한 수학적 작업을 할 때 유용합니다. ZeroDivisionError를 피하려면 try-except를 사용하거나, 나누기 전에 0을 체크하는 것이 좋습니다.
- 나눗셈 연산자: / (division) 연산자는 소수점까지 포함한 나눗셈을 수행합니다. 두 숫자를 나누었을 때 소수점 이하까지 결과를 반환합니다. // (floor division) 연산자는 나눗셈 후 소수점 이하를 버리고 정수 부분만 반환합니다. 즉, 나누고 난 후 소수점 이하를 버리고 내림(floor) 처리됩니다.
print(7 / 2) # 출력: 3.5
print(7 // 2) # 출력: 3
- 소수점 반올림 (round())
print(round(3.14159, 2)) # 출력: 3.14
print(round(2.71828, 3)) # 출력: 2.718
- 버림 (int() 또는 math.floor()): int() 함수는 소수점 이하를 버리고 정수 부분만 남깁니다. 이 함수는 floor와 유사하게 동작하지만, floor는 항상 소수점 아래를 내리는 반면, int()는 그냥 소수점 이하를 제거하는 방법입니다.
print(int(3.99)) # 출력: 3
print(int(-3.99)) # 출력: -3
import math
print(math.floor(3.7)) # 출력: 3
print(math.floor(-3.7)) # 출력: -4
- 올림 (math.ceil()): 숫자의 소수점 이하를 올려서 가장 작은 정수로 변환합니다.
import math
print(math.ceil(3.2)) # 출력: 4
print(math.ceil(-3.2)) # 출력: -3
13. 모든 요소 또는 일부 요소 확인: any() all() 내장 함수
any() 함수는 이터러블의 요소 중 하나라도 참인 경우 True를 반환하고, all() 함수는 모든 요소가 참인 경우 True를 반환합니다.
# 리스트에 양수만 포함되어 있는지 확인 (모든 값이 양수일 경우 True)
numbers = [1, 2, 3, 4]
print(all(num > 0 for num in numbers)) # 출력: True (모두 양수)
# 리스트에 하나라도 음수가 포함되어 있는지 확인 (하나라도 음수이면 True)
numbers = [1, 2, 3, -1]
print(any(num < 0 for num in numbers)) # 출력: True (하나라도 음수인 값이 있으므로)
마무리하며:
이 글에서 소개한 파이썬의 기능들은 제가 평소에 잘 활용하지 않고 있다가 코딩 테스트 문제를 풀어보며 배우게 된 것들입니다. 이 글이 도움이 되어서 day-to-day 개발이나 코딩 테스트에 도움이 되셨으면 좋겠습니다.
'개발 이모저모' 카테고리의 다른 글
Spring Security, 5분 만에 핵심 개념 완전 정복! 초보자를 위한 친절한 가이드 (2) | 2025.03.15 |
---|---|
시스템 디자인 인터뷰: 배달 시스템 디자인하기 (1) | 2025.03.01 |
동시성 프로그래밍: Python 코루틴과 Go 고루틴 (3) | 2025.02.02 |
SQLAlchemy 2.0 - Major Migration Guide (2) | 2025.01.19 |
NGINX, Proxy, Load Balance (0) | 2025.01.04 |