반응형
API 응답이나 사용자 입력 데이터에서 빈 값들을 깔끔하게 정리해야 할 때가 있죠? 딕셔너리에서 빈 문자열, null, 빈 리스트 등을 효율적으로 제거하는 다양한 방법을 알아보겠습니다! 🧹✨
🎯 언제 이 기법이 필요할까?
실제 상황들
# API 응답 데이터 정리
api_response = {
"name": "홍길동",
"email": "", # 빈 문자열
"phone": None, # null 값
"address": "",
"hobbies": [], # 빈 리스트
"age": 30
}
# 폼 데이터 검증
form_data = {
"username": "user123",
"password": "",
"confirm_password": "",
"bio": None,
"interests": []
}
해결해야 할 문제들
- 불필요한 데이터 전송량 증가
- 데이터베이스 저장 시 불필요한 필드
- JSON 응답 크기 최적화
- 데이터 검증 로직 복잡화
📝 빈 값의 정의와 판별 기준
일반적인 빈 값들
empty_values = {
"empty_string": "",
"none_value": None,
"empty_list": [],
"empty_dict": {},
"empty_tuple": (),
"empty_set": set(),
"zero": 0, # 경우에 따라
"false": False # 경우에 따라
}
빈 값 판별 함수
def is_empty_value(value, include_zero=False, include_false=False):
"""
값이 비어있는지 판별하는 함수
Args:
value: 체크할 값
include_zero: 0도 빈 값으로 볼지 여부
include_false: False도 빈 값으로 볼지 여부
"""
# 기본 빈 값들
if value is None or value == "":
return True
# 컬렉션 타입 빈 값들
if isinstance(value, (list, dict, tuple, set)) and len(value) == 0:
return True
# 선택적 빈 값들
if include_zero and value == 0:
return True
if include_false and value is False:
return True
return False
🔧 Python 구현 방법들
1. Dictionary Comprehension (가장 Pythonic)
def remove_empty_values_basic(data):
"""기본적인 딕셔너리 컴프리헨션 방식"""
return {
key: value
for key, value in data.items()
if value is not None and value != "" and value != []
}
# 사용 예시
original_data = {
"name": "홍길동",
"email": "",
"phone": None,
"age": 30,
"hobbies": [],
"active": True
}
cleaned_data = remove_empty_values_basic(original_data)
print(cleaned_data)
# 출력: {'name': '홍길동', 'age': 30, 'active': True}
2. 고급 Dictionary Comprehension
def remove_empty_values_advanced(data, empty_values=None):
"""
커스터마이징 가능한 빈 값 제거 함수
Args:
data: 정리할 딕셔너리
empty_values: 빈 값으로 취급할 값들의 튜플
"""
if empty_values is None:
empty_values = (None, "", [], {}, ())
return {
key: value
for key, value in data.items()
if value not in empty_values
}
# 사용 예시 - 0과 False도 제외
cleaned_data = remove_empty_values_advanced(
original_data,
empty_values=(None, "", [], {}, (), 0, False)
)
3. 함수형 접근법 (filter 활용)
def remove_empty_values_functional(data):
"""filter 함수를 활용한 방식"""
def is_valid_item(item):
key, value = item
return value is not None and value != "" and value != []
return dict(filter(is_valid_item, data.items()))
# 더 간결한 lambda 버전
def remove_empty_values_lambda(data):
return dict(filter(
lambda item: item[1] is not None and item[1] != "" and item[1] != [],
data.items()
))
🔄 중첩 딕셔너리 처리 (재귀)
깊은 정리 함수
def deep_clean_dict(data, empty_values=None):
"""
중첩된 딕셔너리를 재귀적으로 정리하는 함수
Args:
data: 정리할 데이터 (딕셔너리, 리스트, 또는 기본값)
empty_values: 빈 값으로 취급할 값들
"""
if empty_values is None:
empty_values = (None, "", [], {})
if isinstance(data, dict):
# 딕셔너리인 경우 재귀적으로 처리
cleaned = {}
for key, value in data.items():
cleaned_value = deep_clean_dict(value, empty_values)
# 정리된 값이 빈 값이 아닌 경우만 추가
if cleaned_value not in empty_values:
cleaned[key] = cleaned_value
return cleaned
elif isinstance(data, list):
# 리스트인 경우 각 요소를 재귀적으로 처리
cleaned_list = []
for item in data:
cleaned_item = deep_clean_dict(item, empty_values)
if cleaned_item not in empty_values:
cleaned_list.append(cleaned_item)
return cleaned_list
else:
# 기본값인 경우 그대로 반환
return data
# 사용 예시
nested_data = {
"user": {
"name": "홍길동",
"email": "",
"profile": {
"bio": "",
"interests": [],
"settings": {
"theme": "dark",
"notifications": None
}
}
},
"posts": [
{"title": "첫 번째 포스트", "content": "내용"},
{"title": "", "content": None},
{"title": "세 번째 포스트", "content": ""}
]
}
cleaned_nested = deep_clean_dict(nested_data)
print(cleaned_nested)
# 출력: {'user': {'name': '홍길동', 'profile': {'settings': {'theme': 'dark'}}}, 'posts': [{'title': '첫 번째 포스트', 'content': '내용'}]}
🛠️ 클래스 기반 구현
class DictCleaner:
"""딕셔너리 정리를 위한 유틸리티 클래스"""
def __init__(self, empty_values=None, recursive=True):
"""
Args:
empty_values: 빈 값으로 취급할 값들
recursive: 중첩 구조 재귀 처리 여부
"""
self.empty_values = empty_values or (None, "", [], {}, ())
self.recursive = recursive
def clean(self, data):
"""메인 정리 메서드"""
if self.recursive:
return self._clean_recursive(data)
else:
return self._clean_shallow(data)
def _clean_shallow(self, data):
"""얕은 정리 (1레벨만)"""
if not isinstance(data, dict):
return data
return {
key: value
for key, value in data.items()
if not self._is_empty(value)
}
def _clean_recursive(self, data):
"""깊은 정리 (재귀)"""
if isinstance(data, dict):
cleaned = {}
for key, value in data.items():
cleaned_value = self._clean_recursive(value)
if not self._is_empty(cleaned_value):
cleaned[key] = cleaned_value
return cleaned
elif isinstance(data, list):
cleaned_list = []
for item in data:
cleaned_item = self._clean_recursive(item)
if not self._is_empty(cleaned_item):
cleaned_list.append(cleaned_item)
return cleaned_list
return data
def _is_empty(self, value):
"""빈 값 판별"""
return value in self.empty_values
def add_empty_value(self, value):
"""빈 값 기준 추가"""
self.empty_values = self.empty_values + (value,)
def remove_empty_value(self, value):
"""빈 값 기준 제거"""
self.empty_values = tuple(v for v in self.empty_values if v != value)
# 사용 예시
cleaner = DictCleaner()
cleaned_data = cleaner.clean(nested_data)
# 커스텀 설정
custom_cleaner = DictCleaner(
empty_values=(None, "", [], {}, 0, False),
recursive=True
)
🌐 다른 언어 구현 예시
JavaScript
function removeEmptyValues(obj, recursive = false) {
/**
* 객체에서 빈 값들을 제거하는 함수
* @param {Object} obj - 정리할 객체
* @param {boolean} recursive - 중첩 객체 재귀 처리 여부
*/
const isEmpty = (value) => {
return value === null ||
value === undefined ||
value === "" ||
(Array.isArray(value) && value.length === 0) ||
(typeof value === 'object' && Object.keys(value).length === 0);
};
if (recursive && typeof obj === 'object' && obj !== null) {
if (Array.isArray(obj)) {
return obj
.map(item => removeEmptyValues(item, recursive))
.filter(item => !isEmpty(item));
} else {
const cleaned = {};
for (const [key, value] of Object.entries(obj)) {
const cleanedValue = removeEmptyValues(value, recursive);
if (!isEmpty(cleanedValue)) {
cleaned[key] = cleanedValue;
}
}
return cleaned;
}
}
// 비재귀 처리
return Object.fromEntries(
Object.entries(obj).filter(([key, value]) => !isEmpty(value))
);
}
// 사용 예시
const data = {
name: "홍길동",
email: "",
phone: null,
hobbies: [],
settings: {
theme: "dark",
notifications: ""
}
};
const cleaned = removeEmptyValues(data, true);
console.log(cleaned);
// 출력: { name: "홍길동", settings: { theme: "dark" } }
Java
import java.util.*;
import java.util.stream.Collectors;
public class DictCleaner {
public static Map<String, Object> removeEmptyValues(Map<String, Object> map) {
return map.entrySet().stream()
.filter(entry -> !isEmpty(entry.getValue()))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
}
@SuppressWarnings("unchecked")
public static Map<String, Object> deepRemoveEmptyValues(Map<String, Object> map) {
Map<String, Object> cleaned = new HashMap<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object value = entry.getValue();
if (value instanceof Map) {
Map<String, Object> cleanedNested = deepRemoveEmptyValues((Map<String, Object>) value);
if (!cleanedNested.isEmpty()) {
cleaned.put(entry.getKey(), cleanedNested);
}
} else if (value instanceof List) {
List<Object> cleanedList = ((List<Object>) value).stream()
.filter(item -> !isEmpty(item))
.collect(Collectors.toList());
if (!cleanedList.isEmpty()) {
cleaned.put(entry.getKey(), cleanedList);
}
} else if (!isEmpty(value)) {
cleaned.put(entry.getKey(), value);
}
}
return cleaned;
}
private static boolean isEmpty(Object value) {
if (value == null) return true;
if (value instanceof String) return ((String) value).isEmpty();
if (value instanceof Collection) return ((Collection<?>) value).isEmpty();
if (value instanceof Map) return ((Map<?, ?>) value).isEmpty();
return false;
}
}
⚡ 성능 최적화 및 벤치마크
성능 비교 테스트
import time
from functools import wraps
def benchmark(func):
"""실행 시간 측정 데코레이터"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__}: {end - start:.6f}초")
return result
return wrapper
# 테스트 데이터 생성
def generate_test_data(size=1000):
"""대량의 테스트 데이터 생성"""
import random
data = {}
empty_values = [None, "", [], {}]
for i in range(size):
if random.choice([True, False]):
data[f"key_{i}"] = random.choice(empty_values)
else:
data[f"key_{i}"] = f"value_{i}"
return data
# 성능 테스트
@benchmark
def test_dict_comprehension(data):
return {k: v for k, v in data.items() if v not in (None, "", [], {})}
@benchmark
def test_filter_method(data):
return dict(filter(lambda x: x[1] not in (None, "", [], {}), data.items()))
# 테스트 실행
test_data = generate_test_data(10000)
print("10,000개 항목 성능 테스트:")
result1 = test_dict_comprehension(test_data.copy())
result2 = test_filter_method(test_data.copy())
# 일반적으로 dict comprehension이 더 빠름
메모리 효율적인 구현
def clean_dict_generator(data, chunk_size=1000):
"""
대용량 딕셔너리를 청크 단위로 처리하는 제너레이터
Args:
data: 정리할 딕셔너리
chunk_size: 한 번에 처리할 항목 수
"""
items = iter(data.items())
while True:
chunk = dict()
# 청크 크기만큼 항목 수집
for _ in range(chunk_size):
try:
key, value = next(items)
if value not in (None, "", [], {}):
chunk[key] = value
except StopIteration:
if chunk:
yield chunk
return
if chunk:
yield chunk
# 사용 예시
large_data = generate_test_data(100000)
cleaned_chunks = clean_dict_generator(large_data, 1000)
# 청크별로 처리
for i, chunk in enumerate(cleaned_chunks):
print(f"청크 {i+1}: {len(chunk)}개 항목 처리됨")
🎯 실전 활용 사례
1. REST API 응답 정리
class APIResponseCleaner:
"""API 응답 데이터 정리 클래스"""
@staticmethod
def clean_user_profile(profile_data):
"""사용자 프로필 데이터 정리"""
cleaned = {
key: value
for key, value in profile_data.items()
if value not in (None, "", [], {})
}
# 필수 필드 확인
required_fields = ['id', 'username']
for field in required_fields:
if field not in cleaned:
raise ValueError(f"필수 필드 누락: {field}")
return cleaned
@staticmethod
def clean_api_response(response_data):
"""전체 API 응답 정리"""
if isinstance(response_data, dict):
return deep_clean_dict(response_data)
elif isinstance(response_data, list):
return [
deep_clean_dict(item)
for item in response_data
if item not in (None, "", [], {})
]
return response_data
# 사용 예시
api_data = {
"users": [
{"id": 1, "name": "홍길동", "email": "", "active": True},
{"id": 2, "name": "", "email": "kim@example.com", "active": None},
{"id": 3, "name": "박철수", "email": "park@example.com", "active": False}
],
"pagination": {
"page": 1,
"limit": 10,
"total": None
}
}
cleaned_api_data = APIResponseCleaner.clean_api_response(api_data)
2. 폼 데이터 검증
class FormDataValidator:
"""폼 데이터 검증 및 정리 클래스"""
def __init__(self, required_fields=None):
self.required_fields = required_fields or []
def validate_and_clean(self, form_data):
"""폼 데이터 검증 및 정리"""
# 1단계: 빈 값 제거
cleaned = self._remove_empty_values(form_data)
# 2단계: 필수 필드 확인
self._validate_required_fields(cleaned)
# 3단계: 데이터 타입 정규화
cleaned = self._normalize_data_types(cleaned)
return cleaned
def _remove_empty_values(self, data):
"""빈 값 제거"""
return {
key: value.strip() if isinstance(value, str) else value
for key, value in data.items()
if value not in (None, "", [], {}) and
(not isinstance(value, str) or value.strip())
}
def _validate_required_fields(self, data):
"""필수 필드 검증"""
missing = [field for field in self.required_fields if field not in data]
if missing:
raise ValueError(f"필수 필드 누락: {', '.join(missing)}")
def _normalize_data_types(self, data):
"""데이터 타입 정규화"""
normalized = {}
for key, value in data.items():
# 이메일 소문자 변환
if 'email' in key.lower() and isinstance(value, str):
normalized[key] = value.lower()
# 전화번호 정규화 (숫자만)
elif 'phone' in key.lower() and isinstance(value, str):
normalized[key] = ''.join(filter(str.isdigit, value))
else:
normalized[key] = value
return normalized
# 사용 예시
validator = FormDataValidator(required_fields=['username', 'email'])
form_input = {
"username": " testuser ",
"email": " TEST@EXAMPLE.COM ",
"password": "secret123",
"confirm_password": "",
"phone": "010-1234-5678",
"bio": "",
"interests": []
}
try:
validated_data = validator.validate_and_clean(form_input)
print(validated_data)
# 출력: {'username': 'testuser', 'email': 'test@example.com', 'password': 'secret123', 'phone': '01012345678'}
except ValueError as e:
print(f"검증 실패: {e}")
'파이썬 > Basic' 카테고리의 다른 글
Python의 UnicodeDecodeError 해결하기: cp949 코덱 오류 (0) | 2025.05.09 |
---|---|
Python JSON 라이브러리 비교: json vs ujson vs orjson (0) | 2025.02.26 |
Geopy : 지리 정보 다루기 (2) | 2025.02.10 |
lru_cache : 캐싱 메모리 (0) | 2025.02.08 |
함수 하나로 여러 가지 처리하기? Python 오버로딩 (2) | 2024.12.27 |