SW/Python

Python의 숨겨진 보석: Property와 Descriptor 이야기

얇은생각 2025. 3. 26. 07:30
반응형

여러분도 아마 파이썬 코드를 매일 쓰지만, 속에 숨겨진 멋진 기능들을 그냥 지나쳤을지도 몰라요. 오늘은 그중에서도 정말 알아두면 쓸모 있는, 그런데 의외로 많은 사람들이 잘 모르는 기능인 **프로퍼티(Property)**와 **디스크립터(Descriptor)**를 함께 살펴보려고 해요. 이 두 가지는 코드를 더 깔끔하고 효율적으로 만들어주는데요, 여러분도 읽다 보면 "이게 이렇게 편한 거였어?" 하고 놀랄 거예요.

 

Python의 숨겨진 보석: Property와 Descriptor 이야기

 

Getter(Getter)와 Setter(Setter): 아, 이걸 또 해야 해?

여러분 혹시 attribute 값을 가져오거나 수정하려고 매번 method를 일일이 써야 했던 적이 있나요? object-oriented programming을 배우다 보면 게터세터가 필수처럼 보일 때가 있어요. 뭔지 모르겠다면 걱정하지 마세요. 예제를 하나 보면서 간단히 설명할게요.

class Product:
    def __init__(self, price, quantity):
        self._price = price  # 내부 속성이라고 표시하는 관례
        self._quantity = quantity

    def get_price(self):
        return self._price

    def set_price(self, value):
        if value < 0:
            raise ValueError("가격은 음수가 될 수 없습니다.")
        self._price = value

 

여기서 get_price()로 가격을 가져오고, set_price()로 가격을 설정하죠. 그런데 문제는 이 메서드를 매번 호출해야 한다는 거예요.

예를 들어 가격을 출력하려면 print(product.get_price())처럼 적어야 하고, 값을 변경할 때도 product.set_price(15)라고 써야 하죠. 귀찮고 코드도 길어져요.

 

"나 여기 있어요!" 파이썬의 구세주, @property

파이썬은 게터와 세터의 번거로움을 해결하기 위해 @property Decorator를 제공해요. 이걸 쓰면 속성에 그냥 자연스럽게 접근할 수 있답니다.

class Product:
    def __init__(self, price, quantity):
        self._price = price
        self._quantity = quantity

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        if value < 0:
            raise ValueError("가격은 음수가 될 수 없습니다.")
        self._price = value

 

와우, 이제 어떻게 바뀌었는지 보세요:

p = Product(10, 5)
print(p.price)  # 간단하게 값을 가져오기!
p.price = 15  # 깔끔하게 값 설정하기!

 

엄청 간단해졌죠? 더 이상 get_price()나 set_price()를 호출할 필요가 없어요. 그냥 p.price라고 하면 알아서 값을 가져오고 설정합니다. 너무 편해요!

 

프로퍼티의 숨은 기능들

프로퍼티에는 기본적인 값 가져오기와 설정하기 말고도 알아두면 유용한 고급 기능들이 있어요. 예를 들어볼까요?

 

읽기 전용 속성 만들기

가격을 바꿀 수 없게 만들고 싶다면 세터 메서드를 그냥 안 만들어도 돼요.

class Product:
    def __init__(self, cost):
        self._cost = cost

    @property
    def cost(self):
        return self._cost

p = Product(10)
print(p.cost)  # 10 출력
p.cost = 20  # AttributeError 발생: 변경할 수 없습니다.

 

이렇게 하면 외부에서 값을 변경하려고 할 때 오류가 나요. 안전성이 보장되는 거죠.

 

 

계산 결과를 캐싱하는 @cached_property

혹시 반복 계산이 무거운 작업일 때가 있나요? 그럴 땐 파이썬의 @cached_property를 사용해서 한 번 계산한 결과를 저장할 수 있어요.

from functools import cached_property

class Product:
    def __init__(self, values):
        self.values = values

    @cached_property
    def average(self):
        print("평균 계산 중...")
        return sum(self.values) / len(self.values)

p = Product([10, 20, 30])
print(p.average)  # 처음 호출 시 계산
print(p.average)  # 두 번째 호출 시 캐시된 값 반환

 

첫 번째 호출 때는 계산하지만, 두 번째 호출부터는 캐시된 값을 바로 반환하니까 속도도 빨라지고 자원도 절약되죠.

 

 

디스크립터: 숨은 제어자

이제 프로퍼티를 알았으니, 한 단계 더 나아가 디스크립터를 알아볼까요? 디스크립터는 속성의 값을 가져오거나 설정할 때 더 강력한 제어를 가능하게 해주는 기능이에요.

 

간단한 디스크립터 예제

디스크립터를 사용해 양수 값만 허용하는 속성을 만들어볼게요:

class PositiveNumber:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        return instance.__dict__.get(self.name, 0)

    def __set__(self, instance, value):
        if value < 0:
            raise ValueError(f"{self.name}는 양수여야 합니다.")
        instance.__dict__[self.name] = value

class Product:
    price = PositiveNumber("price")
    quantity = PositiveNumber("quantity")

    def __init__(self, price, quantity):
        self.price = price
        self.quantity = quantity

p = Product(10, 5)
print(p.price)  # 10 출력
p.price = -5  # ValueError 발생

 

디스크립터가 속성 값을 검사해서 양수만 허용하도록 해줘요. 실수로 잘못된 값을 넣으려고 해도 바로 오류가 나니까 걱정 없죠.

 

디스크립터의 진짜 매력: 실무에서의 활용

여러분이 Django(Django) 같은 프레임워크를 사용해봤다면 디스크립터를 본 적이 있을지도 몰라요. 장고에서 데이터베이스 필드를 정의할 때 이 디스크립터가 내부적으로 사용돼요. 예를 들어, 문자 필드에 문자열만 들어가도록 검사하거나 최대 길이를 초과하지 않도록 하는 것도 디스크립터 덕분이에요.

class CharField:
    def __init__(self, name, max_length):
        self.name = name
        self.max_length = max_length

    def __get__(self, instance, owner):
        return instance.__dict__.get(self.name, "")

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise ValueError("문자열이어야 합니다.")
        if len(value) > self.max_length:
            raise ValueError(f"최대 길이 {self.max_length}을 초과했습니다.")
        instance.__dict__[self.name] = value

class Product:
    name = CharField("name", max_length=10)

    def __init__(self, name):
        self.name = name

p = Product("커피 머그")
print(p.name)  # 정상 출력
p.name = "너무 길어서 오류"  # ValueError 발생

 

이렇게 디스크립터를 활용하면 데이터 integrity을 보장할 수 있어요.

 

결론: 배운 걸 어떻게 써볼까?

이제 여러분은 파이썬의 프로퍼티와 디스크립터가 얼마나 강력한 도구인지 알게 됐어요. 게터와 세터의 번거로움에서 벗어나고, 데이터 무결성을 자동으로 검사하면서도 더 깔끔하고 가독성 높은 코드를 작성할 수 있답니다.

이런 기능들을 알아두면 파이썬으로 더 효율적이고 안전한 프로그램을 작성할 수 있어요. 다음에 장고나 Flask에서 이런 구조를 볼 때 "아, 이게 바로 디스크립터구나!" 하고 감 잡을 수 있겠죠? 그러니 계속해서 새로운 기능을 탐구하고, 실전에서 써보세요. 프로그래밍은 탐험하는 재미가 있잖아요!

반응형