Python을 배워보자

파이썬에서 복잡한 딕셔너리 구조 탐색하기: 다양한 방법과 예제

_Blue_Sky_ 2025. 2. 21. 22:14
728x90
 

 

파이썬에서 복잡한 딕셔너리 구조에서 특정 경로를 탐색하는 방법은 여러 가지가 있습니다. 딕셔너리는 중첩된 구조를 가질 수 있어서, 특정 키나 값을 찾기 위해 다양한 접근법을 사용할 수 있습니다. 이 답변에서는 예제를 통해 자세히 설명하고, 각 방법을 비교하며, 키워드는 한 줄에 쉼표로 구분해서 정리하겠습니다. 천천히 따라가면서 이해할 수 있도록 길고 상세하게 작성하겠습니다!
 

1. 기본적인 키 접근법 (직접 접근)
가장 간단한 방법은 딕셔너리의 키를 직접 사용해 값을 꺼내는 것입니다. 중첩된 딕셔너리라면 점진적으로 키를 지정해서 들어가야 합니다.
예제 1: 직접 접근
data = {
    "user": {
        "name": "Alice",
        "info": {
            "age": 30,
            "city": "Seoul"
        }
    }
}

# "city" 값을 꺼내기
city = data["user"]["info"]["city"]
print(city)  # 출력: Seoul
설명
  • data["user"]로 첫 번째 레벨에 접근합니다. 결과는 {"name": "Alice", "info": {...}}.
  • 그 다음 ["info"]로 두 번째 레벨에 접근합니다. 결과는 {"age": 30, "city": "Seoul"}.
  • 마지막으로 ["city"]로 원하는 값을 가져옵니다.
  • 이 방식은 경로를 정확히 알고 있을 때 유용하지만, 키가 존재하지 않으면 KeyError가 발생합니다.
장점과 단점
  • 장점: 코드가 직관적이고 빠름.
  • 단점: 키가 없으면 예외 발생, 동적인 경로 탐색 불가능.

728x90
2. get() 메서드 사용 (안전한 접근)
get() 메서드는 키가 없어도 기본값을 반환하게 설정할 수 있어 안전합니다. 중첩 딕셔너리에서는 연속적으로 호출해야 합니다.
예제 2: get() 메서드
data = {
    "user": {
        "name": "Bob",
        "info": {
            "age": 25
        }
    }
}

# "city" 값이 없는 경우 기본값 반환
city = data.get("user", {}).get("info", {}).get("city", "Unknown")
print(city)  # 출력: Unknown
설명
  • data.get("user", {}): "user" 키가 없으면 빈 딕셔너리 {}를 반환.
  • .get("info", {}): "info" 키가 없으면 빈 딕셔너리 반환.
  • .get("city", "Unknown"): "city" 키가 없으면 "Unknown" 반환.
  • 이 방식은 KeyError를 피할 수 있어 디버깅이나 입력 데이터가 불완전할 때 유용합니다.
추가 예제: 중첩 깊이 테스트
data = {"user": {"details": {"active": True}}}
active = data.get("user", {}).get("details", {}).get("active", False)
print(active)  # 출력: True

# 경로가 틀린 경우
wrong = data.get("user", {}).get("info", {}).get("city", "N/A")
print(wrong)  # 출력: N/A
장점과 단점
  • 장점: 예외 처리 불필요, 기본값 설정 가능.
  • 단점: 깊은 중첩 구조에서 코드가 길어질 수 있음, 동적 경로 탐색은 여전히 어려움.

3. 재귀 함수로 동적 탐색
복잡한 딕셔너리에서 특정 키를 찾으려면 재귀적으로 탐색하는 함수를 만들 수 있습니다. 이 방법은 키의 위치를 모를 때 유용합니다.
예제 3: 재귀 탐색 함수
def find_key(data, target_key):
    if not isinstance(data, dict):
        return None
    if target_key in data:
        return data[target_key]
    for value in data.values():
        if isinstance(value, dict):
            result = find_key(value, target_key)
            if result is not None:
                return result
    return None

data = {
    "user": {
        "name": "Charlie",
        "info": {
            "age": 35,
            "address": {
                "city": "Tokyo"
            }
        }
    }
}

city = find_key(data, "city")
print(city)  # 출력: Tokyo
설명
  • isinstance(data, dict)로 현재 데이터가 딕셔너리인지 확인.
  • target_key in data로 목표 키가 있는지 확인하고, 있으면 값을 반환.
  • 없으면 모든 값(data.values())을 순회하며 딕셔너리일 경우 재귀 호출.
  • "city"가 중첩된 "address" 안에 있지만, 함수가 자동으로 찾아줍니다.
추가 예제: 여러 키 탐색
def find_all_keys(data, target_key, results=None):
    if results is None:
        results = []
    if not isinstance(data, dict):
        return results
    if target_key in data:
        results.append(data[target_key])
    for value in data.values():
        if isinstance(value, dict):
            find_all_keys(value, target_key, results)
    return results

data = {
    "user1": {"name": "David", "city": "Paris"},
    "user2": {"info": {"city": "London"}}
}

cities = find_all_keys(data, "city")
print(cities)  # 출력: ["Paris", "London"]
장점과 단점
  • 장점: 동적 탐색 가능, 키 위치를 몰라도 됨, 복잡한 구조에 적합.
  • 단점: 재귀 호출로 성능 저하 가능, 구현이 복잡할 수 있음.

4. 경로 리스트로 탐색 (Path 기반)
경로를 리스트로 받아서 순차적으로 탐색하는 방법입니다. 동적으로 경로를 지정할 수 있어 유연합니다.
예제 4: 경로 리스트 탐색
def get_by_path(data, path):
    current = data
    for key in path:
        if isinstance(current, dict) and key in current:
            current = current[key]
        else:
            return None
    return current

data = {
    "user": {
        "info": {
            "age": 40,
            "city": "Berlin"
        }
    }
}

path = ["user", "info", "city"]
city = get_by_path(data, path)
print(city)  # 출력: Berlin

# 잘못된 경로
wrong_path = ["user", "details", "city"]
result = get_by_path(data, wrong_path)
print(result)  # 출력: None
설명
  • path 리스트를 순회하며 각 키로 접근.
  • isinstance(current, dict)로 딕셔너리인지 확인하고, 키가 존재하는지 체크.
  • 경로가 맞으면 값을 반환, 틀리면 None 반환.
 
추가 예제: 기본값 추가
def get_by_path_with_default(data, path, default="N/A"):
    current = data
    for key in path:
        if isinstance(current, dict) and key in current:
            current = current[key]
        else:
            return default
    return current

city = get_by_path_with_default(data, ["user", "info", "city"])
print(city)  # 출력: Berlin

missing = get_by_path_with_default(data, ["user", "info", "country"])
print(missing)  # 출력: N/A
장점과 단점
  • 장점: 경로를 동적으로 지정 가능, 간결한 코드.
  • 단점: 경로를 미리 알아야 함, 리스트 외의 구조 처리 어려움.

5. collections.defaultdict 활용
collections.defaultdict를 사용하면 중첩 딕셔너리를 쉽게 다룰 수 있습니다. 기본값을 자동으로 생성하도록 설정 가능합니다.
예제 5: defaultdict 사용
from collections import defaultdict

def nested_defaultdict():
    return defaultdict(nested_defaultdict)

data = nested_defaultdict()
data["user"]["info"]["city"] = "New York"
data["user"]["name"] = "Eve"

# 딕셔너리로 변환
data_dict = dict(data)
print(dict(data_dict["user"]["info"]))  # 출력: {'city': 'New York'}
print(data["user"]["name"])  # 출력: Eve
설명
  • defaultdict를 재귀적으로 정의해 중첩 구조에서 키가 없어도 자동으로 딕셔너리 생성.
  • 직접 접근 가능하며, 이후 dict()로 일반 딕셔너리로 변환 가능.
장점과 단점
  • 장점: 키 존재 여부 걱정 없이 바로 사용 가능.
  • 단점: 읽기 전용 탐색에는 적합하지 않음, 메모리 사용량 증가 가능.

 

결론
  • 간단한 경우: 직접 접근이나 get() 사용.
  • 키 위치 모를 때: 재귀 함수로 탐색.
  • 경로가 명확할 때: 경로 리스트 방식.
  • 동적 생성 필요: defaultdict 활용.
 
728x90