모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

36강 / 전체 40강

프로퍼티와 캡슐화 — 데이터 접근 제어

10분 읽기 조회 0

파이썬의 캡슐화 관례(_, __)와 @property로 안전한 데이터 접근 인터페이스를 설계하고 지연 계산 패턴을 이해한다.

🎯 이 강의에서 배우는 것

캡슐화(Encapsulation)는 객체 내부 데이터를 외부에서 직접 바꾸지 못하도록 보호하고, 정해진 인터페이스(메서드)를 통해서만 접근하게 만드는 설계 원칙입니다. 파이썬은 Java처럼 private 키워드가 없지만, 관례와 @property 데코레이터로 동일한 목표를 달성합니다. 처음에는 단순 속성으로 시작해 나중에 검증 로직이 필요해지면 인터페이스를 바꾸지 않고 프로퍼티로 전환할 수 있습니다. 이것이 파이썬답습니다.

🔒 접근 제어 관례 — _, __의 의미

class BankAccount:
    # 공개 속성: 아무 제한 없음
    bank_name = "파이썬 은행"

    def __init__(self, owner: str, balance: float):
        self.owner = owner          # 공개(public) — 자유롭게 접근
        self._balance = balance     # 보호(protected) — "내부 사용" 관례
        self.__pin = "1234"        # 이름 맹글링(name mangling) — 더 강한 보호

    def get_balance(self) -> float:
        return self._balance

    def _validate_amount(self, amount: float) -> None:
        # _로 시작: 내부 헬퍼 — 외부에서 쓰지 말라는 신호
        if amount <= 0:
            raise ValueError("금액은 양수여야 합니다")

acc = BankAccount("홍길동", 100000)

# 공개 속성: 자유로운 접근
print(acc.owner)         # 홍길동

# _ 속성: 기술적으로는 접근 가능, 관례로 막음
print(acc._balance)      # 100000 (접근은 되지만 하면 안 됨)

# __ 속성: 이름 맹글링으로 외부 접근 더 어렵게
try:
    print(acc.__pin)     # AttributeError! __pin 은 없음
except AttributeError as e:
    print(e)

# 실제 저장된 이름 확인
print(acc.__dict__)      # {'owner': '홍길동', '_balance': 100000, '_BankAccount__pin': '1234'}
print(acc._BankAccount__pin)   # 1234 (여전히 접근 가능 — 완전한 private 아님)
# 파이썬 철학: "우리는 어른입니다" — 완전한 강제보다 관례로 협력

🏷️ @property — getter, setter, deleter

class Circle:
    PI = 3.14159265358979

    def __init__(self, radius: float):
        self._radius = radius   # 실제 데이터는 _radius에 저장

    @property
    def radius(self) -> float:
        # getter: circle.radius 처럼 속성처럼 접근
        return self._radius

    @radius.setter
    def radius(self, value: float) -> None:
        # setter: circle.radius = 5 처럼 할당할 때 호출
        if value < 0:
            raise ValueError(f"반지름은 음수일 수 없습니다: {value}")
        self._radius = value

    @radius.deleter
    def radius(self) -> None:
        # deleter: del circle.radius 할 때 호출
        del self._radius

    @property
    def diameter(self) -> float:
        # 계산된 속성 — 저장 안 하고 매번 계산
        return self._radius * 2

    @diameter.setter
    def diameter(self, value: float) -> None:
        self.radius = value / 2   # setter를 통해 검증

    @property
    def area(self) -> float:
        return self.PI * self._radius ** 2

c = Circle(5)
print(c.radius)    # 5 — getter
c.radius = 10      # setter (검증 포함)
print(c.diameter)  # 20 — 계산된 속성
c.diameter = 14    # diameter setter → radius = 7
print(c.radius)    # 7
print(f"넓이: {c.area:.2f}")   # 153.94

try:
    c.radius = -1   # ValueError!
except ValueError as e:
    print(e)

💡 단순 속성 → 프로퍼티 전환 — 인터페이스 유지

# 단계 1: 처음에는 단순 속성으로 시작
class Temperature_v1:
    def __init__(self, celsius: float):
        self.celsius = celsius   # 단순 속성

t = Temperature_v1(100)
print(t.celsius)   # 100

# 단계 2: 나중에 검증이 필요해지면 @property로 전환
# 외부 코드(t.celsius = x)는 변경 없이 그대로 동작!
class Temperature:
    def __init__(self, celsius: float):
        self.celsius = celsius   # setter 호출

    @property
    def celsius(self) -> float:
        return self._celsius

    @celsius.setter
    def celsius(self, value: float) -> None:
        if value < -273.15:
            raise ValueError(f"절대 영도보다 낮은 온도: {value}°C")
        self._celsius = value

    @property
    def fahrenheit(self) -> float:
        return self._celsius * 9/5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value: float) -> None:
        self.celsius = (value - 32) * 5/9

t = Temperature(100)
print(t.celsius)      # 100
print(t.fahrenheit)   # 212.0

t.fahrenheit = 32
print(t.celsius)      # 0.0

try:
    t.celsius = -300   # ValueError!
except ValueError as e:
    print(e)

# 이것이 파이썬에서 처음부터 get_celsius()/set_celsius()를 안 쓰는 이유
# 단순 속성으로 시작, 필요 시 @property로 전환 — 외부 인터페이스 불변

⚡ 지연 계산 프로퍼티 (Lazy Property)

class DataAnalyzer:
    def __init__(self, data: list):
        self._data = data
        self._mean = None      # 캐시 초기값
        self._std = None

    @property
    def mean(self) -> float:
        # 처음 접근 시에만 계산, 이후에는 캐시 반환
        if self._mean is None:
            print("평균 계산 중...")   # 실제로 계산되는지 확인용
            self._mean = sum(self._data) / len(self._data)
        return self._mean

    @property
    def variance(self) -> float:
        m = self.mean   # 캐시 활용
        return sum((x - m) ** 2 for x in self._data) / len(self._data)

    @property
    def std(self) -> float:
        if self._std is None:
            self._std = self.variance ** 0.5
        return self._std

    def add_data(self, value: float) -> None:
        self._data.append(value)
        self._mean = None   # 데이터 변경 시 캐시 무효화
        self._std = None

data = DataAnalyzer([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(data.mean)    # "평균 계산 중..." 출력 후 5.5
print(data.mean)    # 캐시 반환, "계산 중..." 없음
print(data.std)     # 2.872...

data.add_data(11)   # 캐시 무효화
print(data.mean)    # "평균 계산 중..." 다시 출력 후 6.0

⚠️ 자주 하는 실수

  • 프로퍼티와 속성 이름 동일하게 사용: @property def name(self): return self.name은 무한 재귀입니다. 내부 데이터는 self._name처럼 다른 이름(보통 _ 접두사)으로 저장하세요.
  • setter 없이 읽기 전용 프로퍼티에 할당: @property만 정의하고 @xxx.setter가 없으면 읽기 전용 프로퍼티입니다. 할당 시 AttributeError가 발생합니다. 의도적으로 읽기 전용을 만들 때 활용하세요.
  • 처음부터 모든 속성에 프로퍼티 사용: 단순 저장/반환만 하는 속성에 프로퍼티를 쓰면 코드가 길어지고 복잡해집니다. 검증, 계산, 지연 계산이 필요할 때만 사용하세요.

📝 정리 및 다음 강의 예고

  • 파이썬 접근 제어 관례: 공개(name), 보호(_name), 이름 맹글링(__name). 완전한 강제가 아닌 협약.
  • @property: 속성처럼 접근하되 내부에서 getter/setter/deleter 실행. 인터페이스 변경 없이 검증 추가 가능.
  • 계산된 속성: 저장 대신 매번 계산. 지연 계산: 처음 접근 시 계산 후 캐시.
  • 단순 속성으로 시작 → 프로퍼티 전환: 파이썬의 YAGNI 철학. Java 스타일 getX/setX 불필요.

다음 강의: 데이터클래스(@dataclass) — __init__, __repr__, __eq__를 자동 생성하는 현대적인 클래스 작성법. frozen 데이터클래스, field(), post_init 패턴을 배웁니다.

관련 주제

  • 캡슐화 관례(_, __)
  • 이름 맹글링(name mangling)
  • @property 데코레이터
  • getter·setter·deleter
  • 지연 계산(lazy property)
  • 값 검증 프로퍼티
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 파이썬 기초 40강 — 처음 배우는 프로그래밍
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

파이썬 기초 40강 — 처음 배우는 프로그래밍

이 강의가 속한 시리즈는 총 40강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...