본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] Property 데코레이터를 이용한 캡슐화와 Side Effect 관리 방법 3가지

by Papa Martino V 2026. 3. 22.
728x90

캡슐화(Encapsulation)
캡슐화 (Encapsulation)

 

 

객체 지향 프로그래밍(OOP)에서 데이터의 무결성을 유지하고 외부의 잘못된 접근으로부터 내부 상태를 보호하는 캡슐화(Encapsulation)는 가장 핵심적인 개념입니다. 파이썬은 타 언어(Java, C++ 등)처럼 private 키워드를 통한 강제적인 접근 제한을 제공하지 않지만, @property 데코레이터를 통해 이를 우아하고 파이썬답게(Pythonic) 해결할 수 있습니다. 단순히 변수를 숨기는 것을 넘어, 데이터를 읽거나 수정할 때 발생하는 부수 효과(Side Effect)를 어떻게 제어하고 관리할 수 있는지 실무적인 관점에서 깊이 있게 다루어 보겠습니다.


1. 왜 직접 접근 대신 Property를 사용해야 하는가?

클래스의 인스턴스 변수에 직접 접근하여 값을 수정하는 방식(obj.value = 10)은 빠르고 간결하지만, 서비스 규모가 커질수록 치명적인 단점을 드러냅니다. 예를 들어, 온도 데이터에 음수값이 들어오거나, 사용자 나이에 200세가 입력되는 상황을 방어할 로직이 없기 때문입니다.

@property를 사용하면 다음과 같은 3가지 이점을 얻을 수 있습니다.

  • 데이터 검증(Validation): 잘못된 데이터가 주입되는 것을 원천 차단합니다.
  • 읽기 전용 상태 유지: 외부에서 수정하면 안 되는 핵심 데이터를 보호합니다.
  • 계산된 속성(Computed Attributes): 매번 계산할 필요 없이 호출 시점에 로직을 수행하여 결과를 반환합니다.

2. Property와 일반 __dict__ 접근 방식의 차이 비교

파이썬 객체는 기본적으로 __dict__라는 사전형 구조에 속성을 저장합니다. 하지만 @property는 '데이터 기술자(Data Descriptor)'로 동작하여 접근 방식에 차이를 만듭니다.

비교 항목 일반 인스턴스 변수 (__dict__) Property 데코레이터 방식
접근 제어 직접 노출되어 누구나 수정 가능 Getter/Setter를 통한 간접 제어
데이터 검증 불가능 (할당 시 로직 삽입 불가) Setter 내부에서 조건문으로 검증 가능
유지보수성 변수명 변경 시 참조하는 모든 코드 수정 내부 로직을 바꿔도 외부 인터페이스 유지
부수 효과 제어 예측 불가능한 값 변경 발생 가능 변경 시 로그 기록, 알림 등 트리거 가능

3. 실전 예제: Side Effect 관리를 통한 안정적인 클래스 설계

아래 예제는 은행 계좌 시스템을 모사한 코드입니다. 잔액(balance) 수정 시 발생할 수 있는 부수 효과를 어떻게 @property로 관리하는지 확인해 보세요.

Sample Example


class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        # 내부 변수는 관례적으로 _를 붙여 보호 수준임을 명시
        self._balance = balance
        self._transaction_history = []

    @property
    def balance(self):
        """잔액을 조회할 때 호출되는 Getter"""
        print(f"[LOG] {self.owner}님의 잔액을 조회합니다.")
        return self._balance

    @balance.setter
    def balance(self, value):
        """잔액을 수정할 때 Side Effect를 관리하는 Setter"""
        if value < 0:
            raise ValueError("잔액은 음수가 될 수 없습니다.")
        
        # Side Effect 1: 변경 이력 기록
        change = value - self._balance
        status = "입금" if change > 0 else "출금"
        self._transaction_history.append(f"{status}: {abs(change)}원")
        
        # Side Effect 2: 실제 데이터 업데이트
        print(f"[LOG] 데이터가 변경되었습니다: {self._balance} -> {value}")
        self._balance = value

# 실행 코드
account = BankAccount("채원", 1000)

# Getter 호출
print(f"현재 잔액: {account.balance}원")

# Setter 호출 (성공)
account.balance = 5000

# Setter 호출 (실패 - ValueError 발생)
try:
    account.balance = -100
except ValueError as e:
    print(f"에러 메시지: {e}")

print(f"거래 내역: {account._transaction_history}")

4. 고급 기법: 캡슐화를 완성하는 Side Effect 관리 전략

4.1 가짜 속성(Virtual Attributes) 활용

실제 변수는 존재하지 않지만, 기존 데이터를 조합해 마치 속성처럼 보이게 만드는 방법입니다. 예를 들어 first_namelast_name을 합쳐 full_name을 만드는 경우입니다. 이를 통해 중복 데이터를 저장하지 않고도 데이터 일관성을 유지할 수 있습니다.

4.2 지연 로딩(Lazy Loading) 구현

비용이 많이 드는 연산(예: 대용량 파일 읽기, DB 연결)은 객체 생성 시점이 아닌, 실제 데이터가 필요한 @property 호출 시점에 수행하도록 설계하여 메모리 효율을 극대화할 수 있습니다.

4.3 의존성 전파 제어

한 속성이 변경될 때 연관된 다른 객체나 시스템에 상태 변화를 통보해야 한다면, Setter 내부에 관찰자 패턴(Observer Pattern) 로직을 삽입하여 시스템 전체의 정합성을 맞출 수 있습니다.


5. 결론: 전문가가 제안하는 아키텍처 가이드

파이썬에서 @property는 단순한 편의 기능이 아닙니다. 이는 클래스의 공용 인터페이스를 견고하게 유지하면서도, 내부 구현의 유연성을 확보하는 강력한 도구입니다. 처음부터 모든 변수를 숨길 필요는 없지만, 데이터의 유효성 검사가 필요하거나 변경 시 추가 작업이 동반되어야 하는 경우라면 반드시 Property를 활용하는 것이 유지보수 비용을 줄이는 지름길입니다.

객체 지향의 본질은 "어떻게 저장하느냐"가 아니라 "어떻게 소통하느냐"에 있음을 기억하시기 바랍니다.


출처 및 참고 문헌

  • Python Software Foundation. "Built-in Functions: property()". 공식 문서.
  • Luciano Ramalho. "Fluent Python: Clear, Concise, and Effective Programming". O'Reilly Media.
  • Raymond Hettinger. "Descriptor HowTo Guide". Python Documentation.
728x90