C.W.K.
Stream
Lesson 05 of 05 · published

빌트인 decorator — @property / @cached_property / @staticmethod / @classmethod

~22 min · property, cached_property, staticmethod, classmethod, builtin

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

모든 Python 개발자가 쓰는 4 빌트인 decorator

자체 decorator 는 가끔 쓰지. 이 넷은 매주 *써*. @property, @cached_property, @staticmethod, @classmethod — 다 빌트인 또는 stdlib, 각각 클래스 디자인 문제 깔끔히 해결.

@property — 함수인데 속성

사용자가 obj.area() 가 아닌 obj.area 쓰길 원하지만 area 가 계산되는 거 (저장 X) 였으면 좋겠다. @property 가 메서드를 속성 접근 시 실행되는 getter 로. setter, deleter 도 추가 가능 — @area.setter, @area.deleter. 클래스가 일반 속성 가진 것처럼 보이고, 구현은 내부에서 뭐든.

@cached_property — 한 번 계산하고 그 다음 속성

@property 와 같은 사용 모양, 근데 함수가 인스턴스당 한 번만 실행. 첫 접근 후 결과가 인스턴스의 일반 속성으로 저장. 이후 접근은 평범한 속성 lookup — 메서드 호출 X. 안 변하는 비싼 계산에 완벽. 3.8 신규 (전엔 third-party recipe).

@staticmethod — 클래스 네임스페이스에 사는 함수

메서드가 self, cls 둘 다 안 받음. 그냥 조직적 이유로 클래스에 붙은 함수. 코드가 논리적으로 클래스에 속하지만 인스턴스/클래스 state 안 필요할 때. 현대 스타일은 모듈 레벨 함수 선호하지만 @staticmethod 는 stdlib 와 많은 코드베이스에 여전히 흔함.

@classmethod — 인스턴스 X, 클래스 받음

메서드가 self 대신 cls 받음. 가장 흔한 용도 — 대안 생성자 — User.from_dict(d), Date.today(). cls 인자 덕에 서브클래스가 자동으로 맞는 클래스 — SpecialUser.from_dict(d)SpecialUser 반환, User X.

원칙: 클래스에 get_x(), set_x() 메서드 추가하는 자신을 발견하면 멈추고 @property 가 의도 더 잘 표현하지 않는지 자문. idiomatic Python 의 편향 — 객체의 *속성* 인 거에는 메서드 모양보다 속성 모양 API.

Code

@property — getter / setter / deleter·python
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("radius 양수여야")
        self._radius = value

    @property
    def area(self):                     # 계산, setter 불필요
        return 3.14159 * self._radius ** 2

c = Circle(5)
print(c.radius)              # 5     — 속성처럼 보임
print(c.area)                # 78.54 — 접근 시 계산
c.radius = 10                # setter 사용
print(c.area)                # 314.16

try:
    c.radius = -1            # setter 가 검증
except ValueError as e:
    print(e)
@cached_property — 한 번 계산·python
from functools import cached_property
import time

class Report:
    def __init__(self, data):
        self.data = data

    @cached_property
    def summary(self):
        print("summary 계산 중...")
        time.sleep(0.5)              # 비싼 작업이라 가정
        return f"sum={sum(self.data)}, avg={sum(self.data)/len(self.data)}"

r = Report([10, 20, 30, 40, 50])
print(r.summary)             # 'summary 계산 중...' 후 결과
print(r.summary)             # 재계산 X — 캐시된 값 반환
print(r.summary)             # 재계산 X

# 강제 재계산 — 캐시된 속성 삭제
del r.summary
print(r.summary)             # 'summary 계산 중...' 또
@staticmethod / @classmethod — 대안 생성자·python
from datetime import date

class User:
    def __init__(self, name, joined):
        self.name = name
        self.joined = joined

    @classmethod
    def from_dict(cls, d):
        # 서브클래스에 맞는 클래스 반환
        return cls(name=d["name"], joined=d.get("joined", date.today()))

    @classmethod
    def from_csv_row(cls, row):
        name, joined = row.split(",")
        return cls(name.strip(), date.fromisoformat(joined.strip()))

    @staticmethod
    def is_valid_name(name):
        # self/cls 안 만짐 — 단지 유틸리티
        return isinstance(name, str) and 1 <= len(name) <= 50

u = User.from_dict({"name": "Pippa"})
print(u.name, u.joined)
print(User.is_valid_name("x"))   # True
print(User.is_valid_name(""))    # False

# 서브클래스 — classmethod 가 여전히 맞는 타입 반환
class Admin(User):
    pass

a = Admin.from_dict({"name": "Dad"})
print(type(a).__name__)          # 'Admin'  — 'User' 아님!
@property + 캐싱 — 수동 (제어 원할 때)·python
class Stock:
    def __init__(self, ticker, price):
        self.ticker = ticker
        self.price = price
        self._fetched = None

    @property
    def cached_data(self):
        if self._fetched is None:
            print("fetching...")
            self._fetched = {"ticker": self.ticker, "price": self.price}
        return self._fetched

    def refresh(self):
        self._fetched = None             # 캐시 무효화

s = Stock("AAPL", 180)
print(s.cached_data)         # 'fetching...' 후 dict
print(s.cached_data)         # fetching X
s.refresh()
print(s.cached_data)         # 'fetching...' 또 — refresh 작동

# cached_property 가 흔한 경우엔 더 짧음
# 수동 @property 는 무효화 로직 필요할 때

External links

Exercise

클래스 Temperature — 내부적으로 Kelvin (self._k) 으로 저장하지만 세 property 노출 — kelvin, celsius, fahrenheit. 각 property 읽기 + 쓰기 가능 — temperature.celsius = 25 가 내부 Kelvin 을 298.15 로 갱신. setter 가 음의 Kelvin 안 만들어지는지 검증 (만들어지면 ValueError). description@cached_property — '273.15K 에서 물 얼어' 같은 문자열 반환. 모든 path 테스트.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.