静かなる名辞

pythonとプログラミングのこと


【python】使いやすい関数の呼び出し回数カウンタを考える

はじめに

 関数の呼び出し回数を数えたい、というシチュエーションはたまにあります。

 その都度、場当たり的にカウンタ変数を増やしたりして対処するのも、まあ、ありといえばありですが、使いやすいものを作るとしたらどうなるかな? というのを興味本位で書いてみました。

 あくまでも興味本位なので実用的な頑強性までは保証しません。

 なお、ある程度は以前書いた記事の

【python】呼び出し回数カウント関数を色々な方法で作る - 静かなる名辞

 を踏まえた内容になっているので、よろしければこちらも御覧ください。

要件

 「使いやすい」がコンセプトなので、最低限以下のような要件を抑えようと思います。

  • 関数内を書き換えない
  • 追加が容易
  • 関数でもクラスのメソッドなどでも同様に扱える
  • カウント回数を取得する、リセットするなどの機能があると嬉しい

 これは独自クラスのインスタンスを返すデコレータですかね。

作ってみる

 とりあえずカウントするためのクラスを作ります。

class Count:
    def __init__(self, func):
        self.count = 0
        self.func = func

    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.func(*args, **kwargs)

    def reset(self):
        self.count = 0

 最低限これだけあれば良いや。カウントは直接count属性を見て取得します。「ゲッターを作れ」「いやそこはプロパティで」といった指摘が飛んできそうですが、python的ではない気がするのと面倒くさいので(こちらが本音)、やりません。また、resetメソッドでカウンタを0にできます。

 クラスなので、必要なら幾らでも処理を追加できます(オーバーヘッドの許す限り)。

 さて、次にデコレータにするために、普通はこんなコードを書いてみるのではないでしょうか。

def count(f):
    return Count(f)

 ……要らないんじゃね? と思ってクラスをそのままデコレータにできないか試したら、いけました。なので、クラス名をcountにしてそのままデコレータとして使うことにしました。

スポンサーリンク



動かしてみる

 最終的にこんなコードになり、望み通りの結果が得られました。

class count:
    """
    カウントのためのクラス
    """
    def __init__(self, func):
        self.count = 0
        self.func = func

    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.func(*args, **kwargs)

    def reset(self):
        self.count = 0

@count
def f(x, y=None):
    """
    実験用関数
    """
    print("f:", x, y)

# 以下で確認
for i in range(2):
    for j in range(3+i):
        f(i, y=j)
        print("count:", f.count)
    f.reset()

""" 結果 =>
f: 0 0
count: 1
f: 0 1
count: 2
f: 0 2
count: 3
f: 1 0
count: 1
f: 1 1
count: 2
f: 1 2
count: 3
f: 1 3
count: 4
"""

 問題なさげ。できました。

まとめ

 以前の記事と見比べると、カウンタそのものを実装するのと比べて大した手間はかかりません。それでいて汎用的です。高階関数が扱えてデコレータのある言語は良いですね(ってそれpythonだけじゃん)。

 また、クラスが直接デコレータに使えるのは新たな発見でした。これを使っているものはあるのだろうか?(クラスだけだと引数を取れないデコレータになるので、いろいろと厳しいかもしれません) これについてはもう少し追求してみたいです。