CS
[Python] 동적 속성과 프로퍼티
왕밤빵도라에몽
2025. 6. 1. 13:04
728x90
동적 속성
Python 객체는 실행 중에 자유롭게 속성을 추가할 수 있다.
class User:
def __init__(self, name):
self.name = name
u = User("Kyu")
u.age = 25 # 동적으로 속성 추가
print(u.age) # 25
print(u.__dict__) # {'name': 'Kyu', 'age': 25}
동적 속성을 활용한 데이터 랭글링
JSON같은 경우, depth가 깊은 속성에 접근하고자 하면,feed['Schedule']['events'][40]['name']
과 같은 구문으로 접근해야할 것이다.
하지만 속성에 접근하는 클래스를 따로 둔다면,feed.Schedule.events[40].name
과 같이 자바스크립트같은 문법으로도 접근 가능해질 것이다.
from collections import abc
import keyword
class FrozenJSON:
def __new__(cls, arg):
if isinstance(arg, abc.Mapping):
return super().__new__(cls)
elif isinstance(arg, abc.MutableSequence):
return [cls(item) for item in arg]
else:
return arg
def __init__(self, mapping):
self.__data = {}
for key, value in mapping.items():
if keyword.iskeyword(key):
key += '_'
self.__data[key] = value
def __getattr__(self, name):
try:
return getattr(self.__data, name)
except AttributeError:
return FrozenJSON(self.__data[name])
def __dir__(self):
return self.__data.keys()
data = {
"Schedule": {
"events": [
{"name": "Event A"},
{"name": "Event B"}
]
}
}
feed = FrozenJSON(data)
print(feed.Schedule.events[1].name) # Event B
프로퍼티 기본 사용법
@property
- 메서드를 속성처럼 사용할 수 있도록 해주는 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("반지름은 0 이상이어야 합니다.")
self._radius = value
c = Circle(5)
print(c.radius) # 5
c.radius = 10
# c.radius = -1 # ValueError 발생
프로퍼티 팩토리
비슷한 프로퍼티를 반복해서 만들어야 할 경우, 팩토리 함수를 활용하여 코드 중복을 줄인다
def typed_property(name, expected_type):
private_name = f"_{name}"
@property
def prop(self):
return getattr(self, private_name)
@prop.setter
def prop(self, value):
if not isinstance(value, expected_type):
raise TypeError(f"{name}는 {expected_type.__name__} 타입이어야 합니다.")
setattr(self, private_name, value)
return prop
class Point:
x = typed_property('x', int)
y = typed_property('y', int)
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(10, 20)
print(p.x, p.y) # 10 20
# p.x = 'hello' # TypeError 발생
프로퍼티 캐싱
속성 값이 계산 비용이 크고 변하지 않을 경우, 매번 호출할 필요 없이 한 번만 계산하고 캐싱해두면 성능이 향상됨@cached_property
는 딕셔너리 캐시에 값을 저장해서, 두 번째 접근부터는 그냥 저장된 값만 리턴함
쓰기 불가능(read-only) 이고, 값 변경 시엔 객체를 다시 만들어야 함
from functools import cached_property
class Data:
def __init__(self, raw_data):
self.raw_data = raw_data
@cached_property
def summary(self):
print("summary 계산 중...")
return sum(self.raw_data) / len(self.raw_data)
d = Data([10, 20, 30])
print(d.summary) # summary 계산됨
print(d.summary) # 캐시된 값 사용됨 (계산 X)
References
-『 전문가를 위한 파이썬(2판) 』, 루시아누 하말류, 한빛미디어, Part 5 - Chapter 22
728x90