동적 속성 탐색 메커니즘
Python의 동적 특성을 활용하면 런타임에 객체의 내부 구조를 탐색하고 조작할 수 있다. dir() 함수를 통해 객체의 모든 속성명을 문자열 형태로 확인할 수 있으며, 이를 바탕으로 속성에 동적으로 접근하는 기법을 알아본다.
class Person:
def __init__(self, nickname, years):
self.nickname = nickname
self.years = years
someone = Person("alex", 25)
print(dir(someone)) # ['__class__', ..., 'nickname', 'years']속성 접근 제어 함수
Python은 문자열 기반으로 속성을 조작하는 가지 핵심 함수를 제공한다. 클래스와 인스턴스 모두 대상으로 사용 가능하다.
class Employee:
def __init__(self, name):
self.name = name
staff = Employee("Kate")
# 속성 존재 여부 확인
print(hasattr(staff, "name")) # True
print(hasattr(staff, "department")) # False
# 속성 값 획득 (미존재 시 기본값 반환)
print(getattr(staff, "name", "unknown")) # Kate
print(getattr(staff, "department", "Sales")) # Sales
# 속성 동적 생성 및 수정
setattr(staff, "department", "Engineering") # staff.department = "Engineering"
print(staff.department) # Engineering
# 속성 제거
delattr(staff, "department") # del staff.department동적 메서드 디스패치 패턴
사용자 입력에 따라 객체의 메서드를 동적으로 호출하는 패턴은 CLI 도구나 프로토콜 핸들러에서 자주 활용된다.
class FileHandler:
def download(self, filename):
print(f"Fetching {filename} from remote...")
def upload(self, filename):
print(f"Pushing {filename} to remote...")
def remove(self, filename):
print(f"Erasing {filename} permanently...")
def run(self):
commands = {
"get": "download",
"put": "upload",
"rm": "remove"
}
while True:
user_input = input("cmd> ").strip().split()
if len(user_input) != 2:
continue
action, target = user_input
# 매핑된 메서드명으로 동적 호출
method_name = commands.get(action)
if method_name and hasattr(self, method_name):
getattr(self, method_name)(target)
else:
print(f"Unsupported command: {action}")
handler = FileHandler()
# handler.run()특수 메서드(매직 메서드) 활용
이중 밑줄로 둘러싸인 특수 메서드는 특정 상황에서 자동으로 호출된다. 객체의 문자열 표현과 자원 해제를 담당하는 대표적인 메서드를 살펴본다.
객체 표현 커스터마이징
class Product:
def __init__(self, title, price):
self.title = title
self.price = price
def __str__(self):
# print() 또는 str() 호출 시 실행, 반드시 문자열 반환
return f"[Product] {self.title}: ${self.price}"
def __repr__(self):
# 개발자 디버깅용 상세 표현
return f"Product('{self.title}', {self.price})"
item = Product("Wireless Mouse", 29.99)
print(item) # [Product] Wireless Mouse: $29.99
print(repr(item)) # Product('Wireless Mouse', 29.99)자원 정리 보장
class DatabaseConnection:
def __init__(self, host, port):
self.host = host
self.port = port
self.socket = self._establish_link(host, port) # 시스템 자원 할당
def _establish_link(self, h, p):
print(f"Connecting to {h}:{p}...")
return object() # 실제 소켓 객체를 가정
def __del__(self):
# 객체 소멸 시 시스템 자원 해제
print(f"Closing connection to {self.host}")
# self.socket.close() # 실제 해제 로직
db = DatabaseConnection("192.168.1.1", 5432)
del db # Closing connection to 192.168.1.1고급 속성 제어 프로토콜
속성 접근, 설정, 삭제 과정을攔截하여 커스텀 로직을 삽입할 수 있다.
class ValidatedRecord:
def __init__(self, identifier, score):
self.identifier = identifier
self.score = score
def __getattr__(self, attr):
# 존재하지 않는 속성 접근 시
return f"{attr} is not defined"
def __setattr__(self, key, value):
# 속성 설정 시 검증 로직 적용
if key == "score" and not (0 <= value <= 100):
raise ValueError("Score must be between 0 and 100")
# 무한 재귀 방지를 위해 __dict__ 직접 조작
self.__dict__[key] = value
def __call__(self, multiplier=1):
# 인스턴스를 함수처럼 호출
return self.score * multiplier
record = ValidatedRecord("S001", 85)
print(record.grade) # grade is not defined
print(record(2)) # 170, __call__ 실행
record.score = 95
# record.score = 150 # ValueError 발생예외 처리 전략
견고한 프로그램을 위해 예외 상황을 적절히 처리해야 한다.
def process_data(dataset):
try:
result = 100 / len(dataset)
value = dataset[10]
except ZeroDivisionError:
print("Empty dataset provided")
except IndexError:
print("Insufficient elements")
except Exception as err:
print(f"Unexpected issue: {err}")
else:
print("Processing completed without errors")
return result
finally:
print("Cleanup operations executed")
numbers = [1, 2, 3]
process_data(numbers)
# 조건 불만족 시 의도적 예외 발생
if len(numbers) < 5:
raise RuntimeError("Minimum 5 items required")
# 디버깅용 단언
assert len(numbers) == 3, "Length must be exactly 3"