Python 동적 속성 조작과 매직 메서드 활용

동적 속성 탐색 메커니즘

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"

태그: python reflection getattr setattr magic methods

7월 29일 03:14에 게시됨