'Better way 45. 애트리뷰트를 리팩터링하는 대신 @property 를 사용하라' 정리

Effective Python 2nd 파이썬 코딩의 기술 (교보문고 링크)을 제대로 이해하고자 블로그에 정리합니다.



현재 위치

Note
<6. Metaclasses and Attributes>
Item 45: Consider @property Instead of Refactoring Attributes
Better way 45. 애트리뷰트를 리팩터링하는 대신 @property 를 사용하라



@property를 사용해서 기존 애트리뷰트를 사용하는 코드는 그대로 사용하면서 새로운 기능을 부여할 수 있습니다.
인터페이스를 점차 개선해 나가는 과정에서 중간중간 필요한 기능을 제공하는 수단으로도 유용합니다.



아래와 같은 클래스가 있습니다.
여기서 quota 사용 부분에 새로운 기능을 얹고 싶었던 상황이었습니다.

class Bucket:
    def __init__(self, period):
        self.period_delta = timedelta(seconds=period)
        self.reset_time = datetime.now()
        self.quota = 0

    def __repr__(self):
        return f'Bucket(quota={self.quota})'

기존 quata 애트리뷰트를 대신하여 @property 를 사용합니다.

from datetime import datetime, timedelta


# Example 7
class Bucket:  # NewBucket
    def __init__(self, period):
        self.period_delta = timedelta(seconds=period)
        self.reset_time = datetime.now()
        # self.quota = 0  # 기존 요거 대신에 @property 로 quota 제공
        self.max_quota = 0
        self.quota_consumed = 0

    def __repr__(self):
        return (f'NewBucket(max_quota={self.max_quota}, '
                f'quota_consumed={self.quota_consumed})')

    @property
    def quota(self):
        return self.max_quota - self.quota_consumed

    @quota.setter
    def quota(self, amount):
        delta = self.max_quota - amount
        if amount == 0:
            # Quota being reset for a new period
            self.quota_consumed = 0
            self.max_quota = 0
        elif delta < 0:
            # Quota being filled for the new period
            assert self.quota_consumed == 0
            self.max_quota = amount
        else:
            # Quota being consumed during the period
            assert self.max_quota >= self.quota_consumed
            self.quota_consumed += delta

기존 클래스를 호출하던 코드는 그대로 사용 합니다.

# Example 2
def fill(bucket, amount):
    now = datetime.now()
    if (now - bucket.reset_time) > bucket.period_delta:
        bucket.quota = 0
        bucket.reset_time = now
    bucket.quota += amount


# Example 3
def deduct(bucket, amount):
    now = datetime.now()
    if (now - bucket.reset_time) > bucket.period_delta:
        return False  # Bucket hasn't been filled this period
    if bucket.quota - amount < 0:
        return False  # Bucket was filled, but not enough
    bucket.quota -= amount
    return True  # Bucket had enough, quota consumed

사용 예시

# Example 10
bucket = NewBucket(60)
print('Initial', bucket)  # Initial NewBucket(max_quota=0, quota_consumed=0)
fill(bucket, 100)
print('Filled', bucket)  # Filled NewBucket(max_quota=100, quota_consumed=0)

if deduct(bucket, 99):
    print('Had 99 quota')  # Had 99 quota
else:
    print('Not enough for 99 quota')

print('Now', bucket)  # Now NewBucket(max_quota=100, quota_consumed=99)

if deduct(bucket, 3):
    print('Had 3 quota')
else:
    print('Not enough for 3 quota')  # Not enough for 3 quota

print('Still', bucket)  # Still NewBucket(max_quota=100, quota_consumed=99)


책에서 챕터 마지막 부분에 적혀있는 내용입니다.

Tip
Use @property to give existing instance attributes new functionality.
@property를 사용해 기존 인스턴스 애트리뷰트에 새로운 기능을 제공할 수 있다.
Tip
Make incremental progress toward better data models by using @property.
@property를 사용해 데이터 모델을 점진적으로 개선하라.
Tip
Consider refactoring a class and all call sites when you find yourself using @property too heavily.
@property 메서드를 너무 과하게 쓰고 있다면, 클래스와 클래스를 사용하는 모든 코드를 리팩터링하는 것을 고려하라.




Related Content