Python(整数の管理を行うクラス定義)

整数の管理を行うための、次の要件を満たすクラス SimpleCounter を定義

このクラスは以下の要件を満たす
1) このクラスは、初期値が整数の0である内部カウンタ持ちます。
2) メソッドincとメソッドdecにより、それぞれ内部カウンタに対してインクリメント(1増やす)とデクリメント(1減らす)を行います。
3) 内部カウンタの状態は、メソッドcountによって取得します。

class SimpleCounter:
    def __init__(self):
        self.__x = 0

    def inc(self):
        self.__x += 1

    def dec(self):
        self.__x -= 1

    def count(self):
        return self.__x
>>> c = SimpleCounter()
>>> c
<__main__.SimpleCounter object at 0x000001A37C6C9588>
>>> c.count()
0
>>> c.inc()
>>> c.count()
1
>>> c.inc()
>>> c.count()
2
>>> c.dec()
>>> c.count()
1
先に定義したクラス SimpleCounter を継承し、次の機能を持たせた、より実用的なクラスCounter を定義

クラスCounter は、少なくとも次の要件を満たす。
1) 特殊メソッドstrおよびreprによって、カウンタの状態を表示するための文字列化を行うことができます。
例えば、内部カウンタの値が1のとき、文字列化すると”<Counter: 1>”のように内部状態を反映した結果が返ります。
2) 特殊メソッドeqによって、Counterクラス同士で内部カウンタが等しいかどうかの比較を行うことができます。なお、Counterクラス同士でなければ、無条件でFalseを返します。(Pythonにおいてobj1 == obj2という式は、obj1がクラスのインスタンスである場合には、内部的にobj.eq(obj2)というメソッド呼び出しと等価です)

class Counter(SimpleCounter):
    def __repr__(self):
        return f"<Counter:{self.count()}>"

    def __str__(self):
        return f"<Counter:{self.count()}>"

    def __eq__(self,other):
        if isinstance(other,Counter):
            return self.count() == other.count()
        return False
>>> c1 = Counter()
>>> str(c1)
'< Counter: 0 >'
>>> c1.inc()
>>> c1
< Counter: 1 >
>>> c2 = Counter()
>>> c1 == c2
False
>>> c1.dec()
>>> c1 == c2
True
SimpleCounterクラスとCounterクラスを適宜補筆修正し、より安全かつ好ましいオブジェクトとなるよう設計を見直す

1) 両クラス共に、インスタンス生成の際に内部カウンタの初期値をオプショナル引数で設定できると便利です。
2) inc及びdecメソッドにインクリメント(デクリメント)の量をオプショナル引数で設定できると便利です。またその際、整数でないものが引数として渡されればエラーを発生させる仕様だと、なお親切です。
3) SimpleCounterクラスの内部カウンタのようなインスタンス変数は、継承先から直接参照できないほうが、クラス設計としては一般的に安全とされます。
4) Counterクラスのインスタンス同士の比較だけでなく、==演算子の右辺にSimpleCounterクラスのインスタンスもしくは整数が置かれた場合でも内部カウンタと比較できると便利です。

class SimpleCounter:
    @staticmethod
    def check_value(value):
        if not isinstance(value,int):
            raise ValueError(f"{value}は整数ではありません.")

    def __init__(self,value=0):
        SimpleCounter.check_value(value)
        self.__count = value

    def inc(self,value=1):
        SimpleCounter.check_value(value)
        self.__count += value

    def dec(self,value=1):
        SimpleCounter.check_value(value)
        self.__count -= value

    def count(self):
        return self.__count

class Counter(SimpleCounter):
    def __repr__(self):
        return f"<Counter:{self.count()}>"

    def __str__(self):
        return f"<Counter:{self.count()}>"

    def __eq__(self,other):
        if isinstance(other,SimpleCounter):
            return self.count() == other.count()
        elif isinstance(other,int):
            return self.count() == other
        return False
>>> Counter()
< Counter: 0 >
>>> Counter(100)
< Counter: 100 >
>>> c = Counter()
>>> c.inc(200)
>>> c
< Counter: 200 >
>>> c.inc()
>>> c
< Counter: 201 >
>>> c.dec(100)
>>> c
< Counter: 101 >
>>> c.dec(None)
→ エラー発生
>>> Counter(10) == Counter(10)
True
>>> Counter(10) == SimpleCounter(10)
True
>>> Counter(10) == 10
True

<2019/11/12追記>

クラスを使用しない場合のカウンター設計と注意点

1.グローバル変数を関数で操作

count = 0
def count_inc():
    global count # ※1
    count += 1

def count_dec():
    global count
    count -= 1

def count_get():
    global count
    return count

※1・・・pythonの場合、関数内でのブロックスコープはなく、ポインタもない為、定義済みの変数名を使用する場合かつグローバル変数を操作する場合は関数内でglobal修飾詞を付けて利用する。
また、関数内で同名の変数を定義した場合は関数名のnamespace内で定義された変数の扱いとなる。(グローバル変数とは別物)

※グローバル変数なので外部から変数の書き換えを容易に行えてしまう問題がある。
※1つのグローバル変数に対して、関数で操作しているので、複数のカウンターを扱いたいときなどの汎用性はない。

2.辞書を関数で操作

data_dic = {"cnt":0}
def cntdic_inc(dic):
    dic["cnt"] += 1

def cntdic_dec(dic):
    dic["cnt"] -= 1

def cntdic_get(dic):
    return dic["cnt"]

※Pythonのクラスは内部的に辞書で管理しているので、仕組みとしてはクラス定義での設計と同じようなことをしているが、こちらも辞書を直接操作可能であり、関数の引数に登録された辞書を取り込んで操作しているが、引数の型指定がないため、意図しないものが入れられる可能性がある。

<結論>
Pythonには構造体がなく、変数を格納、隠蔽にはクラスを使う必要がある。
オブジェクト指向に慣れ、安全な設計を行いましょう。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

This site uses Akismet to reduce spam. Learn how your comment data is processed.