@property 데코레이터: 캡슐화된 속성 접근
Python @property 데코레이터로 getter·setter·deleter를 구현하는 방법, 읽기 전용 속성 패턴, 유효성 검사와 지연 계산(lazy property) 활용법을 설명합니다.
지난 글에서 __enter__·__exit__로 컨텍스트 매니저 프로토콜을 구현하는 방법을 살펴보았습니다. 이번에는 클래스 내부 상태를 안전하게 감싸는 또 다른 핵심 도구인 @property 데코레이터를 다룹니다. @property를 쓰면 인스턴스 속성처럼 읽히지만, 뒤에서는 메서드가 실행되는 접근 인터페이스를 만들 수 있습니다.
@property 기본
속성 접근을 메서드로 가로채려면 @property 데코레이터를 붙입니다.
class Circle:
def __init__(self, radius: float) -> None:
self._radius = radius
@property
def radius(self) -> float:
return self._radius
c = Circle(5.0)
print(c.radius) # 5.0 — 마치 일반 속성처럼
c.radius는 메서드 호출처럼 보이지 않지만 내부적으로 Circle.radius.fget(c)를 실행합니다.
동작 흐름
속성에 접근하는 방식에 따라 Python이 호출하는 메서드가 달라집니다.
세 가지 접근자를 모두 조합한 예입니다.
class Temperature:
def __init__(self, celsius: float = 0.0) -> None:
self._celsius = celsius
@property
def celsius(self) -> float:
return self._celsius
@celsius.setter
def celsius(self, value: float) -> None:
if value < -273.15:
raise ValueError("절대영도 미만")
self._celsius = value
@celsius.deleter
def celsius(self) -> None:
self._celsius = 0.0
setter에 유효성 검사 로직을 담으면 외부에서 잘못된 값을 넣는 순간 즉시 오류가 발생하고, 내부 _celsius는 항상 유효한 상태를 유지합니다.
읽기 전용 속성
setter를 정의하지 않으면 쓰기 시도 시 AttributeError가 발생합니다. 이것이 읽기 전용 속성을 구현하는 가장 간단한 방법입니다.
class Rectangle:
def __init__(self, w: float, h: float) -> None:
self._w, self._h = w, h
@property
def area(self) -> float:
return self._w * self._h
area는 w와 h로부터 계산되는 파생 속성입니다. 직접 저장하지 않고 필요할 때 계산하므로 항상 최신 값을 반환합니다.
코드 예제
getter·setter·읽기 전용 속성의 조합과 사용 방법입니다.
지연 계산(Lazy Property)
값이 비싼 경우, 처음 접근할 때만 계산하고 결과를 캐시합니다.
class DataSet:
def __init__(self, data: list[float]) -> None:
self._data = data
self._mean: float | None = None
@property
def mean(self) -> float:
if self._mean is None:
self._mean = sum(self._data) / len(self._data)
return self._mean
Python 3.8+에서는 functools.cached_property가 이 패턴을 단 한 줄로 대체합니다.
from functools import cached_property
class DataSet:
def __init__(self, data: list[float]) -> None:
self._data = data
@cached_property
def mean(self) -> float:
return sum(self._data) / len(self._data)
cached_property는 첫 호출 후 결과를 인스턴스 __dict__에 직접 저장합니다. 이후 접근은 디스크립터를 건너뛰고 __dict__에서 바로 읽히므로 일반 속성과 같은 속도입니다.
property() 함수 형식
데코레이터 문법 없이 property() 내장 함수를 직접 쓸 수도 있습니다.
class Angle:
def _get_deg(self): return self._deg
def _set_deg(self, v): self._deg = v % 360
degrees = property(_get_deg, _set_deg)
데코레이터 방식이 더 읽기 쉽지만, 속성 이름과 접근자 이름을 분리해야 하는 드문 경우에 유용합니다.
흔한 실수: property와 init 순서
setter를 정의했다면 __init__에서도 public 이름으로 할당해야 유효성 검사가 적용됩니다.
# 잘못된 예
class Bad:
def __init__(self, v):
self._v = v # setter 건너뜀
# 올바른 예
class Good:
def __init__(self, v):
self.v = v # setter를 통과
@property
def v(self): return self._v
@v.setter
def v(self, val):
if val < 0: raise ValueError
self._v = val
__init__에서 self._v = v로 직접 쓰면 setter 로직을 우회하므로 생성자에서도 반드시 public 이름으로 할당해야 합니다.
지난 글: 컨텍스트 매니저 프로토콜: __enter__와 exit
다음 글: @classmethod와 @staticmethod: 세 가지 메서드 종류
읽어주셔서 감사합니다. 😊