デコレータ - 静かなる名辞
pythonとプログラミングのこと
2020-05-07T20:42:34+09:00
hayataka2049
Hatena::Blog
hatenablog://blog/10328537792367869878
【python】クラスでデコレータ!
hatenablog://entry/10257846132706875828
2019-01-17T05:53:22+09:00
2019-09-05T02:54:22+09:00 デコレータといえば関数で作るものだと思っている人も大勢いると思いますが、クラスでも__call__メソッドを実装すればクラスインスタンスはcallableになり、呼び出しできるので、デコレータたりえます。
通常のデコレータ並みに高機能なものが作れるのかどうか追求してみます。
<div class="section">
<h3 id="はじめに">はじめに</h3>
<p> デコレータといえば関数で作るものだと思っている人も大勢いると思いますが、クラスでも__call__メソッドを実装すればクラスインスタンスはcallableになり、呼び出しできるので、デコレータたりえます。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time
<span class="synStatement">class</span> <span class="synIdentifier">deco</span>:
<span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, f):
self.f = f
<span class="synStatement">def</span> <span class="synIdentifier">__call__</span>(self, *args, **kwargs):
t1 = time.time()
ret = self.f(*args, **kwargs)
t2 = time.time()
<span class="synIdentifier">print</span>(<span class="synConstant">"deco:"</span>, t2-t1)
<span class="synStatement">return</span> ret
<span class="synPreProc">@</span><span class="synIdentifier">deco</span>
<span class="synStatement">def</span> <span class="synIdentifier">f</span>(message):
<span class="synIdentifier">print</span>(<span class="synConstant">"f:"</span>, message)
f(<span class="synConstant">"hoge"</span>)
<span class="synConstant">""" =></span>
<span class="synConstant">f: hoge</span>
<span class="synConstant">deco: 6.508827209472656e-05</span>
<span class="synConstant">"""</span>
</pre><p> このことには<a href="https://www.haya-programming.com/entry/2019/01/17/050411">先に書いた記事</a>で気づいたのですが、もう少し追求してみます。</p><p> 目次</p>
<ul class="table-of-contents">
<li><a href="#はじめに">はじめに</a></li>
<li><a href="#デコレータの種類ごとに実装してみる">デコレータの種類ごとに実装してみる</a><ul>
<li><a href="#引数なしでラッパー関数を返すデコレータ">引数なしでラッパー関数を返すデコレータ</a></li>
<li><a href="#引数ありでラッパー関数を返さない場合">引数ありでラッパー関数を返さない場合</a></li>
<li><a href="#引数ありでラッパー関数を返す場合">引数ありでラッパー関数を返す場合</a></li>
</ul>
</li>
<li><a href="#考察">考察</a><ul>
<li><a href="#どうしてこの書き方が一般的ではないの">どうしてこの書き方が一般的ではないの?</a></li>
<li><a href="#利点は">利点は?</a></li>
<li><a href="#欠点は">欠点は?</a></li>
<li><a href="#総評">総評</a></li>
</ul>
</li>
<li><a href="#まとめ">まとめ</a></li>
<li><a href="#追記">追記</a></li>
</ul>
</div>
<div class="section">
<h3 id="デコレータの種類ごとに実装してみる">デコレータの種類ごとに実装してみる</h3>
<p> こちらの記事によると、デコレータは4種類に分類できます。<br />
<iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fmacinjoke%2Fitems%2F1be6cf0f1f238b5ba01b" title="Python デコレータ再入門 ~デコレータは種類別に覚えよう~ - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://qiita.com/macinjoke/items/1be6cf0f1f238b5ba01b">Python デコレータ再入門 ~デコレータは種類別に覚えよう~ - Qiita</a></p><p> 以下の2項目の組み合わせによる</p>
<ul>
<li>引数の有無</li>
<li>ラッパー関数を返すか否か</li>
</ul><p> ただし、引数なしでラッパー関数を返さないデコレータは簡単すぎて意味がない、としているので3種類考えることにします。</p><p> これらすべてを実装できたらすごいですね。ということで、実装していきます。なお、以下で上記記事からの引用コードを一部示します。</p><p><span style="font-size: 80%">スポンサーリンク</span><br />
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script></p>
<p><ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-6261827798827777"
data-ad-slot="1744230936"
data-ad-format="auto"
data-full-width-responsive="true"></ins><br />
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script></p><br />
<p></p>
<div class="section">
<h4 id="引数なしでラッパー関数を返すデコレータ">引数なしでラッパー関数を返すデコレータ</h4>
<blockquote>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">args_logger</span>(f):
<span class="synStatement">def</span> <span class="synIdentifier">wrapper</span>(*args, **kwargs):
f(*args, **kwargs)
<span class="synIdentifier">print</span>(<span class="synConstant">'args: {}, kwargs: {}'</span>.format(args, kwargs))
<span class="synStatement">return</span> wrapper
</pre><p>(引用)</p>
</blockquote>
<p> これはクラスのインスタンス自身をラッパー関数として取り扱えば簡単です。冒頭のコードもそれです。もうやったので除外。</p>
</div>
<div class="section">
<h4 id="引数ありでラッパー関数を返さない場合">引数ありでラッパー関数を返さない場合</h4>
<blockquote>
<pre class="code lang-python" data-lang="python" data-unlink>funcs = []
<span class="synStatement">def</span> <span class="synIdentifier">appender</span>(*args, **kwargs):
<span class="synStatement">def</span> <span class="synIdentifier">decorator</span>(f):
<span class="synComment"># args や kwargsの内容によって処理内容を変えたり変えなかったり</span>
<span class="synIdentifier">print</span>(args)
<span class="synStatement">if</span> kwargs.get(<span class="synConstant">'option1'</span>):
<span class="synIdentifier">print</span>(<span class="synConstant">'option1 is True'</span>)
<span class="synComment"># 元の関数をfuncsに追加</span>
funcs.append(f)
<span class="synStatement">return</span> decorator
</pre><p>(引用)</p>
</blockquote>
<p> 簡単そうです。</p>
<pre class="code lang-python" data-lang="python" data-unlink>funcs = []
<span class="synStatement">class</span> <span class="synIdentifier">deco</span>:
<span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, cond):
self.cond = cond
<span class="synStatement">def</span> <span class="synIdentifier">__call__</span>(self, f):
<span class="synIdentifier">print</span>(self.cond) <span class="synComment"># 条件によって処理を変えたり</span>
funcs.append(f)
<span class="synPreProc">@</span><span class="synIdentifier">deco</span>(<span class="synConstant">"aaa"</span>)
<span class="synStatement">def</span> <span class="synIdentifier">hoge</span>(message):
<span class="synIdentifier">print</span>(<span class="synConstant">"hoge:"</span>, message)
<span class="synPreProc">@</span><span class="synIdentifier">deco</span>(<span class="synConstant">"bbb"</span>)
<span class="synStatement">def</span> <span class="synIdentifier">fuga</span>(message):
<span class="synIdentifier">print</span>(<span class="synConstant">"fuga:"</span>, message)
<span class="synStatement">for</span> f <span class="synStatement">in</span> funcs:
f(<span class="synConstant">"called"</span>)
<span class="synConstant">""" =></span>
<span class="synConstant">aaa</span>
<span class="synConstant">bbb</span>
<span class="synConstant">hoge: called</span>
<span class="synConstant">fuga: called</span>
<span class="synConstant">"""</span>
</pre><p> できた。</p>
</div>
<div class="section">
<h4 id="引数ありでラッパー関数を返す場合">引数ありでラッパー関数を返す場合</h4>
<blockquote>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">args_joiner</span>(*dargs, **dkwargs):
<span class="synStatement">def</span> <span class="synIdentifier">decorator</span>(f):
<span class="synStatement">def</span> <span class="synIdentifier">wrapper</span>(*args, **kwargs):
newargs = dargs + args <span class="synComment"># リストの結合</span>
newkwargs = {**kwargs, **dkwargs} <span class="synComment"># 辞書の結合 (python3.5以上で動く)</span>
f(*newargs, **newkwargs)
<span class="synStatement">return</span> wrapper
<span class="synStatement">return</span> decorator
</pre><p>(引用)</p>
</blockquote>
<p> ちょっとむずかしそうですかね。デコレータ自身の引数は__init__で受け取ることになります。次に__call__が関数を受け取るしかないのですが、更にwrapperがないと関数そのものの引数が取り扱えません。</p><p> まあ、wrapperもメソッドで作れるので、実は難しいと見せかけて楽勝なのですが。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">deco</span>:
<span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, decomessage):
self.decome = decomessage
<span class="synStatement">def</span> <span class="synIdentifier">__call__</span>(self, f):
self.f = f
<span class="synStatement">return</span> self.wrapper
<span class="synStatement">def</span> <span class="synIdentifier">wrapper</span>(self, *args, **kwargs):
<span class="synIdentifier">print</span>(<span class="synConstant">"deco:"</span>, self.decome)
<span class="synStatement">return</span> self.f(*args, **kwargs)
<span class="synPreProc">@</span><span class="synIdentifier">deco</span>(<span class="synConstant">"hoge"</span>)
<span class="synStatement">def</span> <span class="synIdentifier">func</span>(x):
<span class="synIdentifier">print</span>(<span class="synConstant">"func:"</span>, x)
func(<span class="synConstant">"foo"</span>)
<span class="synPreProc">@</span><span class="synIdentifier">deco</span>(<span class="synConstant">"fuga"</span>)
<span class="synStatement">def</span> <span class="synIdentifier">func</span>(x):
<span class="synIdentifier">print</span>(<span class="synConstant">"func:"</span>, x)
func(<span class="synConstant">"bar"</span>)
<span class="synPreProc">@</span><span class="synIdentifier">deco</span>(<span class="synConstant">"piyo"</span>)
<span class="synStatement">def</span> <span class="synIdentifier">func</span>(x):
<span class="synIdentifier">print</span>(<span class="synConstant">"func:"</span>, x)
func(<span class="synConstant">"baz"</span>)
<span class="synConstant">""" =></span>
<span class="synConstant">deco: hoge</span>
<span class="synConstant">func: foo</span>
<span class="synConstant">deco: fuga</span>
<span class="synConstant">func: bar</span>
<span class="synConstant">deco: piyo</span>
<span class="synConstant">func: baz</span>
<span class="synConstant">"""</span>
</pre><p> ということで、ぜんぶできました。よって、クラスもデコレータになれますね。</p>
</div>
</div>
<div class="section">
<h3 id="考察">考察</h3>
<p> 思いついたことをつらつらと考察します。</p>
<div class="section">
<h4 id="どうしてこの書き方が一般的ではないの">どうしてこの書き方が一般的ではないの?</h4>
<p> 単純に__call__を使うのがキモいからだと思います。。。</p>
</div>
<div class="section">
<h4 id="利点は">利点は?</h4>
<p> 幾つか考えられます。</p>
<ul>
<li>関数定義のネストが深くならない。</li>
<li>通常の関数によるデコレータでは状態をコールスタック上で管理するが(クロージャ)、クラスで書くと明示的にselfの状態として取り扱える。</li>
</ul><p> 以上2点によって、人によっては可読性が高いと感じる可能性があります。</p>
<ul>
<li>各種拡張が行いやすい</li>
</ul><p> 通常のデコレータはいかんせんクロージャなので、あまり大規模なものの実装は避けると思います。この方法だと何しろクラスなので、その気になればなんでも作れるでしょう。まあ、クラスを他に定義しておいて普通のデコレータでそれのインスタンスを返しても良い訳ですが。</p><p> まったく利点がない訳ではないということです。</p>
</div>
<div class="section">
<h4 id="欠点は">欠点は?</h4>
<ul>
<li>とりあえずキモい。</li>
<li>更に一般的でもない。</li>
<li>そもそも濫用しすぎ。</li>
<li>決定的な利点がある訳ではないので、上記欠点がむしろ目立つ。</li>
</ul><p> うーん、苦しいかな。</p>
</div>
<div class="section">
<h4 id="総評">総評</h4>
<p> 面白いけど、これを使ったコードを書いて他人に渡すのは嫌がらせに近いかもしれない。<br />
(いやでも、普通のデコレータもなかなか読みづらいけどなぁ)</p>
</div>
</div>
<div class="section">
<h3 id="まとめ">まとめ</h3>
<p> とにかくできることはわかりました。「ここが駄目!」とか「こんなふうに活用できるね!」などの意見がありましたら、遠慮なくコメントに書いてください。</p>
</div>
<div class="section">
<h3 id="追記">追記</h3>
<p> pandasのコードを読んでいたら、実際に使っている例を見かけました。</p><p><a href="https://github.com/pandas-dev/pandas/blob/master/pandas/util/_decorators.py#L223">pandas/_decorators.py at master · pandas-dev/pandas · GitHub</a></p><p> やるときはけっこうやるんですね。</p>
</div>
hayataka2049
【python】使いやすい関数の呼び出し回数カウンタを考える
hatenablog://entry/10257846132706867574
2019-01-17T05:04:11+09:00
2019-09-05T02:55:03+09:00 関数の呼び出し回数を数えたい、というシチュエーションはたまにあります。その都度、場当たり的にカウンタ変数を増やしたりして対処するのも、まあ、ありといえばありですが、使いやすいものを作るとしたらどうなるかな? というのを興味本位で書いてみました。
<div class="section">
<h3>はじめに</h3>
<p> 関数の呼び出し回数を数えたい、というシチュエーションはたまにあります。</p><p> その都度、場当たり的にカウンタ変数を増やしたりして対処するのも、まあ、ありといえばありですが、使いやすいものを作るとしたらどうなるかな? というのを興味本位で書いてみました。</p><p> あくまでも興味本位なので実用的な頑強性までは保証しません。</p><p> なお、ある程度は以前書いた記事の</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.haya-programming.com%2Fentry%2F2018%2F10%2F22%2F003414" title="【python】呼び出し回数カウント関数を色々な方法で作る - 静かなる名辞" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><a href="https://www.haya-programming.com/entry/2018/10/22/003414">【python】呼び出し回数カウント関数を色々な方法で作る - 静かなる名辞</a></p><p> を踏まえた内容になっているので、よろしければこちらも御覧ください。</p>
</div>
<div class="section">
<h3>要件</h3>
<p> 「使いやすい」がコンセプトなので、最低限以下のような要件を抑えようと思います。</p>
<ul>
<li>関数内を書き換えない</li>
<li>追加が容易</li>
<li>関数でもクラスのメソッドなどでも同様に扱える</li>
<li>カウント回数を取得する、リセットするなどの機能があると嬉しい</li>
</ul><p> これは独自クラスのインスタンスを返すデコレータですかね。</p>
</div>
<div class="section">
<h3>作ってみる</h3>
<p> とりあえずカウントするためのクラスを作ります。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">Count</span>:
<span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, func):
self.count = <span class="synConstant">0</span>
self.func = func
<span class="synStatement">def</span> <span class="synIdentifier">__call__</span>(self, *args, **kwargs):
self.count += <span class="synConstant">1</span>
<span class="synStatement">return</span> self.func(*args, **kwargs)
<span class="synStatement">def</span> <span class="synIdentifier">reset</span>(self):
self.count = <span class="synConstant">0</span>
</pre><p> 最低限これだけあれば良いや。カウントは直接count属性を見て取得します。「ゲッターを作れ」「いやそこはプロパティで」といった指摘が飛んできそうですが、python的ではない気がするのと面倒くさいので(こちらが本音)、やりません。また、resetメソッドでカウンタを0にできます。</p><p> クラスなので、必要なら幾らでも処理を追加できます(オーバーヘッドの許す限り)。</p><p> さて、次にデコレータにするために、普通はこんなコードを書いてみるのではないでしょうか。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">count</span>(f):
<span class="synStatement">return</span> Count(f)
</pre><p> ……要らないんじゃね? と思ってクラスをそのままデコレータにできないか試したら、いけました。なので、クラス名をcountにしてそのままデコレータとして使うことにしました。</p><p><span style="font-size: 80%">スポンサーリンク</span><br />
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script></p>
<p><ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-6261827798827777"
data-ad-slot="1744230936"
data-ad-format="auto"
data-full-width-responsive="true"></ins><br />
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script></p><br />
<p></p>
</div>
<div class="section">
<h3>動かしてみる</h3>
<p> 最終的にこんなコードになり、望み通りの結果が得られました。</p>
<pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">count</span>:
<span class="synConstant">"""</span>
<span class="synConstant"> カウントのためのクラス</span>
<span class="synConstant"> """</span>
<span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, func):
self.count = <span class="synConstant">0</span>
self.func = func
<span class="synStatement">def</span> <span class="synIdentifier">__call__</span>(self, *args, **kwargs):
self.count += <span class="synConstant">1</span>
<span class="synStatement">return</span> self.func(*args, **kwargs)
<span class="synStatement">def</span> <span class="synIdentifier">reset</span>(self):
self.count = <span class="synConstant">0</span>
<span class="synPreProc">@</span><span class="synIdentifier">count</span>
<span class="synStatement">def</span> <span class="synIdentifier">f</span>(x, y=<span class="synIdentifier">None</span>):
<span class="synConstant">"""</span>
<span class="synConstant"> 実験用関数</span>
<span class="synConstant"> """</span>
<span class="synIdentifier">print</span>(<span class="synConstant">"f:"</span>, x, y)
<span class="synComment"># 以下で確認</span>
<span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">2</span>):
<span class="synStatement">for</span> j <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">3</span>+i):
f(i, y=j)
<span class="synIdentifier">print</span>(<span class="synConstant">"count:"</span>, f.count)
f.reset()
<span class="synConstant">""" 結果 =></span>
<span class="synConstant">f: 0 0</span>
<span class="synConstant">count: 1</span>
<span class="synConstant">f: 0 1</span>
<span class="synConstant">count: 2</span>
<span class="synConstant">f: 0 2</span>
<span class="synConstant">count: 3</span>
<span class="synConstant">f: 1 0</span>
<span class="synConstant">count: 1</span>
<span class="synConstant">f: 1 1</span>
<span class="synConstant">count: 2</span>
<span class="synConstant">f: 1 2</span>
<span class="synConstant">count: 3</span>
<span class="synConstant">f: 1 3</span>
<span class="synConstant">count: 4</span>
<span class="synConstant">"""</span>
</pre><p> 問題なさげ。できました。</p>
</div>
<div class="section">
<h3>まとめ</h3>
<p> <a href="https://www.haya-programming.com/entry/2018/10/22/003414">以前の記事</a>と見比べると、カウンタそのものを実装するのと比べて大した手間はかかりません。それでいて汎用的です。高階関数が扱えてデコレータのある言語は良いですね(ってそれpythonだけじゃん)。</p><p> また、クラスが直接デコレータに使えるのは新たな発見でした。これを使っているものはあるのだろうか?(クラスだけだと引数を取れないデコレータになるので、いろいろと厳しいかもしれません) これについてはもう少し追求してみたいです。</p>
</div>
hayataka2049