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

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' 카테고리의 다른 글

파이썬 시리얼 통신 완벽 가이드: pyserial과 struct 활용법  (0) 2025.10.20
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
'파이썬/Basic' 카테고리의 다른 글
  • 파이썬 시리얼 통신 완벽 가이드: pyserial과 struct 활용법
  • Python의 UnicodeDecodeError 해결하기: cp949 코덱 오류
  • Python JSON 라이브러리 비교: json vs ujson vs orjson
  • Geopy : 지리 정보 다루기
코샵
코샵
나의 코딩 일기장
    반응형
  • 코샵
    끄적끄적 코딩 공방
    코샵
    • 분류 전체보기 (730)
      • 스마트팜 (1)
      • 상품 추천 (223)
      • DataBase (0)
        • MongoDB (4)
        • PostgreSQL (0)
      • 하드웨어 (19)
      • 일기장 (4)
      • 파이썬 (131)
        • Basic (42)
        • 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 (5)
      • 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)
  • 인기 글

  • 태그

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

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

티스토리툴바