~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 는 무효화 로직 필요할 때
클래스 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.