10.Python config.json 파일 읽고 저장하는 방법 — 설정 파일 관리 완벽 가이드
저도 처음엔 데이터베이스 주소나 결과물 저장 경로를 소스 코드 상단에 변수로 적어두고 썼습니다. 개발할 때는 로컬 경로로 바꿨다가, 서버에 올릴 때는 다시 운영 서버 경로로 바꾸는 작업을 매번 수동으로 진행했죠. 그러다 어느 날 단 하나의 경로를 미처 고치지 않고 배포하는 바람에, 실제 운영 데이터가 로컬 테스트 데이터에 덮어써 지는 대형 사고를 쳤습니다. 수습하느라 주말 16시간을 통째로 날린 뒤, 설정값은 무조건 코드와 분리해야 한다는 걸 깨닫고 config.json을 도입했습니다.
Python 프로젝트에서 config.json 파일을 활용한 설정 관리는 코드의 유연성과 유지보수성을 크게 향상시킵니다. 이 글에서는 JSON 파일로 설정을 읽고 저장하는 기본부터, 중첩 구조 처리, 기본값 관리, 그리고 .env 파일과의 역할 분리까지 — VS Code 환경에서 실무에 바로 쓸 수 있는 수준으로 안내해 드리겠습니다.
config.json이 필요한 이유 — 코드와 설정을 분리하는 것
설정값을 코드에 직접 넣으면 생기는 문제
프로그램을 개발하다 보면 자주 바뀌는 값들이 생깁니다. API 서버 주소, 최대 요청 횟수, 파일 저장 경로, 알림 조건 수치 같은 것들입니다. 이 값들을 코드 안에 직접 넣으면 값 하나를 바꿀 때마다 코드를 열고, 수정하고, 저장하고, 다시 실행해야 합니다.
이를 레스토랑에 비유하면 이렇습니다. 메뉴판(config.json)은 따로 있고, 요리사(코드)는 메뉴판을 보고 요리합니다. 메뉴를 바꿀 때 요리사의 머릿속(코드)을 바꾸는 것이 아니라 메뉴판(설정 파일)만 바꾸면 됩니다. 코드는 건드리지 않아도 프로그램의 동작이 달라집니다.
.env 파일과 config.json의 역할 차이
두 파일을 함께 쓰는 경우가 많아 혼동하기 쉽습니다. 역할은 명확히 다릅니다.
| 구분 | .env 파일 | config.json |
|---|---|---|
| 용도 | 비밀 정보 (API Key, 비밀번호) | 일반 설정값 (경로, 수치, 옵션) |
| 깃허브 공개 | ❌ 절대 불가 | ✅ 공개 가능 |
| 형식 | KEY=VALUE | JSON 구조체 |
| 팀 공유 | .env.example로 간접 공유 | 파일 그대로 공유 |
비밀이 필요 없는 설정값은 config.json으로, API Key처럼 외부에 노출되면 안 되는 값은 .env로 관리하는 것이 원칙입니다.
config.json 파일 기본 구조 만들기
파일 생성 및 구조 설계
VS Code에서 프로젝트 루트 폴더에 config.json 파일을 새로 만들고 아래처럼 작성합니다.
{
"app": {
"name": "MyProject",
"version": "1.0.0",
"debug": false
},
"api": {
"base_url": "https://api.example.com",
"timeout": 30,
"max_retries": 3
},
"paths": {
"output_dir": "./output",
"log_dir": "./logs",
"data_dir": "./data"
},
"alert": {
"price_change_threshold": 5.0,
"volume_multiplier": 3.0,
"cooldown_minutes": 60
}
}
JSON은 계층 구조를 그대로 시각화할 수 있어 설정값이 많아져도 분류가 명확합니다. 마치 서랍장에 칸을 나눠 물건을 정리하는 것처럼, 관련 있는 설정끼리 묶어두면 나중에 찾기 쉽습니다.
Python에서 config.json 읽기
기본 읽기 — json 모듈 활용
Python 기본 라이브러리인 json 모듈만으로 충분합니다. 별도 설치가 필요 없습니다.
import json
import os
from pathlib import Path
def load_config(config_path: str = "config.json") -> dict:
"""config.json 파일을 읽어 딕셔너리로 반환합니다."""
# 스크립트 파일 기준으로 경로 설정 (경로 오류 방지)
base_dir = Path(__file__).parent
full_path = base_dir / config_path
if not full_path.exists():
raise FileNotFoundError(
f"설정 파일을 찾을 수 없습니다: {full_path}\n"
f"프로젝트 루트에 config.json 파일을 생성해 주세요."
)
with open(full_path, "r", encoding="utf-8") as f:
config = json.load(f)
print(f"✅ 설정 파일 로드 완료: {full_path}")
return config
# 사용 예시
config = load_config()
# 중첩 구조 접근
app_name = config["app"]["name"]
timeout = config["api"]["timeout"]
output_dir = config["paths"]["output_dir"]
print(f"앱 이름: {app_name}")
print(f"타임아웃: {timeout}초")
print(f"출력 경로: {output_dir}")
.get()으로 안전하게 읽기 — KeyError 방지
딕셔너리에서 존재하지 않는 키를 접근하면 KeyError가 발생합니다. .get() 메서드를 사용하면 키가 없을 때 기본값을 반환해 프로그램이 멈추는 것을 방지합니다.
config = load_config()
# 키가 없으면 오류 발생 — 위험한 방식
# timeout = config["api"]["timeout"]
# 키가 없으면 기본값 반환 — 안전한 방식
timeout = config.get("api", {}).get("timeout", 30)
debug = config.get("app", {}).get("debug", False)
threshold = config.get("alert", {}).get("price_change_threshold", 3.0)
print(f"타임아웃: {timeout}초")
print(f"디버그 모드: {debug}")
print(f"가격 변동 임계값: {threshold}%")
Python에서 config.json 저장하기
설정값을 사용자가 변경하거나, 프로그램 실행 중에 상태를 저장해야 할 때 JSON 파일에 다시 쓸 수 있습니다.
기본 저장
import json
from pathlib import Path
def save_config(config: dict, config_path: str = "config.json") -> None:
"""딕셔너리를 config.json 파일로 저장합니다."""
base_dir = Path(__file__).parent
full_path = base_dir / config_path
with open(full_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=2)
print(f"✅ 설정 파일 저장 완료: {full_path}")
# 사용 예시 — 특정 값만 변경 후 저장
config = load_config()
config["alert"]["price_change_threshold"] = 7.0 # 임계값 변경
config["app"]["version"] = "1.1.0" # 버전 업데이트
save_config(config)
💡
ensure_ascii=False: 한글이 포함된 경우 이 옵션 없이 저장하면\uD55C\uAE00같은 유니코드 이스케이프로 저장됩니다. 한글을 그대로 저장하려면 반드시ensure_ascii=False를 추가하십시오.
💡
indent=2: 이 옵션을 주면 들여쓰기가 적용된 읽기 좋은 형태로 저장됩니다. 없으면 한 줄로 압축되어 저장됩니다.
실전 패턴 — ConfigManager 클래스로 통합 관리하기
읽기와 쓰기를 매번 따로 호출하는 대신, 클래스로 묶어두면 프로젝트 어디서든 일관되게 사용할 수 있습니다.
import json
from pathlib import Path
from typing import Any
class ConfigManager:
"""config.json 파일을 읽고 쓰는 기능을 통합 관리하는 클래스입니다."""
def __init__(self, config_path: str = "config.json"):
self.config_path = Path(__file__).parent / config_path
self._config = self._load()
def _load(self) -> dict:
"""파일에서 설정을 읽어옵니다. 파일이 없으면 빈 딕셔너리를 반환합니다."""
if not self.config_path.exists():
print(f"⚠️ 설정 파일 없음. 기본값으로 시작합니다: {self.config_path}")
return {}
with open(self.config_path, "r", encoding="utf-8") as f:
return json.load(f)
def save(self) -> None:
"""현재 설정을 파일로 저장합니다."""
with open(self.config_path, "w", encoding="utf-8") as f:
json.dump(self._config, f, ensure_ascii=False, indent=2)
print(f"✅ 설정 저장 완료")
def get(self, *keys: str, default: Any = None) -> Any:
"""중첩 키를 순서대로 전달해 값을 가져옵니다."""
value = self._config
for key in keys:
if not isinstance(value, dict):
return default
value = value.get(key, default)
if value is default:
return default
return value
def set(self, *keys: str, value: Any) -> None:
"""중첩 키를 순서대로 전달해 값을 설정합니다."""
target = self._config
for key in keys[:-1]:
target = target.setdefault(key, {})
target[keys[-1]] = value
def reload(self) -> None:
"""파일에서 설정을 다시 읽어옵니다."""
self._config = self._load()
print("🔄 설정 파일 다시 로드 완료")
# 실제 사용 예시
cfg = ConfigManager()
# 값 읽기
timeout = cfg.get("api", "timeout", default=30)
threshold = cfg.get("alert", "price_change_threshold", default=5.0)
print(f"타임아웃: {timeout}초")
print(f"임계값: {threshold}%")
# 값 변경 및 저장
cfg.set("alert", "price_change_threshold", value=7.5)
cfg.set("app", "version", value="1.2.0")
cfg.save()
기본값(default config) 안전하게 관리하기
설정 파일이 없거나 특정 키가 빠진 경우를 대비해 기본값을 코드에 정의해 두는 패턴입니다.
import json
from pathlib import Path
# 기본값 정의 — 설정 파일이 없거나 키가 빠진 경우 사용
DEFAULT_CONFIG = {
"app": {
"name": "MyProject",
"version": "1.0.0",
"debug": False
},
"api": {
"base_url": "https://api.example.com",
"timeout": 30,
"max_retries": 3
},
"alert": {
"price_change_threshold": 5.0,
"cooldown_minutes": 60
}
}
def load_config_with_defaults(config_path: str = "config.json") -> dict:
"""설정 파일을 읽고, 없는 키는 기본값으로 채웁니다."""
config = DEFAULT_CONFIG.copy()
path = Path(__file__).parent / config_path
if path.exists():
with open(path, "r", encoding="utf-8") as f:
user_config = json.load(f)
# 사용자 설정으로 기본값을 덮어씁니다 (중첩 딕셔너리 병합)
for section, values in user_config.items():
if section in config and isinstance(values, dict):
config[section].update(values)
else:
config[section] = values
return config
자주 발생하는 오류와 해결법
오류 1 — json.decoder.JSONDecodeError 발생
원인: JSON 문법 오류 (쉼표 누락, 따옴표 불일치, 주석 포함 등)
해결: VS Code에서 JSON 파일을 열면 문법 오류 위치를 빨간 밑줄로 표시해 줍니다. 주석(//)은 JSON 표준에서 지원되지 않으므로 제거하십시오.
# 오류 위치 파악을 위한 예외 처리
try:
with open("config.json", "r", encoding="utf-8") as f:
config = json.load(f)
except json.JSONDecodeError as e:
print(f"JSON 문법 오류: {e.msg}")
print(f"오류 위치: {e.lineno}번째 줄, {e.colno}번째 열")
오류 2 — 한글이 \uXXXX 형태로 저장되는 경우
원인: ensure_ascii=True (기본값)로 저장된 경우
해결: 저장 시 ensure_ascii=False 추가
오류 3 — FileNotFoundError — 파일을 찾을 수 없는 경우
원인: 작업 디렉토리와 파일 위치 불일치
해결: Path(__file__).parent를 기준으로 절대 경로 사용 (위 예제 코드 참고)
마무리 — 핵심 요약
✅ Python config.json 관리 체크리스트
- 비밀값은
.env, 일반 설정값은config.json으로 역할 분리 json.load()로 읽기,json.dump()로 저장ensure_ascii=False— 한글 깨짐 방지 필수 옵션indent=2— 사람이 읽기 좋은 형태로 저장.get()메서드 또는ConfigManager클래스 로 안전하게 접근DEFAULT_CONFIG로 기본값을 미리 정의해 빠진 키에 대비Path(__file__).parent기준으로 경로 설정해FileNotFoundError방지
다음으로 알아두면 좋은 것
config.json에 익숙해졌다면, 더 복잡한 설정 관리가 필요한 시점에 YAML 형식(PyYAML 라이브러리)을 검토해 보시길 권장합니다. YAML은 주석을 지원하고 들여쓰기로 계층을 표현해 JSON보다 사람이 읽고 쓰기 편리합니다. 특히 Django, FastAPI 같은 프레임워크 기반 프로젝트에서는 YAML 설정 파일이 자주 활용됩니다.
댓글
댓글 쓰기