みろりHP


緑色さんの多目的ブログ
みろりえいちぴー
ごゆるりとおくつろぎあさーせ。
<< マリー・セクストン『ロング・ゲイン』 | main | Python python3でウェブサーバを作る(Bottleでリトライ) >>
| カテゴリ:プログラミング |
Python デコレータ奮闘記



見たことない構文が目に留まった。
@property
def foo(self):
    なんちゃらかんちゃら
なんだこの@は? 調べたところによるとデコレータっていうものらしい。ひとつ覚えてみるかというわけで「1日1python」のお時間です。



どうもデコレータは機能ってよりプログラムの書き方の技術のひとつみたいだな。ある汎用的な関数に何か機能を追加したいときに用いる技術だ。上書きじゃなくて、追加。上書きしたいんだったら単に定義しなおせばいいわけだもんね。関数をまるごと上書きするんじゃもったいないから少しずつデコレーションして使おうってことな。ほんで、汎用的関数をデコレーションするために使う関数のことをデコレータっていうわけだ。そういうことなら話が早い。実際に書いてみるぜ。
今回の目次



クラスとインスタンスを使ったデコレータを書いてみる

まず汎用的なものを作る。
class Foo:
    def __init__(self, name):
        self.name = name

    def call(self):
        return self.name
名前を与えてインスタンスを作ってcall()を呼べば、その名前を返してくれるクラスね。以下のように使う。
name = 'William Forsyth'
instance = Foo(name)
print(instance.call())
# William Forsyth と表示される。

使ってるうちに、名前だけじゃちょっとさみしいから枠線をつけたくなった。枠線でデコレーションをする、Deco_Wakusenデコレータを作る。上のクラスの続きに記述する。
class Deco_Wakusen:
    def __init__(self, instance):
        self.instance = instance

    def call(self):
        ret = ''
        ret += '--------------------¥n'
        ret += '  ' + self.instance.call() + '¥n'
        ret += '--------------------'
        return ret
これは上で作ったFooのインスタンスを与えてインスタンスを作る。イメージとしてはチョコレートを糖衣で包む感じか。糖衣であるDeco_Wakusenにもcall()メソッドを準備してある。このcall()では元のFooインスタンスの結果を使いつつ、枠線をつけて返すようにしてある。別にクラスを継承してるわけじゃないから、上書きにもならない。デコレーションしてるだけ。以下のように使うと、上のとおんなじようにcallしてるはずなのにデコレーションされたものが飛び出してくるぜ。
instance_wakusen = Deco_Wakusen(instance)
print(instance_wakusen.call())
# 以下のように表示される
--------------------
  William Forsyth
--------------------

今度は枠線じゃなくて、名前を大文字にして表示したくなった。やることは同じだ。
class Deco_Oomoji:
    def __init__(self, instance):
        self.instance = instance

    def call(self):
        return self.instance.call().upper()
使い方は枠線のときと同じ。
instance_oomoji = Deco_Oomoji(instance)
print(instance_oomoji.call())
# WILLIAM FORSYTHと表示される。

それでデコレータの楽しいトコなんだけど、メソッドを上書きしてるわけじゃないから、続けざまにデコレーションをすると単純に機能が追加されていくのだよ。まず汎用物を作って、枠線デコレータでデコレーションして、さらに大文字デコレータでデコレーションしてみる。
instance_wakusen = Deco_Wakusen(instance)
instance_wakusen_oomoji = Deco_Oomoji(instance_wakusen)
print(instance_wakusen_oomoji.call())
# 以下のように表示される。
--------------------
  WILLIAM FORSYTH
--------------------
ビューリホー。新しい機能を使ってるわけでも、なんでもない。ただのロジックの組み方で面白い機構ができた。

上述の例ではわかりやすさのためいちいちインスタンス名を変えてたけれど、同じインスタンス名を使ってもOK。使用感がまったく変わらないままデコレートした結果が飛び出してくる。テンプレートとしてまとめるとこんな感じか?
class 汎用:
    def デコレーションされるメソッド(self):
        なんちゃらかんちゃら

class デコレータ:
    def __init__(self, instance):
        self.instance = instance

    def デコレーションするメソッドと同名のメソッド(self):
        self.instance.メソッド()を使用しつつ
        前後に機能を書き加えて結果をreturn

instance = 汎用()
instance = デコレータ(instance)
instance.デコレーションされたメソッド()



クラスはちと大袈裟だからふつーの関数で書いてみる

いやお前いちいちクラス使うことはねーだろということで関数でやってみる。
def call(name):
    return name
シンプルどころの話じゃない。
name = 'William Forsyth'
print(call(name))
# William Forsyth

枠線デコレータとしてはこういう関数を用意。
def deco_wakusen(func):
    def call_kari(name):
        ret = ''
        ret += '--------------------¥n'
        ret += '  ' + func(name) + '¥n'
        ret += '--------------------'
        return ret
    return call_kari
関数バージョンはクラスよりシンプルになるかと思いきや、ネストのある関数が登場しちまった。けれどやってることはクラスバージョンと同じで、「もともとの関数を引数にして、内部でその関数をデコレーションした関数を作成し、その関数を返」してるだけ。関数を呼び出してる側からすれば、「関数投げたら機能追加されて返ってきたわ」みたいな感じ。
call_wakusen = deco_wakusen(call)
print(call_wakusen(name))
# こうなる。
--------------------
  William Forsyth
--------------------

大文字デコレータ。
# 元の関数を投げて、
def deco_oomoji(func):
    # 元の関数を利用しつつデコレーションする関数を作って、
    def call_kari(name):
        return func(name).upper()
    # デコレート済みの関数を返す。
    return call_kari
使い方。
call_oomoji = deco_oomoji(call)
print(call_oomoji(name))
# WILLIAM FORSYTH

重ねがけ。
call_wakusen = deco_wakusen(call)
call_wakusen_oomoji = deco_oomoji(call_wakusen)
print(call_wakusen_oomoji(name))
# こうなる。
--------------------
  WILLIAM FORSYTH
--------------------

まとめるとこう。
def デコレーションされる汎用関数():
    なんちゃらかんちゃら

def デコレータ関数(func):
    def ここの名前はどうでもいい():
        funcを使用しつつ
        前後に機能を書き加えて結果をreturn
    作った関数をreturn

汎用関数 = デコレータ関数(汎用関数)
汎用関数()



@を使って書いてみる

おいデコレータ実現できちゃったじゃねーか。どこにも@が出てこないぞ、というわけなんだが、これは上述のスクリプトを簡単に書くために使えるようだ。具体的には以下。
@deco_wakusen
def call(name):
    return name
こういう風に書くと、call()関数がdeco_wakusenデコレータでデコレーションされたことになるようだ。大文字のほうも重ねがけするならこう。
@deco_wakusen
@deco_oomoji
def call(name):
    return name
直後にこう使える。
print(call(name))
# 結果は以下。
--------------------
  WILLIAM FORSYTH
--------------------

というわけでな、冒頭で俺の目に留まった記述は、関数foo()の中身を、どっかにあるproperty()という関数で加工しているんだよーということを表していたわけだ。これの直後にfoo()を実行したら、fooに書いてあることそのままじゃなくて、propertyでデコレーションされた結果が返ってくるのだろう。いやー、調べる前に実行していたらえらい混乱が俺を襲っていたぜ、ハッハッハ。
@property
def foo(self):
    なんちゃらかんちゃら

# 実行したら、「なんちゃらかんちゃら」じゃなくて
# property()で加工されたなんちゃらかんちゃらが
# 起こる。(たぶん)



さて、みっつの章に分けてまとめてみたわけだが、最初のふたつと@使用バージョンでは趣が異なるのが気になる。俺がデコレータという概念を調べて実際に試してみた「クラスバージョン」と「関数バージョン」は、「まず汎用的な関数があり、それをあとから目的に合わせて異なるパッチを作って当てていく」という状況想定で書いてある。いっぽう@でデコレーションを簡易化しちゃうぜバージョンは、「まず汎用的なデコレータがあり、それを必要に応じて装着していく」という状況を想定しているように見える。たとえばデコレータが「関数実行結果のログをとっておく」みたいな内容だとしたら断然後者のほうが使いやすいだろう。でも前者の登場機会だっておんなじくらいあるんじゃねえ? 俺が何に引っかかってるかっていうとさ、「汎用関数が最初にある」型と「汎用デコレータが最初にある」型のうち、後者にだけ@を使った省略的書き方があるのはなんでなんだろう? ってとこなんだよ。俺がデコレータという概念を知って自然に発想した型が前者だったから、余計にね。これについては特に答えが見つかんなかった。まあ当初の目的は果たせたのでグーとする。1日1pythonどころか、このスクリプト完成にこぎつけるのには一週間くらいかかっちゃったけどこれにて閉幕。




| 緑色 | プログラミング | comments(0) |
| カテゴリ:- |
スポンサーサイト
| スポンサードリンク | - | - |









 123456
78910111213
14151617181920
21222324252627
28293031   
<< May 2017 >>
+ みろりHP内検索
+ 閲覧記事
+ カテゴリ
+ 年月選択
  • 2017年 05月 (6)
  • 2017年 04月 (8)
  • 2017年 03月 (7)
  • 2017年 02月 (10)
  • 2017年 01月 (6)
  • 2016年 12月 (8)
  • 2016年 11月 (8)
  • 2016年 10月 (5)
  • 2016年 09月 (5)
  • 2016年 08月 (7)
  • 2016年 07月 (9)
  • 2016年 06月 (6)
  • 2016年 05月 (8)
  • 2016年 04月 (10)
  • 2016年 03月 (10)
  • 2016年 02月 (8)
  • 2016年 01月 (9)
  • 2015年 12月 (9)
  • 2015年 11月 (6)
  • 2015年 10月 (5)
  • 2015年 09月 (4)
  • 2015年 08月 (8)
  • 2015年 07月 (5)
  • 2015年 06月 (3)
  • 2015年 05月 (7)
  • 2015年 04月 (8)
  • 2015年 03月 (12)
  • 2015年 02月 (8)
  • 2015年 01月 (4)
  • 2014年 12月 (5)
  • 2014年 11月 (5)
  • 2014年 10月 (7)
  • 2014年 09月 (4)
  • 2014年 08月 (7)
  • 2014年 07月 (6)
  • 2014年 06月 (4)
  • 2014年 05月 (12)
  • 2014年 04月 (9)
  • 2014年 03月 (6)
  • 2014年 02月 (6)
  • 2014年 01月 (8)
  • 2013年 12月 (7)
  • 2013年 11月 (10)
  • 2013年 10月 (10)
  • 2013年 09月 (9)
  • 2013年 08月 (11)
  • 2013年 07月 (10)
  • 2013年 06月 (9)
  • 2013年 05月 (15)
  • 2013年 04月 (11)
  • 2013年 03月 (5)
  • 2013年 02月 (7)
  • 2013年 01月 (6)
  • 2012年 12月 (9)
  • 2012年 11月 (10)
  • 2012年 10月 (10)
  • 2012年 09月 (4)
  • 2012年 08月 (2)
  • 2012年 07月 (7)
  • 2012年 06月 (13)
  • 2012年 05月 (13)
  • 2012年 04月 (15)
  • 2012年 03月 (4)
  • 2012年 02月 (12)
  • 2012年 01月 (9)
  • 2011年 12月 (5)
  • 2011年 11月 (13)
  • 2011年 10月 (2)
  • 2011年 09月 (2)
  • 2011年 08月 (1)
  • 2011年 06月 (1)
  • 2011年 05月 (4)
  • 2011年 04月 (10)
  • 2011年 03月 (8)
  • 2011年 02月 (11)
  • 2011年 01月 (14)
  • 2010年 12月 (14)
  • 2010年 11月 (17)
  • 2010年 10月 (17)
  • 2010年 09月 (19)
  • 2010年 08月 (22)
  • 2010年 07月 (18)
  • 2010年 06月 (16)
  • 2010年 05月 (19)
  • 2010年 04月 (15)
  • 2010年 03月 (22)
  • 2010年 02月 (18)
  • 2010年 01月 (18)
  • 2009年 06月 (2)
  • 2009年 04月 (1)
  • 2007年 12月 (10)
  • 2007年 11月 (7)
  • 2007年 10月 (9)
  • 2007年 09月 (4)
  • 2007年 07月 (5)
  • 2007年 06月 (11)
  • 2007年 05月 (6)
  • 2007年 04月 (4)
  • + ブックマーク
    + 最近のコメント
    + アクセスカウンター
    全体(since 2010.02.03.)
    今日… 昨日…