ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] tip - 속성을 리팩토링하는 대신 @property를 고려
    언어/파이썬 & 장고 2017. 10. 21. 15:24

    내장 @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클래스가 변경된 사실을 몰라도 되는 장점이 있습니다.

    댓글