파이썬의 캡슐화 관례(_, __)와 @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
불러오는 중...
