[파이썬 100강] 84강. pprint로 중첩 데이터를 읽기 좋게 출력해 디버깅 속도 높이기
리스트 안에 딕셔너리, 그 안에 또 리스트가 들어간 데이터를 print()로 찍으면 한 줄로 길게 붙어서 눈이 바로 포기하게 됩니다. 실무에서는 API 응답, 설정 객체, 로그 컨텍스트처럼 중첩 구조를 매일 다루기 때문에, “잘 보이게 출력하는 기술”이 디버깅 시간을 크게 줄입니다. 이번 강의에서는 pprint 모듈을 사용해 데이터를 사람이 읽기 좋은 형태로 정리 출력하고, 운영 로그에 안전하게 적용하는 방법까지 정리합니다.
핵심 개념
pprint.pprint()는 파이썬 객체를 보기 좋은 들여쓰기 형태로 출력합니다.pprint.pp()는 3.8+에서 사용할 수 있는 간단한 별칭이며, 실무에서 빠르게 쓰기 좋습니다.pprint.pformat()은 문자열을 반환하므로 로그/파일 저장/메시지 전송에 유리합니다.width,indent,depth,sort_dicts,compact같은 옵션으로 출력 스타일을 제어할 수 있습니다.- 보기 좋은 출력은 기능이 아니라 디버깅 생산성 도구입니다. 데이터를 잘못 읽는 실수를 줄여 줍니다.
print()는 객체를 기본 표현(repr)으로 보여 줍니다. 구조가 단순할 때는 충분하지만, 중첩이 깊거나 항목이 길면 한 줄이 매우 길어져서 핵심 값을 찾기 어렵습니다. 이때 pprint는 줄바꿈과 들여쓰기를 자동으로 넣어 사람이 패턴을 인식하기 쉽게 만들어 줍니다.
중요한 점은 pprint가 데이터를 바꾸지 않는다는 것입니다. 오직 “표현 방식”만 바꿉니다. 그래서 비즈니스 로직에 영향 없이 디버깅 코드로 넣었다가 안전하게 제거하거나, 개발 환경에서만 켜는 방식으로 운영할 수 있습니다. 또한 pformat()으로 문자열을 만든 뒤 로거에 넘기면, 추적 로그의 품질이 크게 좋아집니다.
기본 사용
예제 1) print()와 pprint() 비교
>>> from pprint import pprint
>>> payload = {
... "user": {"id": 101, "name": "Geonwoo", "roles": ["admin", "editor"]},
... "orders": [
... {"id": "A-1", "price": 12000, "items": ["keyboard", "cable"]},
... {"id": "A-2", "price": 35000, "items": ["monitor"]},
... ],
... }
>>> print(payload)
{'user': {'id': 101, 'name': 'Geonwoo', 'roles': ['admin', 'editor']}, 'orders': [{'id': 'A-1', 'price': 12000, 'items': ['keyboard', 'cable']}, {'id': 'A-2', 'price': 35000, 'items': ['monitor']}]}
>>> pprint(payload)
{'orders': [{'id': 'A-1', 'items': ['keyboard', 'cable'], 'price': 12000},
{'id': 'A-2', 'items': ['monitor'], 'price': 35000}],
'user': {'id': 101, 'name': 'Geonwoo', 'roles': ['admin', 'editor']}}
해설:
- 같은 데이터인데
pprint는 줄바꿈/들여쓰기를 적용해 읽기 쉬워집니다. - 눈으로 구조를 파악하는 시간이 줄어들어, 버그 위치를 빨리 찾을 수 있습니다.
예제 2) 폭(width)과 정렬 옵션 조절
>>> from pprint import pprint
>>> data = {
... "service": "billing",
... "flags": {"debug": True, "safe_mode": False, "trace": True},
... "regions": ["ap-northeast-2", "us-west-2", "eu-central-1"],
... }
>>> pprint(data, width=50, sort_dicts=False)
{'service': 'billing',
'flags': {'debug': True,
'safe_mode': False,
'trace': True},
'regions': ['ap-northeast-2',
'us-west-2',
'eu-central-1']}
해설:
width를 줄이면 줄바꿈이 적극적으로 적용되어 좁은 터미널에서도 읽기 좋습니다.sort_dicts=False로 원래 키 순서를 유지하면, 입력 순서가 의미 있는 데이터에서 이해가 쉬워집니다.
예제 3) pformat()으로 로그 문자열 만들기
>>> from pprint import pformat
>>> context = {
... "request_id": "req-20260217-001",
... "user": {"id": 7, "tier": "pro"},
... "cart": [{"sku": "KB-01", "qty": 2}, {"sku": "MS-09", "qty": 1}],
... }
>>> message = pformat(context, width=60)
>>> print("DEBUG payload:\n" + message)
DEBUG payload:
{'cart': [{'qty': 2, 'sku': 'KB-01'}, {'qty': 1, 'sku': 'MS-09'}],
'request_id': 'req-20260217-001',
'user': {'id': 7, 'tier': 'pro'}}
해설:
pformat()은 반환값이 문자열이라서 로거, 파일 저장, 알림 메시지에 그대로 연결할 수 있습니다.- “출력”과 “전송”을 분리할 수 있어 유지보수가 쉽습니다.
예제 4) 깊이 제한(depth)으로 과도한 출력 방지
>>> from pprint import pprint
>>> deep = {"a": {"b": {"c": {"d": {"e": 12345}}}}}
>>> pprint(deep, depth=3)
{'a': {'b': {'c': {...}}}}
해설:
- 구조가 너무 깊으면 로그가 길어져 핵심을 놓치기 쉽습니다.
depth를 제한하면 필요한 레벨까지만 보여 줘서 운영 로그가 깔끔해집니다.
자주 하는 실수
실수 1) print() 한 줄 출력만 고집해 구조를 잘못 해석
>>> payload = {'result': [{'ok': True, 'meta': {'retry': 0, 'source': 'cache'}}]}
>>> print(payload)
{'result': [{'ok': True, 'meta': {'retry': 0, 'source': 'cache'}}]}
원인:
- 길게 붙은 텍스트에서 괄호 짝/중첩 레벨을 눈으로 빠르게 파악하기 어렵습니다.
- 특히 장애 상황에서 급하게 보면 필드 위치를 오해하기 쉽습니다.
해결:
>>> from pprint import pprint
>>> pprint(payload)
{'result': [{'meta': {'retry': 0, 'source': 'cache'}, 'ok': True}]}
- 구조를 먼저 정확히 읽고, 그다음 조건 분기나 인덱싱 코드를 작성합니다.
실수 2) 운영 환경에서 전체 객체를 그대로 pprint해 로그 폭증
- 증상: 로그 용량 급증, 검색 속도 저하, 민감정보 노출 위험 증가
- 원인: 디버깅 편의 때문에 대용량/전체 payload를 무차별 출력
- 해결:
- 필요한 키만 추려
subset = {"id": x["id"], "status": x["status"]}처럼 축소 depth,width를 제한하고, 개발/운영 로그 레벨을 분리- 개인정보 필드는 마스킹 후 출력
- 필요한 키만 추려
실수 3) 딕셔너리 순서를 기대하면서 sort_dicts=True 기본값을 인지 못함
>>> from pprint import pprint
>>> cfg = {"z": 1, "a": 2, "m": 3}
>>> pprint(cfg)
{'a': 2, 'm': 3, 'z': 1}
원인:
- 기본값에서 키가 정렬되어, 입력 순서를 기대한 사람에게 혼란을 줍니다.
해결:
>>> pprint(cfg, sort_dicts=False)
{'z': 1, 'a': 2, 'm': 3}
- “원본 순서 보존”이 중요한 데이터라면 옵션을 명시적으로 넣어 습관화합니다.
실무 패턴
-
입력 검증 규칙
- 디버깅 출력 전에 데이터 크기(리스트 길이, 문자열 길이)를 체크해 과도한 로그를 방지합니다.
- 민감 필드(
token,email,phone)는 마스킹 함수를 거친 뒤 출력합니다.
-
로그/예외 처리 규칙
- 예외 발생 시 전체 객체 대신 “핵심 컨텍스트 + pformat 결과”를 함께 남깁니다.
- 예:
logger.error("checkout failed: %s", pformat(debug_context, width=80))
-
재사용 함수/구조화 팁
- 팀 공용
pretty_debug(obj, *, depth=4)유틸을 만들어 출력 정책을 통일합니다. - 개발 환경에서는 상세 출력, 운영 환경에서는 축약 출력으로 분기합니다.
- 팀 공용
-
성능/메모리 체크 포인트
pprint는 시각화 도구이므로 핫패스(초당 대량 처리 루프)에서 남발하지 않습니다.- 반복문 내부보다 오류 케이스나 샘플링 구간에서만 사용해 비용을 줄입니다.
오늘의 결론
한 줄 요약: pprint는 데이터를 바꾸지 않으면서 디버깅 해상도를 올려 주는 가장 저비용 도구다.
기억할 것:
print()가 답답해지는 순간,pprint()로 바로 전환하세요.- 로그에 남길 때는
pformat()+ 필드 축소 + 마스킹을 같이 적용하세요. - 운영에서는 보기 좋음보다 안전성과 비용 통제가 먼저입니다.
연습문제
- 중첩 딕셔너리/리스트 구조를 하나 만들고,
print()와pprint()결과를 비교해 어떤 값이 더 빨리 눈에 들어오는지 기록해 보세요. pformat()으로 생성한 문자열을debug.log파일에 저장하는 코드를 작성해 보세요. 단,token키는"***"로 마스킹하세요.- 같은 데이터에 대해
sort_dicts=True/False를 각각 적용해 출력 차이를 확인하고, 어떤 상황에서 각 옵션이 유리한지 설명해 보세요.
이전 강의 정답
- 얕은 복사에서 중첩 딕셔너리가 왜 함께 바뀌는지 확인
>>> import copy
>>> settings = {"mode": "prod", "db": {"host": "127.0.0.1"}}
>>> cloned = copy.copy(settings)
>>> cloned["db"]["host"] = "10.0.0.5"
>>> settings
{'mode': 'prod', 'db': {'host': '10.0.0.5'}}
설명: copy.copy()는 바깥 dict만 복사하므로 내부 db dict는 원본과 공유됩니다.
- 주문 템플릿을 깊은 복사해서 원본 보존 확인
>>> import copy
>>> order_template = {"items": [{"sku": "A", "qty": 1}], "meta": {"priority": "normal"}}
>>> order1 = copy.deepcopy(order_template)
>>> order1["items"].append({"sku": "B", "qty": 2})
>>> order1["meta"]["priority"] = "high"
>>> order_template
{'items': [{'sku': 'A', 'qty': 1}], 'meta': {'priority': 'normal'}}
설명: deepcopy()는 중첩 객체까지 분리해서 원본이 오염되지 않습니다.
deepcopyvs 부분 재구성 성능 비교 예시
>>> import copy, time
>>> data = [{"id": i, "meta": {"flag": True, "score": i % 10}} for i in range(20000)]
>>> t0 = time.perf_counter(); a = copy.deepcopy(data); t1 = time.perf_counter()
>>> t2 = time.perf_counter(); b = [{"id": x["id"], "meta": {"flag": x["meta"]["flag"]}} for x in data]; t3 = time.perf_counter()
>>> round(t1 - t0, 4), round(t3 - t2, 4)
(0.08, 0.021)
설명: 데이터 구조에 따라 다르지만, 필요한 필드만 재구성하는 방식이 더 빠를 수 있습니다.
실습 환경/재현 정보
- 실행 환경:
condaenvpython100(Python 3.11.14) - 가정한 OS: macOS/Linux 공통
- 사용 모듈:
pprint(pprint,pp,pformat) - 재현 팁:
- 터미널 너비가 다르면 줄바꿈 형태가 달라질 수 있음
width,depth,sort_dicts를 바꿔가며 출력 변화를 비교- 운영 로그에서는 민감정보 마스킹을 먼저 적용