딕셔너리 빈 값 제거 완전 가이드: 깔끔한 데이터 정리 기법

2025. 6. 4. 23:44·파이썬/Basic
반응형

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
'파이썬/Basic' 카테고리의 다른 글
  • Python의 UnicodeDecodeError 해결하기: cp949 코덱 오류
  • Python JSON 라이브러리 비교: json vs ujson vs orjson
  • Geopy : 지리 정보 다루기
  • lru_cache : 캐싱 메모리
코샵
코샵
나의 코딩 일기장
    반응형
  • 코샵
    끄적끄적 코딩 공방
    코샵
    • 분류 전체보기 (725)
      • 스마트팜 (0)
      • 상품 추천 (223)
      • MongoDB (4)
      • 하드웨어 (17)
      • 일기장 (4)
      • 파이썬 (130)
        • Basic (41)
        • OpenCV (8)
        • Pandas (15)
        • PyQT (3)
        • SBC(Single Board Computer) (1)
        • 크롤링 (14)
        • Fast API (29)
        • Package (6)
      • Unity (138)
        • Tip (41)
        • Project (1)
        • Design Pattern (8)
        • Firebase (6)
        • Asset (2)
      • Linux (4)
      • C# (97)
        • Algorithm (11)
        • Window (7)
      • TypeScript (51)
        • CSS (10)
      • Git (11)
      • SQL (5)
      • Flutter (10)
        • Tip (1)
      • System (1)
      • BaekJoon (6)
      • Portfolio (2)
      • MacOS (1)
      • 유틸리티 (1)
      • 서비스 (6)
      • 자동화 (3)
      • Hobby (10)
        • 물생활 (10)
        • 식집사 (0)
  • 인기 글

  • 태그

    programmerlife
    상품 리뷰 크롤링
    유니티
    ipcamera
    셀레니움
    리스트
    카페24리뷰
    list
    C#
    믈레코비타멸균우유
    스크립트 실행
    codingtips
    appdevelopment
    리뷰관리
    unity
    rtsp
    codingcommunity
    스마트스토어리뷰
    카페24리뷰이관
    Python
    devlife
    cv2
    쇼핑몰리뷰
    라떼우유
    스크립트 실행 순서
    programming101
    파이썬
    리뷰이관
    긴유통기한우유
    learntocode
  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
코샵
딕셔너리 빈 값 제거 완전 가이드: 깔끔한 데이터 정리 기법
상단으로

티스토리툴바