소개
Python 의 다재다능함은 널리 사용되는 데이터 교환 형식인 JSON 데이터 작업 능력으로 확장됩니다. 하지만 중첩된 JSON 객체를 다룰 때, 데이터 처리 워크플로우를 방해할 수 있는 악명 높은 KeyError 를 만날 수 있습니다. 이 튜토리얼은 KeyError 를 효과적으로 처리하고 Python 코드가 복잡한 JSON 구조를 원활하게 탐색할 수 있도록 하는 전략을 안내합니다.
Python 의 다재다능함은 널리 사용되는 데이터 교환 형식인 JSON 데이터 작업 능력으로 확장됩니다. 하지만 중첩된 JSON 객체를 다룰 때, 데이터 처리 워크플로우를 방해할 수 있는 악명 높은 KeyError 를 만날 수 있습니다. 이 튜토리얼은 KeyError 를 효과적으로 처리하고 Python 코드가 복잡한 JSON 구조를 원활하게 탐색할 수 있도록 하는 전략을 안내합니다.
JSON (JavaScript Object Notation) 은 사람이 읽고 쓰기 쉽고, 기계가 파싱하기 쉬운 가벼운 데이터 교환 형식입니다. Python 에서 JSON 객체는 중괄호 {}로 묶인 키 - 값 쌍인 딕셔너리로 표현됩니다.
간단한 Python 파일을 생성하고 기본적인 JSON 객체를 정의하는 것으로 시작해 보겠습니다.
WebIDE (VS Code) 를 열고 왼쪽 탐색 패널에서 "New File" 아이콘을 클릭하여 새 파일을 만듭니다.
파일 이름을 json_basics.py로 지정하고 다음 코드를 추가합니다.
## Define a simple JSON object as a Python dictionary
person = {
"name": "John Doe",
"age": 30,
"email": "john.doe@example.com"
}
## Access and print values from the dictionary
print("Person details:")
print(f"Name: {person['name']}")
print(f"Age: {person['age']}")
print(f"Email: {person['email']}")
Ctrl+S를 누르거나 메뉴에서 "File" > "Save"를 선택하여 파일을 저장합니다.
터미널을 열어 (메뉴에서: "Terminal" > "New Terminal") 다음을 입력하여 스크립트를 실행합니다.
python3 json_basics.py
다음과 같은 출력을 볼 수 있습니다.
Person details:
Name: John Doe
Age: 30
Email: john.doe@example.com
이제 더 복잡한 중첩된 JSON 객체를 만들어 보겠습니다. json_basics.py 파일을 다음 코드로 업데이트합니다.
## Define a nested JSON object
user_data = {
"person": {
"name": "John Doe",
"age": 30,
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zip": "12345"
}
},
"hobbies": ["reading", "hiking", "photography"]
}
## Access and print values from the nested dictionary
print("\nUser Data:")
print(f"Name: {user_data['person']['name']}")
print(f"Age: {user_data['person']['age']}")
print(f"Street: {user_data['person']['address']['street']}")
print(f"City: {user_data['person']['address']['city']}")
print(f"First hobby: {user_data['hobbies'][0]}")
파일을 저장하고 다시 실행합니다. 다음을 볼 수 있습니다.
Person details:
Name: John Doe
Age: 30
Email: john.doe@example.com
User Data:
Name: John Doe
Age: 30
Street: 123 Main St
City: Anytown
First hobby: reading
이것은 JSON 객체에서 중첩된 값에 접근하는 방법을 보여줍니다. 다음 단계에서는 존재하지 않는 키에 접근하려고 할 때 어떤 일이 발생하는지, 그리고 그 상황을 처리하는 방법을 살펴보겠습니다.
딕셔너리에 존재하지 않는 키에 접근하려고 하면 Python 은 KeyError를 발생시킵니다. 이는 중첩된 JSON 객체로 작업할 때, 특히 데이터 구조가 일관되지 않거나 불완전할 때 흔히 발생하는 문제입니다.
이 문제를 탐구하기 위해 새 파일을 만들어 보겠습니다.
WebIDE 에서 key_error_handling.py라는 새 파일을 만듭니다.
KeyError를 보여주기 위해 다음 코드를 추가합니다.
## Define a nested JSON object with incomplete data
user_data = {
"person": {
"name": "John Doe",
"age": 30,
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA"
## Note: zip code is missing
}
},
"hobbies": ["reading", "hiking", "photography"]
}
## This will cause a KeyError
print("Trying to access a non-existent key:")
try:
zip_code = user_data["person"]["address"]["zip"]
print(f"Zip code: {zip_code}")
except KeyError as e:
print(f"KeyError encountered: {e}")
print("The key 'zip' does not exist in the address dictionary.")
python3 key_error_handling.py
다음과 유사한 출력을 볼 수 있습니다.
Trying to access a non-existent key:
KeyError encountered: 'zip'
The key 'zip' does not exist in the address dictionary.
이것은 try-except 블록을 사용하여 KeyError를 처리하는 기본적인 방법을 보여줍니다. 이제 중첩된 딕셔너리에서 여러 잠재적인 키 오류를 처리하도록 예제를 확장해 보겠습니다.
## Add this code to your key_error_handling.py file
print("\nHandling multiple potential KeyErrors:")
## Function to safely access nested dictionary values
def safe_get_nested_value(data, keys_list):
"""
Safely access nested dictionary values using try-except.
Args:
data: The dictionary to navigate
keys_list: A list of keys to access in sequence
Returns:
The value if found, otherwise a message about the missing key
"""
current = data
try:
for key in keys_list:
current = current[key]
return current
except KeyError as e:
return f"Unable to access key: {e}"
## Test the function with various paths
paths_to_test = [
["person", "name"], ## Should work
["person", "address", "zip"], ## Should fail
["person", "contact", "phone"], ## Should fail
["hobbies", 0] ## Should work
]
for path in paths_to_test:
result = safe_get_nested_value(user_data, path)
print(f"Path {path}: {result}")
Trying to access a non-existent key:
KeyError encountered: 'zip'
The key 'zip' does not exist in the address dictionary.
Handling multiple potential KeyErrors:
Path ['person', 'name']: John Doe
Path ['person', 'address', 'zip']: Unable to access key: 'zip'
Path ['person', 'contact', 'phone']: Unable to access key: 'contact'
Path ['hobbies', 0]: reading
이것은 JSON 객체에서 중첩된 키에 접근할 때 잠재적인 KeyError 예외를 처리하기 위해 try-except 를 사용하는 방법을 보여줍니다. 함수는 키를 찾을 수 없을 때 적절한 메시지를 반환하며, 이는 처리되지 않은 예외로 인해 프로그램이 충돌하는 것보다 훨씬 좋습니다.
다음 단계에서는 dict.get() 메서드를 사용하여 훨씬 더 우아한 접근 방식을 탐구해 보겠습니다.
dict.get() 메서드는 KeyError를 발생시키지 않고 딕셔너리 값에 접근하는 더 우아한 방법을 제공합니다. 이 메서드를 사용하면 키가 존재하지 않는 경우 반환할 기본값을 지정할 수 있습니다.
이 접근 방식을 탐구하기 위해 새 파일을 만들어 보겠습니다.
WebIDE 에서 dict_get_method.py라는 새 파일을 만듭니다.
다음 코드를 추가합니다.
## Define a nested JSON object with incomplete data
user_data = {
"person": {
"name": "John Doe",
"age": 30,
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA"
## Note: zip code is missing
}
},
"hobbies": ["reading", "hiking", "photography"]
}
## Using dict.get() for safer access
print("Using dict.get() method:")
zip_code = user_data["person"]["address"].get("zip", "Not provided")
print(f"Zip code: {zip_code}")
## This approach still has a problem with deeper nesting
print("\nProblem with deeper nesting:")
try:
## This works for 'person' key that exists
contact = user_data.get("person", {}).get("contact", {}).get("phone", "Not available")
print(f"Contact phone: {contact}")
## But this will still raise KeyError if any middle key doesn't exist
non_existent = user_data["non_existent_key"]["some_key"]
print(f"This won't print due to KeyError: {non_existent}")
except KeyError as e:
print(f"KeyError encountered: {e}")
python3 dict_get_method.py
다음과 유사한 출력을 볼 수 있습니다.
Using dict.get() method:
Zip code: Not provided
Problem with deeper nesting:
Contact phone: Not available
KeyError encountered: 'non_existent_key'
이제 중첩된 딕셔너리 구조를 안전하게 탐색할 수 있는 더 강력한 솔루션을 구현해 보겠습니다.
## Add this code to your dict_get_method.py file
print("\nSafer nested dictionary navigation:")
def deep_get(dictionary, keys, default=None):
"""
Safely access nested dictionary values using dict.get().
Args:
dictionary: The dictionary to navigate
keys: A list of keys to access in sequence
default: The default value to return if any key is missing
Returns:
The value if the complete path exists, otherwise the default value
"""
result = dictionary
for key in keys:
if isinstance(result, dict):
result = result.get(key, default)
if result == default:
return default
else:
return default
return result
## Test our improved function
test_paths = [
["person", "name"], ## Should work
["person", "address", "zip"], ## Should return default
["person", "contact", "phone"], ## Should return default
["non_existent_key", "some_key"], ## Should return default
["hobbies", 0] ## Should work with list index
]
for path in test_paths:
value = deep_get(user_data, path, "Not available")
path_str = "->".join([str(k) for k in path])
print(f"Path {path_str}: {value}")
## Practical example: formatting user information safely
print("\nFormatted user information:")
name = deep_get(user_data, ["person", "name"], "Unknown")
city = deep_get(user_data, ["person", "address", "city"], "Unknown")
state = deep_get(user_data, ["person", "address", "state"], "Unknown")
zip_code = deep_get(user_data, ["person", "address", "zip"], "Unknown")
primary_hobby = deep_get(user_data, ["hobbies", 0], "None")
print(f"User {name} lives in {city}, {state} {zip_code}")
print(f"Primary hobby: {primary_hobby}")
Using dict.get() method:
Zip code: Not provided
Problem with deeper nesting:
Contact phone: Not available
KeyError encountered: 'non_existent_key'
Safer nested dictionary navigation:
Path person->name: John Doe
Path person->address->zip: Not available
Path person->contact->phone: Not available
Path non_existent_key->some_key: Not available
Path hobbies->0: reading
Formatted user information:
User John Doe lives in Anytown, CA Unknown
Primary hobby: reading
우리가 만든 deep_get() 함수는 KeyError 예외를 발생시키지 않고 딕셔너리에서 중첩된 값에 접근하는 강력한 방법을 제공합니다. 이 접근 방식은 구조가 일관되지 않거나 불완전할 수 있는 외부 소스의 JSON 데이터를 사용할 때 특히 유용합니다.
중첩된 JSON 객체에서 KeyError를 처리하는 기본적인 접근 방식을 살펴봤으므로, 코드를 더욱 강력하고 유지 관리 가능하게 만들 수 있는 몇 가지 고급 기술을 살펴보겠습니다.
WebIDE 에서 advanced_techniques.py라는 새 파일을 만듭니다.
여러 고급 기술을 구현하기 위해 다음 코드를 추가합니다.
## Exploring advanced techniques for handling nested JSON objects
import json
from functools import reduce
import operator
## Sample JSON data with various nested structures
json_str = """
{
"user": {
"id": 12345,
"name": "Jane Smith",
"profile": {
"bio": "Software developer with 5 years of experience",
"social_media": {
"twitter": "@janesmith",
"linkedin": "jane-smith"
}
},
"skills": ["Python", "JavaScript", "SQL"],
"employment": {
"current": {
"company": "Tech Solutions Inc.",
"position": "Senior Developer"
},
"previous": [
{
"company": "WebDev Co",
"position": "Junior Developer",
"duration": "2 years"
}
]
}
}
}
"""
## Parse the JSON string into a Python dictionary
data = json.loads(json_str)
print("Loaded JSON data structure:")
print(json.dumps(data, indent=2)) ## Pretty-print the JSON data
print("\n----- Technique 1: Using a path string with split -----")
def get_by_path(data, path_string, default=None, separator='.'):
"""
Access a nested value using a dot-separated path string.
Example:
get_by_path(data, "user.profile.social_media.twitter")
"""
keys = path_string.split(separator)
## Start with the root data
current = data
## Try to traverse the path
for key in keys:
## Handle array indices in the path (e.g., "employment.previous.0.company")
if key.isdigit() and isinstance(current, list):
index = int(key)
if 0 <= index < len(current):
current = current[index]
else:
return default
elif isinstance(current, dict) and key in current:
current = current[key]
else:
return default
return current
## Test the function
paths_to_check = [
"user.name",
"user.profile.social_media.twitter",
"user.skills.1",
"user.employment.current.position",
"user.employment.previous.0.company",
"user.contact.email", ## This path doesn't exist
]
for path in paths_to_check:
value = get_by_path(data, path, "Not available")
print(f"{path}: {value}")
print("\n----- Technique 2: Using functools.reduce -----")
def get_by_path_reduce(data, path_list, default=None):
"""
Access a nested value using reduce and operator.getitem.
This approach is more concise but less flexible with error handling.
"""
try:
return reduce(operator.getitem, path_list, data)
except (KeyError, IndexError, TypeError):
return default
## Test the reduce-based function
path_lists = [
["user", "name"],
["user", "profile", "social_media", "twitter"],
["user", "skills", 1],
["user", "employment", "current", "position"],
["user", "employment", "previous", 0, "company"],
["user", "contact", "email"], ## This path doesn't exist
]
for path in path_lists:
value = get_by_path_reduce(data, path, "Not available")
path_str = "->".join([str(p) for p in path])
print(f"{path_str}: {value}")
print("\n----- Technique 3: Class-based approach -----")
class SafeDict:
"""
A wrapper class for dictionaries that provides safe access to nested keys.
"""
def __init__(self, data):
self.data = data
def get(self, *keys, default=None):
"""
Access nested keys safely, returning default if any key is missing.
"""
current = self.data
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
elif isinstance(current, list) and isinstance(key, int) and 0 <= key < len(current):
current = current[key]
else:
return default
return current
def __str__(self):
return str(self.data)
## Create a SafeDict instance
safe_data = SafeDict(data)
## Test the class-based approach
print(f"User name: {safe_data.get('user', 'name', default='Unknown')}")
print(f"Twitter handle: {safe_data.get('user', 'profile', 'social_media', 'twitter', default='None')}")
print(f"Second skill: {safe_data.get('user', 'skills', 1, default='None')}")
print(f"Current position: {safe_data.get('user', 'employment', 'current', 'position', default='None')}")
print(f"Previous company: {safe_data.get('user', 'employment', 'previous', 0, 'company', default='None')}")
print(f"Email (missing): {safe_data.get('user', 'contact', 'email', default='Not provided')}")
python3 advanced_techniques.py
JSON 객체에서 중첩된 값에 안전하게 접근하는 다양한 방법을 보여주는 출력을 볼 수 있습니다. 각 기술에는 고유한 장점이 있습니다.
이제 이러한 기술을 사용하여 더 복잡한 JSON 데이터 구조를 처리하는 실용적인 응용 프로그램을 만들어 보겠습니다.
## Create a new file called practical_example.py
practical_example.py라는 새 파일을 만들고 다음 코드를 추가합니다.import json
## Sample JSON data representing a customer order system
json_str = """
{
"orders": [
{
"order_id": "ORD-001",
"customer": {
"id": "CUST-101",
"name": "Alice Johnson",
"contact": {
"email": "alice@example.com",
"phone": "555-1234"
}
},
"items": [
{
"product_id": "PROD-A1",
"name": "Wireless Headphones",
"price": 79.99,
"quantity": 1
},
{
"product_id": "PROD-B2",
"name": "Smartphone Case",
"price": 19.99,
"quantity": 2
}
],
"shipping_address": {
"street": "123 Maple Ave",
"city": "Springfield",
"state": "IL",
"zip": "62704"
},
"payment": {
"method": "credit_card",
"status": "completed"
}
},
{
"order_id": "ORD-002",
"customer": {
"id": "CUST-102",
"name": "Bob Smith",
"contact": {
"email": "bob@example.com"
// phone missing
}
},
"items": [
{
"product_id": "PROD-C3",
"name": "Bluetooth Speaker",
"price": 49.99,
"quantity": 1
}
],
"shipping_address": {
"street": "456 Oak St",
"city": "Rivertown",
"state": "CA"
// zip missing
}
// payment information missing
}
]
}
"""
## Parse the JSON data
try:
data = json.loads(json_str)
except json.JSONDecodeError as e:
print(f"Invalid JSON: {e}")
exit(1)
## Import our SafeDict class from the previous example
class SafeDict:
def __init__(self, data):
self.data = data
def get(self, *keys, default=None):
current = self.data
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
elif isinstance(current, list) and isinstance(key, int) and 0 <= key < len(current):
current = current[key]
else:
return default
return current
def __str__(self):
return str(self.data)
## Create a SafeDict instance
safe_data = SafeDict(data)
print("Processing order information safely...")
## Process each order
for i in range(10): ## Try to process up to 10 orders
## Use SafeDict to avoid KeyError
order = safe_data.get('orders', i)
if order is None:
print(f"No order found at index {i}")
break
## Create a SafeDict for this specific order
order_dict = SafeDict(order)
## Safely extract order information
order_id = order_dict.get('order_id', default='Unknown')
customer_name = order_dict.get('customer', 'name', default='Unknown Customer')
customer_email = order_dict.get('customer', 'contact', 'email', default='No email provided')
customer_phone = order_dict.get('customer', 'contact', 'phone', default='No phone provided')
## Process shipping information
shipping = order_dict.get('shipping_address', default={})
shipping_dict = SafeDict(shipping)
shipping_address = f"{shipping_dict.get('street', default='')}, " \
f"{shipping_dict.get('city', default='')}, " \
f"{shipping_dict.get('state', default='')} " \
f"{shipping_dict.get('zip', default='')}"
## Process payment information
payment_status = order_dict.get('payment', 'status', default='Unknown')
## Calculate order total
items = order_dict.get('items', default=[])
order_total = 0
for item in items:
item_dict = SafeDict(item)
price = item_dict.get('price', default=0)
quantity = item_dict.get('quantity', default=0)
order_total += price * quantity
## Print order summary
print(f"\nOrder ID: {order_id}")
print(f"Customer: {customer_name}")
print(f"Contact: {customer_email} | {customer_phone}")
print(f"Shipping Address: {shipping_address}")
print(f"Payment Status: {payment_status}")
print(f"Order Total: ${order_total:.2f}")
print(f"Items: {len(items)}")
## Print item details
for j, item in enumerate(items):
item_dict = SafeDict(item)
name = item_dict.get('name', default='Unknown Product')
price = item_dict.get('price', default=0)
quantity = item_dict.get('quantity', default=0)
print(f" {j+1}. {name} (${price:.2f} × {quantity}) = ${price*quantity:.2f}")
python3 practical_example.py
누락되거나 불완전한 데이터를 적절하게 처리하면서 복잡한 JSON 데이터 구조를 안전하게 처리하는 방법을 보여주는 출력을 볼 수 있습니다. 이는 구조가 항상 예상과 일치하지 않을 수 있는 외부 소스의 데이터를 처리할 때 특히 중요합니다.
실용적인 예제는 다음을 보여줍니다.
이러한 기술은 KeyError 예외로 인해 충돌하지 않고 실제 JSON 데이터를 처리할 수 있는 더 강력한 애플리케이션을 구축하는 데 도움이 됩니다.
이 튜토리얼에서는 Python JSON 객체에서 중첩된 키에 접근할 때 KeyError를 처리하기 위한 효과적인 전략을 배웠습니다. 우리는 다음과 같은 여러 가지 접근 방식을 탐구했습니다.
KeyError 예외를 포착하고 처리하는 기본적인 방법이러한 기술을 적용하여 JSON 객체에서 누락되거나 불완전한 데이터를 우아하게 처리하고 KeyError 예외로 인해 애플리케이션이 충돌하는 것을 방지하는 더 강력한 코드를 작성할 수 있습니다.
다음 핵심 사항을 기억하십시오.
이러한 기술은 외부 소스, API 또는 사용자 입력에서 JSON 데이터를 사용하는 모든 Python 개발자에게 필수적이며, 실제 데이터를 자신 있게 처리할 수 있는 더욱 탄력적인 애플리케이션을 구축할 수 있도록 합니다.