본문 바로가기
AI Journey/혼자 공부하는 파이썬

[파이썬] OOP - 캡슐화 개념과 데이터 은닉, getter/setter 이해하기

by 보눔비스타 2025. 4. 10.

지난 포스팅에서는 파이썬의 객체지향 프로그래밍(OOP) 핵심 개념 중 하나인 다형성(Polymorphism)에 대해 알아보았다. 이번 포스팅에서는 데이터 보호와 코드의 안정성에 매우 중요한 역할을 하는 캡슐화(Encapsulation) 개념과 함께 이를 구현하는 getter와 setter 메서드에 대해 자세히 알아보자. 

캡슐화란?

캡슐화(Encapsulation)는 객체의 내부 상태(속성)를 외부에서 직접 접근하지 못하도록 감추고, 접근을 위한 메서드를 통해 데이터를 간접적으로 제어하는 방법이다.

즉, 객체 내부의 데이터 보호인터페이스 제공을 목적으로 하며, 불필요하거나 위험한 접근을 제한함으로써 코드의 안정성과 유지보수성을 높여준다. 만약 캡슐화를 통해 객체의 데이터를 보호하지 않으면, 외부에서 내부 상태를 마음대로 조작할 수 있어 비정상적인 상태나 예기치 못한 동작이 발생할 수 있다.

"불필요하거나 위험한 접근"의 의미 

여기서 "불필요하거나 위험한 접근"이라는게 어떤 상황을 의미하는지 잘 와닿지 않을 수 있다. 

이해를 돕기 위해 계좌 잔액을 관리하는 클래스 BankAccount를 예로 들어보자.  

 

💡전제: 이 클래스는 마이너스 잔액을 허용하지 않는 일반 계좌 모델임

class BankAccount:
    def __init__(self, balance):
        self.balance = balance  # 공개 속성 (public)

 

이렇게 balance라는 속성을 private으로 설정하지 않고 공개(public) 속성으로 두면, 외부 코드에서 아래와 같이 balance 속성에 직접 접근할 수 있기 때문에 잔액을 음수로 바꾸는 비정상적인 조작이 가능해진다.

account = BankAccount(1000)
account.balance = -5000  # 잘못된 값이 입력됨 (논리적으로 불가능한 상태)

 

이와 같은 입력은 잘못된 데이터로, 시스템의 무결성을 해치게 된다.

이처럼 객체의 상태가 비정상적으로 변할 수 있는 위험한 상황을 방지하려면, 속성을 직접 노출하기보다 검증을 거친 메서드를 통해서만 간접적으로 제어할 수 있도록 해야 한다.

검증된 메서드를 통한 간접 제어 (setter 메서드)

간접 제어를 이해하려면, 직접 제어 개념을 알아야 한다. 사실 알고 보면 단순한 개념이다. 

 

1. 직접 제어 (캡슐화 X)

  • 외부 코드가 객체의 속성에 직접 접근해서 마음대로 바꿀 수 있다. 
  • 이 경우, 값의 유효성 여부를 검사하지 않기 때문에 오류나 논리적 문제가 발생할 수 있다. 

2. 간접 제어 (캡슐화 O)

  • 클래스 외부 코드에서 직접 속성을 바꿀 수 없다. 
  • 반드시 setter 메서드를 통해서만 값을 바꿀 수 있다.
  • 메서드 안에는 유효한 값인지 검사하는 로직이 있어야 한다.

다시 BankAccount 클래스를 예로 들어, 유효성 검사를 포함한 메서드를 어떻게 사용할 수 있는지 알아보자. 

우선 가장 간단한 방법은 값을 설정하는 전용 메서드, 즉 setter 메서드를 만들어 이 메서드를 통해서만 속성 값을 변경하도록 강제하는 것이다. 

class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def set_balance(self, amount):  # Setter 메서드
        if amount >= 0:
            self.balance = amount
        else:
            print("잔액은 음수가 될 수 없습니다.")



account = BankAccount(1000)
account.set_balance(500)     # ✅ 정상
account.set_balance(-2000)   # ❌ 경고 출력

 

이처럼 Setter 메서드를 통해 속성 값을 간접적으로 제어할 수 있고, 잘못된 값이 입력되는 것을 사전에 방지할 수 있다.

💡 참고: Getter는 속성 값을 “읽을 때” 사용하는 메서드이며, Setter는 “설정할 때” 사용하는 메서드다.

 

그러나 이처럼 setter가 있다고 해도, 아래와 같이 외부에서  balance에 직접 접근할 수 있다면 이러한 검증 로직은 무용지물이다. 

account.balance = -9999  # 직접 접근 가능!

 

그래서 필요한 것이 바로 접근 제한자이다. 

 

데이터 은닉(Data hiding)

파이썬은 Java, C++, C# 등과 달리 private, protected, public 같은 접근 제어 키워드를 명시적으로 제공하지 않는다.
대신 언더스코어 네이밍 규칙(접근 제한자)을 통해 속성의 접근 수준을 표현한다.

표기법 의미 접근 수준
name public 누구나 접근 가능
_name protected 접근 가능하지만 외부 사용은 권장하지 않음
__name private Name mangling 적용 → 외부 접근 차단됨

 

파이썬에서 __balance처럼 언더스코어 두 개(__)로 시작하는 변수는 private 변수처럼 다뤄지는데,
실제로는 “Name Mangling(네임 맹글링)”이라는 방식으로 변수명이 바뀌는 것이다.

이는 객체지향 원칙 중 하나인 데이터 은닉(data hiding) 을 실현하는 방법이다.

 

따라서 다음과 같이 정의하면 외부에서는 __balance직접 접근할 수 없게 된다. (에러 발생)

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # private 속성


account = BankAccount(1000)
print(account.__balance)  # ❌ AttributeError 발생

 

따라서 BankAccount 클래스에서 외부 코드로부터 balance에 직접 접근하는 것을 차단하고, 대신 간접 접근으로 값을 안전하게 조회하고 검증된 값만 입력할 수 있게 하기 위해서는 다음과 같이 접근 제한자 (__)를 통해 변수를 private으로 선언하고 필요한 경우 getter와 setter 메서드로 간접 접근을 허용하면 된다. 

Private 변수, Getter와 Setter를 사용한 클래스 예시

다음은 위에서 살펴본 캡슐화 원칙을 활용해 BankAccount라는 클래스를 정의하고, Getter/Setter 메서드로 안전하게 값을 제어하는 코드 예제다. 

class BankAccount:
    def __init__(self, balance):
        if balance < 0:
            raise ValueError("초기 잔액은 음수가 될 수 없습니다.")
        self.__balance = balance

    def set_balance(self, amount):
        if amount >= 0:
            self.__balance = amount
        else:
            raise ValueError("잔액은 음수가 될 수 없습니다.")  # 음수 입력 방지

    def get_balance(self):
        return self.__balance

# 사용 예시
account = BankAccount(1000)

account.set_balance(500)          # ✅ 정상
print(account.get_balance())      # 500 출력

account.set_balance(-300)         # ❌ ValueError: 잔액은 음수가 될 수 없습니다.

 

동작 과정

1. 클래스 정의

class BankAccount:
    def __init__(self, balance):
        if balance < 0:
            raise ValueError("초기 잔액은 음수가 될 수 없습니다.")
        self.__balance = balance

 

  • __init__()은 생성자 메서드. 객체를 만들 때 자동으로 호출됨.
  • 인자로 받은 balance가 음수이면 에러 발생 (ValueError)
  • 그 외의 경우엔 __balance라는 private 속성에 잔액을 저장
    def set_balance(self, amount):
        if amount >= 0:
            self.__balance = amount
        else:
            raise ValueError("잔액은 음수가 될 수 없습니다.")

 

 

  • set_balance()setter 메서드.
  • 잔액을 설정할 때, 음수인지 먼저 검사
  • 음수면 예외 발생, 양수면 __balance를 갱신
    def get_balance(self):
        return self.__balance

 

  • get_balance()getter 메서드.
  • 현재 잔액을 외부에서 확인할 수 있도록 반환

2. 클래스 사용

account = BankAccount(1000)
  • BankAccount 객체 생성
  • __init__() 호출 → 1000은 음수가 아니므로 통과
  • self.__balance = 1000 저장됨
    ✅ 정상적으로 계좌 생성됨
account.set_balance(500)

 

  • set_balance() 호출
  • amount = 500 → 양수이므로 통과
  • __balance가 500으로 갱신됨
    ✅ 잔액 수정 완료
print(account.get_balance())

 

 

  • get_balance() 호출 → __balance 반환
  • 현재 잔액은 500 →
    ✅ 콘솔에 500 출력
account.set_balance(-300)

 

 

  • set_balance() 호출
  • amount = -300 → 음수!
  • 조건 불충족 → raise ValueError(...) 실행
    ❌ 예외 발생: "잔액은 음수가 될 수 없습니다."

 

 

지금까지 은행 계좌 잔액 클래스를 예시로 파이썬 객체지향 핵심 개념인 캡슐화에 대해 알아보았다. 

다음에 기회가 되면 메서드를 속성처럼 사용할 수 있도록 만들어주는 @property 데코레이터에 대한 포스팅도 한 번 작성해봐야겠다.