昨日からデコレータについて色々テストコードを書きながら調べてて分かった事です。デコレータに限らない部分も多々。
昨日書いたデコレータの話で、デコレータ内で状態を保持するために、「f.call_count = -1」みたいに書いたけど、本当は普通に考えて以下のように書きたかった。
def negligence(n): def _(f): call_count = -1 def __(*args, **kwargs): call_count += 1 if call_count % n == 0: call_count = 0 return f(*args, **kwargs) return __ return _
でもコレだと関数の実行時”__(*args, **kwargs)”の実行時にUnboundLocalError例外が発生します。
これは”__(*args, **kwargs)”の最初の”call_count += 1″でブロック外の変数の参照と代入が行われるからです。
ブロック内で代入だけ(ローカル変数の作成)や参照だけ(上位ブロックの変数の参照)や代入後参照(ローカル変数の利用)なら例外は発生しません。
参照後代入で例外が発生します。(詳細)
UnboundLocalErrorを発生させないようにするには例えば以下のように書きます。
def negligence(n): def _(f): status = dict(call_count = -1) def __(*args, **kwargs): status['call_count'] += 1 if status['call_count'] % n == 0: status['call_count'] = 0 return f(*args, **kwargs) return __ return _
この例では”__(*args, **kwargs)”の一つ上のブロックで定義されているstatusという変数に直接代入してないためうまく動きます。
話はちょっと変わりますが、昨日書いた記事でcallableオブジェクトを返すデコレータのパターンを失敗作として紹介しましたが、その逆も可能なんではないかと思い試してみました。
つまり、「callableオブジェクトを返す”関数で実装された”デコレータ」の逆の「関数を返す”クラスで実装された”デコレータ」です。
class negligence(object): def __init__(self, n): self.__n = n self.__call_count = -1 def __call__(self, f): self.__f = f def _(*args, **kwargs): self.__call_count += 1 if self.__call_count % self.__n == 0: self.__call_count = 0 return self.__f(*args, **kwargs)
これも正常に動作しました。
何も目新しいパターンではないと思いますが、状態を保持する必要があるデコレータではこちらを使った方が混乱は少ないかも。
あと、昨日書いた失敗作(callableオブジェクトを返すパターンのデコレータ)がメソッドに適用するとうまく動作しない理由をもうちょっと。
class文のブロック直下の関数は通常インスタンス化されたタイミングでbound method化するけど、callableオブジェクトはインスタンス化されてもcallableオブジェクトのままなため、bound methodでは当然受け取れるはずの第一引数のselfが受け取れないという事になるみたい。
最近のコメント
名前
しゅごい
Jane Doe
FYI Avoid Annoying Unexpe…
Jane Doe
ご存じとは思いますが、whileには、”~の間”と…
peta_okechan
針金みたいなパーツを引っ張ると外れます。 他の方の…
虎徹ファン交換
虎徹の標準ファンを外す際に、どのようにして外されま…
花粉症対策2019 – 日曜研究室
[…] 花粉症対策についてはこれまで次の記事を書いてきました。https://…
花粉症対策2019 – 日曜研究室
[…] 花粉症対策についてはこれまで次の記事を書いてきました。https://…
花粉症対策2019 – 日曜研究室
[…] 花粉症対策についてはこれまで次の記事を書いてきました。https://…
花粉症対策2019 – 日曜研究室
[…] 花粉症対策についてはこれまで次の記事を書いてきました。https://…
花粉症対策2019 – 日曜研究室
[…] 花粉症対策についてはこれまで次の記事を書いてきました。https://…