내장 @property 데코레이터를 이용하면 더 간결한 방식으로 인스턴스의 속성에 접근하게 할 수 있습니다. 고급 기법이지만 흔히 사용하는 @property 사용법 중 하나는 단순 숫자 속성을 즉석에서 계산하는 방식으로 변경하는 것입니다. 호출하는 쪽을 변경하지 않고도 기존에 클래스를 사용한 곳이 새로운 동작을 하게 해주므로 매우 유용한 기법입니다. 또한 시간이 지나면서 인터페이스를 개선할 때 중요한 임시방편이 됩니다.


예를 들어 구멍 난 양동이의 할당량을 일반 파이썬 객체로 구현하려 한다고 가정합니다. 다음 Bucket 클래스는 남은 할당량과 이 할당량을 이용할 수 있는 기간을 표현합니다.

import datetime


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

    def __repr__(self):
        return 'Bucket(quota=%d) ' % self.quota
# 구멍난 양동이 알고리즘은 양동이를 채울 때마다 할당량이 다음 기간으로 넘어가지 않게 하는식으로 동작

def fill(bucket, amount):
    now = datetime.datetime.now()
    if now - bucket.reset_time > bucket.period_delta:
        bucket.quota = 0
        bucket.reset_time = now
    bucket.quota += amount
# 할당량을 소비하는 쪽에서는 매번 사용할 양을 뺄 수 있는지부터 확인해야 함

def deduct(bucket, amount):
    now = datetime.datetime.now()
    if now - bucket.reset_time > bucket.period_delta:
        return False
    if bucket.quota - amount < 0:
        return False
    bucket.quota -= amount
    return True

bucket = Bucket(60)
fill(bucket, 100)
print(bucket)
# 결과
# Bucket(quota=100)

if deduct(bucket, 99):
    print('Had 99 quota')
else:
    print('Not enough for 99 quota')
print(bucket)
# 결과 이용할 수 있는 양보다 많이 뺴려해서 진행이 중단, 이 경우 양동이의 할당량은 그대로 남음
# Had 99 quota
# Bucket(quota=1)

if deduct(bucket, 3):
    print('Had 3 quota')
else:
    print('Not enough for 3 quota')
print(bucket)
# 결과
# Not enough for 3 quota
# Bucket(quota=1)

이 구현에서 문제는 양동이의 할당량이 어떤 수준에서 시작하는지 모른다는 점입니다. 양동이는 0이 될 때까지 진행 기간 동안 할당량이 줄어듭니다. 0이 되면 deduct가 항상 False를 반환합니다. 이때 deduct를 호출하는 쪽이 중단된 이유가 Bucket의 할당량이 소진되어서인지 아니면 처음부터 BBucket에 할당량이 없어서인지 알 수 있다면 좋을 것입니다. 


__repr__ 함수의 용도 ( vs __call__ 차이)


간단하게 print 역할을 함.

아래는 __repr__함수와 __call__함수의 차이를 나타냄

class Test:
    def __call__(self, *args, **kwargs):
        return '__call__ 호출'
    def __repr__(self):
        return '__repr__ 호출'


test = Test()

print(test) # __repr__ 호출
print(test()) # __call__ 호출


__repr__

  • 디버깅할 떄 사용
  • 객체에서 선언되있는 공식적인 값을 출력

__call__

  • 인스턴스가 함수로 사용될 때 사용
  • __init__함수와 쓰임새가 비슷?????!!
  • __call__ vs __init__ 차이



    __init__은 클래스를 인스턴스로 생성 할 때 맨 처음 사용.

    __call__은 생성된 인스턴스를 호출할 때 사용

    class foo:
        def __init__(self, a, b, c):
            # ...
    
    x = foo(1, 2, 3) # __init__


    class foo:
        def __call__(self, a, b, c):
            # ...
    
    x = foo()
    x(1, 2, 3) # __call__



문제를 해결하려면 클래스에서 기간동안 발생한 max_quota와 quota_consumed의 변경을 추적하도록 수정하면 됩니다. 

# import datetime
# import time
#
# now_dt = datetime.datetime.now()
# print('현재 시간: ', now_dt)
#
# now_t = time.mktime(now_dt.timetuple()) + now_dt.microsecond / 1E6
# c_now_dt = datetime.datetime.fromtimestamp(now_t, datetime.timezone.utc)
#
# print('datetime-> time -> datetime 변환시간: ', c_now_dt)
#
# print('replace() 변환시간: ',now_dt.replace(tzinfo=datetime.timezone.utc))
# print('astimezone() 변환시간: ',now_dt.astimezone(datetime.timezone.utc))

# 결과
# 2017-10-15 18:45:32.357988
# 1508060732.357988
# 2017-10-15 09:45:32.357988+00:00

import datetime


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

    def __repr__(self):
        return 'Bucket(max_quota=%d, quota_consumed=%d) ' % (self.max_quota, 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:
            # 새 기간의 할당량을 리셋
            self.quota_consumed = 0
            self.max_quota = 0
        elif delta < 0:
            # 새 기간의 할당량을 채움
            assert self.quota_consumed == 0
            self.max_quota = amount
        else:
            # 기간동안 할당량을 소비함
            assert self.max_quota >= self.quota_consumed
            self.quota_consumed += delta

def fill(bucket, amount):
    now = datetime.datetime.now()
    if now - bucket.reset_time > bucket.period_delta:
        bucket.quota = 0
        bucket.reset_time = now
    bucket.quota += amount
# 할당량을 소비하는 쪽에서는 매번 사용할 양을 뺄 수 있는지부터 확인해야 함

def deduct(bucket, amount):
    now = datetime.datetime.now()
    if now - bucket.reset_time > bucket.period_delta:
        return False
    if bucket.quota - amount < 0:
        return False
    bucket.quota -= amount
    return True

bucket = Bucket(60)
print('Init', bucket)
fill(bucket, 100)
print('Filled', bucket)

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


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

# 결과
# Init Bucket(max_quota=0, quota_consumed=0) 
# Filled Bucket(max_quota=100, quota_consumed=0) 
# Had 99 quota
# Now Bucket(max_quota=100, quota_consumed=99) 
# Not enough for 3 quota
# Still Bucket(max_quota=100, quota_consumed=99)


이처럼 property를 사용해 bucket클래스가 변경된 사실을 몰라도 되는 장점이 있습니다.

+ Recent posts