Effective Python item 30


ちょっととっかかりづらかったので、leaky ではない普通のバケツの問題に書き換えました。


◯ property を使うメリット

既存の関数, メソッドを書き換えることなく、属性を変更することができます。

◯ 問題

以下のコードを現在量 quota ではなく...

def sample_code():
    # 水を貯められるバケツ Bucket オブジェクト
    bucket = Bucket()
    
    # 水を汲む fill 関数
    fill(bucket, 100)
    
    # 水を抜く deduct 関数
    deduct(bucket, 30)
    
    # __repr__
    print(bucket)  # Bucket(quota=70)
    
    #
    fill(bucket, 100)
    deduct(bucket, 20)
    print(bucket)  # Bucket(quota=150)


class Bucket(object):
    def __init__(self, quota=0):
        self.quota = quota
    
    def __repr__(self):
        return 'Bucket(quota=%d)' % self.quota


def fill(bucket, amount):
    bucket.quota += amount

def deduct(bucket, amount):
    bucket.quota -= amount

if __name__ == '__main__':
    sample_code()



総支出量 self.quota_consumed、総取得量 self.quota_accumulated で管理するように、2つの quota メソッドを実装してください。

ただし、既存のメソッド、fill, deduct を変更してはいけません。

def sample_code():
    bucket = Bucket()
    fill(bucket, 100)
    deduct(bucket, 30)
    print(bucket)
    fill(bucket, 100)
    deduct(bucket, 20)
    print(bucket)


class Bucket(object):
    def __init__(self, quota_accumulated=0, quota_consumed=0):
        self.max_quota = max_quota
        self.quota_consumed = quota_consumed

    def __repr__(self):
        return ('Bucket(quota_accumulated=%d, quota_consumed=%d)' %
                (self.max_quota, self.quota_consumed))

    @property
    def quota(self):
        ...
    
    @quota.setter
    def quota(self, new_quota):
        ...


def fill(bucket, amount):
    bucket.quota += amount

def deduct(bucket, amount):
    bucket.quota -= amount

if __name__ == '__main__':
    sample_code()


◯ 解答例

こんな感じに。fill, deduct を書き換えることなく、総支出量 self.quota_consumed、総取得量 self.quota_accumulated で管理するようになりました。

def sample_code():
    bucket = Bucket()
    fill(bucket, 100)
    deduct(bucket, 30)
    print(bucket)
    fill(bucket, 100)
    deduct(bucket, 20)
    print(bucket)


class Bucket(object):
    def __init__(self, quota_accumulated=0, quota_consumed=0):
        self.quota_accumulated = quota_accumulated
        self.quota_consumed = quota_consumed
    
    def __repr__(self):
        return ('Bucket(quota_accumulated=%d, quota_consumed=%d)' %
                (self.quota_accumulated, self.quota_consumed))
    
    @property
    def quota(self):
        return self.quota_accumulated - self.quota_consumed
    
    @quota.setter
    def quota(self, new_quota):
        delta = new_quota - self.quota
        if new_quota < 0:
            raise ValueError
        # fill
        if delta > 0:
            self.quota_accumulated += delta
        # deduct
        else:
            self.quota_consumed -= delta


def fill(bucket, amount):
    bucket.quota += amount

def deduct(bucket, amount):
    bucket.quota -= amount

if __name__ == '__main__':
    sample_code()

オリジナルのコードが若干理解しづらい...

  1. まず、水漏れバケツ, leaky bucket が何かよくわかっていなかった。

  2. leaky bucket が中途半端に実装されていて、逆にわかりづらくなってしまっている。かと言って、leaky じゃないと上のコードのように property の何が嬉しいのか、いまさんで伝わりづらくなる。

  3. self.quota_max, self_quota_consumed の値の管理の仕方。片方は、総計なのに、片方は累積になってる。両方とも累積の表現にしました。