1. ChainMap 이란?
- ChainMap은 Python의
collections
모듈에서 제공하는 클래스로, - 여러 매핑(딕셔너리 등)을 단일 뷰로 그룹화하는 기능을 제공.
ChainMap은 여러 딕셔너리를 연결(chain)하여
마치 하나의 딕셔너리처럼 사용 가능한 자료구조.
내부적으로 매핑 목록을 유지하면서 이들을 함께 검색하는 구조임.
2023.07.11 - [Python] - [Python] dictionary (Mapping type) : basic
[Python] dictionary (Mapping type) : basic
dictionary (dict)Python에서 dictionary는key-value pair를 item으로 가지는unorderedmutablecollection임.set과 함께 curly bracket (or brace)를 사용하는데, empty dictionary가 바로 {}로 표현됨(dictionary가 set보다 많이 이용되는
ds31x.tistory.com
2. 기본 문법
2.1. 생성자
collections.ChainMap(*maps)
생성자 매개변수:
*maps
:- 연결할 매핑 객체들(딕셔너리).
매개변수를 제공하지 않으면 기본적으로 하나의 빈 딕셔너리를 포함하는ChainMap
인스턴스 생성.
- 연결할 매핑 객체들(딕셔너리).
2.2. 반환값
생성자는 여러 매핑의 단일 통합 뷰를 제공하는 ChainMap
인스턴스 반환.
3. ChainMap 사용 튜토리얼
3.1. 기본 사용법
from collections import ChainMap
# 1. 두 개의 딕셔너리 정의
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
# 2. ChainMap 생성
chain = ChainMap(dict1, dict2)
# 3. ChainMap 사용
print(chain['a']) # 1 (dict1에서 찾음)
print(chain['b']) # 2 (dict1에서 먼저 찾음)
print(chain['c']) # 4 (dict2에서 찾음)
# 4. 존재하지 않는 키 접근 시 KeyError 발생
# print(chain['d']) # KeyError: 'd'
# 5. 키 목록 확인 (중복 제거된 모든 키)
print(list(chain.keys())) # ['a', 'b', 'c']
# 6. 값 목록 확인 (첫 번째 발견된 키의 값)
print(list(chain.values())) # [1, 2, 4]
3.2. ChainMap 탐색 순서
- ChainMap은 리스트에 저장된 매핑을 순서대로 검색하여 첫 번째로 발견되는 키 반환.
- ChainMap에서는 앞쪽에 위치한 매핑(child라고 불림)이 높은 우선순위를 가지는 구조.
주의점
- 키 검색 시 첫 번째 맵부터 순차적 탐색 진행.
- 먼저 발견된 키의 값 반환 및 이후 맵 탐색 중단.
- 모든 맵에서 키를 찾지 못한 경우 KeyError 발생.
from collections import ChainMap
# 여러 딕셔너리 정의
defaults = {'theme': 'default', 'language': 'en', 'showIndex': True, 'showFooter': True}
user_settings = {'theme': 'dark'}
site_settings = {'showFooter': False}
# ChainMap 생성 (검색 순서: user_settings -> site_settings -> defaults)
settings = ChainMap(user_settings, site_settings, defaults)
# 탐색 동작 확인
print(settings['theme']) # 'dark' (user_settings에서 발견)
print(settings['language']) # 'en' (defaults에서 발견)
print(settings['showFooter']) # False (site_settings에서 발견)
# 첫 번째 맵 확인
print(settings.maps[0]) # {'theme': 'dark'}
# 모든 맵 확인
print(settings.maps) # [{'theme': 'dark'}, {'showFooter': False}, {...}]
3.3. ChainMap 수정
ChainMap 객체의 수정은 첫 번째 매핑에만 영향을 준다.
즉, 키 추가, 변경, 삭제와 같은 모든 수정 작업이 첫 번째 맵에서만 수행되는 특성을 가짐.
- 새 키-값 쌍 추가 시 첫 번째 맵에만 추가.
- 키 값 변경 시 첫 번째 맵의 해당 키만 변경.
- 키 삭제 시 첫 번째 맵에 존재하는 키만 삭제 가능.
- 첫 번째 맵에 없는 키 삭제 시도 시 KeyError 발생.
from collections import ChainMap
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
chain = ChainMap(dict1, dict2)
# 1. 키 수정 (첫 번째 맵에만 적용)
chain['b'] = 10
print(chain['b']) # 10
print(dict1) # {'a': 1, 'b': 10} - dict1이 수정됨
print(dict2) # {'b': 3, 'c': 4} - dict2는 변경 없음
# 2. 새 키 추가 (첫 번째 맵에만 적용)
chain['d'] = 5
print(chain['d']) # 5
print(dict1) # {'a': 1, 'b': 10, 'd': 5} - dict1에 추가됨
print(dict2) # {'b': 3, 'c': 4} - dict2는 변경 없음
# 3. del 연산 (첫 번째 맵에서만 삭제)
del chain['b']
print('b' in chain) # True - dict2에 여전히 존재
print(dict1) # {'a': 1, 'd': 5} - dict1에서 삭제됨
print(dict2) # {'b': 3, 'c': 4} - dict2는 변경 없음
# 4. 첫 번째 맵에 없는 키 삭제 시도시 KeyError 발생
# del chain['c'] # KeyError: "Key not found in the first mapping: 'c'"
3.4. child와 parent 개념
ChainMap
에서 'child'와 'parent'는 계층 구조를 표현하는 중요한 개념.
- child(자식):
ChainMap
에서 앞쪽(인덱스가 작은)에 위치한 맵.- 높은 우선순위를 가지며 수정 작업이 이루어지는 위치.
- 보통 첫 번째 맵(maps[0])이 기본 child.
- parent(부모):
- 첫 번째 맵을 제외한 나머지 맵들의
ChainMap
. parents
속성으로 접근 가능한 구조.
- 첫 번째 맵을 제외한 나머지 맵들의
- 계층 관계:
- 자식 맵이 부모 맵보다 우선순위가 높으며, 변경 가능한 상태를 표현할 때 유용한 구조.
from collections import ChainMap
# 기본 맵 정의
local_settings = {'debug': True} # child - 지역 설정
global_settings = {'debug': False, 'log_level': 'INFO'} # parent의 일부 - 전역 설정
default_settings = {'debug': False, 'log_level': 'WARNING', 'theme': 'default'} # parent의 일부 - 기본 설정
# ChainMap 생성 (local_settings가 child, 나머지는 parent)
settings = ChainMap(local_settings, global_settings, default_settings)
# child 접근 및 수정
print(settings.maps[0]) # {'debug': True} - child 맵
settings['log_level'] = 'DEBUG' # child 맵에 새 키-값 추가
print(settings['log_level']) # 'DEBUG'
# parent 접근
parent_settings = settings.parents # 첫 번째 맵 제외한 ChainMap
print(parent_settings['log_level']) # 'INFO' - global_settings에서 참조
print(parent_settings['theme']) # 'default' - default_settings에서 참조
# 새로운 child 맵 추가
temporary_settings = {}
new_settings = settings.new_child(temporary_settings) # temporary_settings가 새로운 child
print(new_settings.maps) # [{}(새 child), {'debug': True, 'log_level': 'DEBUG'}, ...]
ChainMap
의 child-parent 구조는 범위(스코프), 설정 계층, 오버라이드 메커니즘 등 여러 개념적 모델을 표현하는데 강력한 도구임.
from collections import ChainMap
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
chain = ChainMap(dict1, dict2)
# 1. 새로운 빈 맵을 앞에 추가 (new_child 메서드)
new_chain = chain.new_child()
print(new_chain.maps) # [{}, {'a': 1, 'b': 2}, {'b': 3, 'c': 4}]
# 2. 특정 맵을 앞에 추가
dict3 = {'a': 10, 'd': 5}
new_chain2 = chain.new_child(dict3)
print(new_chain2['a']) # 10 (dict3의 값이 우선)
print(new_chain2.maps) # [{'a': 10, 'd': 5}, {'a': 1, 'b': 2}, {'b': 3, 'c': 4}]
# 3. 부모 체인 얻기 (첫 번째 맵 제외)
parents = chain.parents
print(parents.maps) # [{'b': 3, 'c': 4}]
# 4. 새로운 ChainMap 직접 생성
new_chain3 = ChainMap({}, dict1, dict2)
print(new_chain3.maps) # [{}, {'a': 1, 'b': 2}, {'b': 3, 'c': 4}]
4. ChainMap 주요 속성 및 메서드
4.1. 주요 속성
from collections import ChainMap
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
chain = ChainMap(dict1, dict2)
# 1. maps 속성 - 매핑 목록 확인
print(chain.maps) # [{'a': 1, 'b': 2}, {'b': 3, 'c': 4}]
# 2. parents 속성 - 첫 번째 맵을 제외한 ChainMap 반환
print(chain.parents) # ChainMap({'b': 3, 'c': 4})
print(chain.parents.maps) # [{'b': 3, 'c': 4}]
4.2. 주요 메서드
ChainMap의 주요 메서드와 각 기능.
new_child(m=None)
: 새 맵을 체인 앞쪽에 추가한 새ChainMap
반환. 인자 없이 호출 시 빈 딕셔너리 추가.keys()
,items()
,values()
: 중복이 제거된 모든 키/항목/값의 뷰 객체 반환. 첫 번째 발견된 값만 포함.get(key, default=None)
: 키가 존재하면 해당 값 반환, 없으면 지정된 기본값 반환.pop(key)
: 첫 번째 맵에서 키 제거 후 해당 값 반환. 첫 번째 맵에 키가 없으면 KeyError 발생.popitem()
: 첫 번째 맵에서 임의의 키-값 쌍 제거 후 반환. 첫 번째 맵이 비어있으면 KeyError 발생.clear()
: 첫 번째 맵의 모든 항목 제거. 다른 맵들은 영향 없음.
5. ChainMap 실제 사용 사례
5.1. 계층적 구성 관리
애플리케이션 설정을 여러 계층으로 관리하는 방법.
- 기본 설정: 애플리케이션의 기본값 제공. 가장 낮은 우선순위.
- 환경 변수: 시스템 환경에 따른 설정 제공. 중간 우선순위.
- 명령줄 인수: 사용자가 직접 지정한 설정. 가장 높은 우선순위.
- ChainMap으로 세 계층을 결합하여 통합된 설정 뷰 제공.
- 각 설정 소스의 무결성 유지하면서 우선순위에 따른 값 탐색 가능.
from collections import ChainMap
import os
import argparse
# 1. 명령줄 인수 파싱
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--debug', dest='debug', action='store_true', default=False)
parser.add_argument('--verbose', dest='verbose', action='store_true', default=False)
return parser.parse_args()
# 2. 설정 계층 구성
def get_config():
# 기본 설정
defaults = {
'debug': False,
'verbose': False,
'host': 'localhost',
'port': 8000,
'timeout': 30
}
# 환경 변수에서 설정 (환경 변수 이름을 대문자로 변환)
env = {k.lower(): v for k, v in os.environ.items()
if k.lower() in defaults}
# 명령줄 인수에서 설정
args = vars(parse_args())
args = {k: v for k, v in args.items() if v is not None}
# ChainMap으로 통합 (우선순위: 명령줄 > 환경 변수 > 기본값)
return ChainMap(args, env, defaults)
# 3. 사용 예시
# config = get_config()
# print(f"Debug mode: {config['debug']}")
# print(f"Server running at {config['host']}:{config['port']} with {config['timeout']}s timeout")
5.2. 중첩된 스코프 구현
프로그래밍 언어의 스코프 체인 구현 방법.
- 전역 스코프: 모든 코드에서 접근 가능한 변수 저장. 가장 바깥쪽 스코프.
- 외부 함수 스코프: 외부 함수 내에서 정의된 변수 저장. 중간 스코프.
- 내부 함수 스코프: 가장 안쪽 함수에서 정의된 변수 저장. 가장 안쪽 스코프.
ChainMap으로 이러한 스코프를 계층적으로 연결하여 변수 탐색 구현.
실제 프로그래밍 언어의 변수 검색 메커니즘과 유사한 방식으로 작동.
from collections import ChainMap
# 간단한 심볼 테이블 스코프 구현
def create_scope_chain():
# 전역 스코프 정의
global_scope = {'x': 10, 'y': 20, 'print': print}
# 외부 함수 스코프
outer_scope = {'x': 30, 'z': 40}
# 내부 함수 스코프
inner_scope = {'x': 50, 'w': 60}
# 스코프 체인 생성 (안쪽 스코프가 우선순위 높음)
return ChainMap(inner_scope, outer_scope, global_scope)
# 변수 조회 예시
scope = create_scope_chain()
print(f"x: {scope['x']}") # 50 (inner_scope)
print(f"y: {scope['y']}") # 20 (global_scope)
print(f"z: {scope['z']}") # 40 (outer_scope)
print(f"w: {scope['w']}") # 60 (inner_scope)
# 스코프 체인에서 심볼 찾기
def find_symbol(symbol, scope_chain):
if symbol in scope_chain:
value = scope_chain[symbol]
# 어느 스코프에서 찾았는지 확인
for i, scope in enumerate(scope_chain.maps):
if symbol in scope:
scope_name = ['inner', 'outer', 'global'][i]
return f"'{symbol}' found in {scope_name} scope with value {value}"
return f"'{symbol}' not found in any scope"
print(find_symbol('x', scope)) # 'x' found in inner scope with value 50
print(find_symbol('y', scope)) # 'y' found in global scope with value 20
5.3. 컨텍스트 관리
임시적인 변경을 격리하는 컨텍스트 관리 패턴 구현.
- 기본 설정: 애플리케이션의 기본 상태 저장.
- 임시 설정: 특정 컨텍스트에서만 적용되는 임시 변경사항 저장.
- ChainMap의 new_child()를 활용하여 임시 컨텍스트 생성.
- 컨텍스트 종료 시 이전 상태로 복원하는 메커니즘 구현.
- 격리된 환경에서의 안전한 변경 및 원래 상태 보존 가능.
from collections import ChainMap
import contextlib
class SettingsContext:
def __init__(self):
self.settings = ChainMap({}) # 기본 빈 맵으로 시작
@contextlib.contextmanager
def temp_settings(self, **kwargs):
# 새로운 설정 맵을 체인의 앞에 추가
new_settings = self.settings.new_child(kwargs)
old_settings = self.settings
self.settings = new_settings
try:
yield self.settings
finally:
# 컨텍스트 종료 시 이전 설정으로 복원
self.settings = old_settings
def get(self, key, default=None):
return self.settings.get(key, default)
def __getitem__(self, key):
return self.settings[key]
def __setitem__(self, key, value):
self.settings.maps[0][key] = value
# 사용 예시
context = SettingsContext()
context['theme'] = 'light'
context['font_size'] = 12
print(f"기본 설정 - 테마: {context['theme']}, 폰트 크기: {context['font_size']}")
# 임시 설정 변경
with context.temp_settings(theme='dark', debug=True):
print(f"임시 설정 - 테마: {context['theme']}, 폰트 크기: {context['font_size']}, 디버그: {context.get('debug')}")
# 원래 설정으로 복원
print(f"복원된 설정 - 테마: {context['theme']}, 폰트 크기: {context['font_size']}")
print(f"디버그 설정 존재?: {'debug' in context.settings}")
6. ChainMap vs 다른 자료구조 비교
6.1. ChainMap vs 딕셔너리 병합
ChainMap과 딕셔너리 병합 방식의 비교.
- 병합 방식: 딕셔너리 병합은 모든 키-값 쌍을 새 딕셔너리로 복사하는 과정.
- 생성 속도: ChainMap은 참조만 저장하므로 병합보다 빠른 생성 속도.
- 메모리 사용: ChainMap은 원본 딕셔너리 참조만 유지하여 메모리 효율성 우수.
- 동적 업데이트: 원본 딕셔너리 변경 시 ChainMap에 자동 반영, 병합은 재병합 필요.
- 키 조회 성능: 병합된 딕셔너리는 단일 해시 테이블 조회로 더 빠른 키 접근 가능.
- 대규모 데이터 처리 시 ChainMap의 생성 속도가 병합보다 수십에서 수백 배 빠른 성능 차이.
6.2. ChainMap vs 중첩 사전(Nested Dictionary)
ChainMap과 중첩 딕셔너리 구조의 비교.
- 데이터 구조: 중첩 딕셔너리는 계층적 트리 구조, ChainMap은 평면적 맵의 연결 구조.
- 키 접근 방식: 중첩 딕셔너리는 다중 레벨 접근(
dict[section][key]
), ChainMap은 단일 레벨 접근(chain[key]
). - 우선순위 처리: 중첩 딕셔너리는 명시적 우선순위 함수 필요, ChainMap은 자동 우선순위 처리.
- 동적 업데이트: 두 방식 모두 원본 딕셔너리 변경 시 반영 가능.
- 코드 간결성: ChainMap이 보다 깔끔하고 직관적인 코드 작성 가능.
- 유지보수성: ChainMap 방식이 로직 분리와 명확한 우선순위 체계로 더 나은 유지보수성 제공.
7. 정리: ChainMap의 장점과 한계
7.1. 장점:
- 여러 딕셔너리의 통합된 뷰 제공
- 원본 딕셔너리 변경 시 자동 반영
- 딕셔너리 병합보다 빠른 생성 속도
- 계층적 데이터 관리에 이상적
- 메모리 효율성 (데이터 복사 없음)
7.2. 한계:
- 키 중복 시 첫 번째 맵의 값만 사용 가능
- 직접 수정 시 첫 번째 맵에만 적용
- 딕셔너리 병합보다 키 조회가 약간 느릴 수 있음
- 딕셔너리만큼 익숙하지 않은 API
8. 마무리
ChainMap
은 여러 딕셔너리를 논리적으로 결합하여 단일 매핑처럼 사용할 수 있게 해주는 유용한 자료구조.- 특히 설정 관리, 스코프 체인 구현, 계층적 데이터 작업에 매우 적합.
- 원본 매핑의 무결성을 유지하면서 동적으로 변화하는 다중 계층 데이터를 효율적으로 처리하는데 큰 장점 보유.
단순히 딕셔너리를 병합하는 경우에는 일반 딕셔너리 연산이 더 직관적일 수 있으나,
동적이고 계층적인 데이터 구조를 다룰 때는 ChainMap
이 보다 나을 수 있음.
같이보면 좋은 자료들
2025.04.04 - [Python] - [Py] collections 모듈 (summary) - 작성중
[Py] collections 모듈 (summary) - 작성중
Python의 collections 모듈은 파이썬의 built-in 자료구조를 확장한 special container 클래스들을 제공함.1. Counter요소의 개수를 세는 dictionary의 subclass.해시 가능한 객체의 카운트를 저장함.from collections impor
ds31x.tistory.com
'Python' 카테고리의 다른 글
[Py] 연습문제-carriage return + time.sleep (0) | 2025.04.07 |
---|---|
[OpenCV] macOS에서 Qt 지원하도록 빌드. (0) | 2025.04.05 |
[Py] collections.namedtuple-Factory Function (0) | 2025.04.04 |
[Py] collection.OrderedDict (0) | 2025.04.04 |
[Py] collections 모듈 (summary) - 작성중 (0) | 2025.04.04 |