Hatena::ブログ(Diary)

Soleil cou coupé

2012-05-23

Pythonのデコレータ(decorator)を理解する 1

StackOverFlow:Understanding Python decoratorsに対するe-satis氏によるデコレータの丁寧な解説。
一つエクスキューズしておくと、翻訳の作法とかよく分かってません。でも頑張ったつもり。つもり。

Imran氏による質問の内容

@makebold
@makeitalic
def say():
   return "Hello"

というデコレータに対して

<b><i>Hello</i></b>

という文字列を返すような関数ってどうやって書いたらいいの?

e-satis氏による解説

Pythonの関数はオブジェクトである

デコレータを理解するためには、まずPythonにおける関数がオブジェクトであることを理解しなければならない。このことは重大な影響をもたらす。なぜなのか簡単な例とともに見てみよう。

def shout(word="yes"):
    return word.capitalize()+"!"

print shout()
# => Yes!

関数は他のオブジェクトと同じように、変数に割り当てることもできる。

scream = shout

ここでカッコを使っていないことに注意しよう:関数を呼び出したわけではなく、screamという変数の中にshoutという関数を置いただけだ。それはすなわちscreamからshoutを呼び出せるようになった、ということを意味する:

print scream()
# => Yes!

更に、それ(関数がオブジェクトであるということ)は古い名前のshoutを削除しても、依然として関数にはscreamからアクセス可能であることを意味する。

del shout
try:
    print shout()
except NameError, e:
    print e
    # => "name 'shout' is not defined"

print scream()
# => 'Yes!'

オーケー、以上のことを覚えておいていてくれ、すぐに戻ってくるからね。さて、Pythonのもう一つの興味深い性質として、関数が定義できるということが挙げられる……別の関数の中で!

def talk():
    # talkの内側においてその場で関数を定義している…
    def whisper(word="yes"):
        return word.lower()+"...";

    # …そして直ちに実行するのだ!
    print whisper()

whisperは、talkの内部で呼び出されるさい、talkを呼び出すたびに毎回定義される。

talk()
# => yes...

しかしwhisperはtalkの外側では存在しない

try:
    print whisper()
except NameError, e:
    print e
    # => "name 'whisper' is not defined"*

関数の参照

オーケー、ついてきてるかい? ここからが楽しいパートだ。ここまで関数がオブジェクトであり、従って

  • 変数に割り当てることができる。
  • 他の関数の内側で定義することができる。

ということを見てきた。というわけで、関数は別の関数を返すこともできるんだ:-) まあ次のコードを見てみてよ:

def getTalk(type="shout"):

    # その場で関数を定義する
    def shout(word="yes"):
        return word.capitalize()+"!"

    def whisper(word="yes") :
        return word.lower()+"...";

    # それからいずれかの関数を返す
    if type == "shout":
        # "()"は使わない、関数を呼び出すわけではないのだ
        # 関数オブジェクトを返すだけなのだから
        return shout
    else:
        return whisper

# それにしてもどうやってこの奇妙なヤツを使うのか?

# 関数を得て変数に割り当てる
talk = getTalk()

# talkは関数オブジェクトであることがわかる:
print talk
# => <function shout at 0xb7ea817c>

# オブジェクトは関数によって返されたものだ:
print talk()
# => Yes!

# お望みなら荒っぽく直接呼び出すこともできる:
print getTalk("whisper")()
# => yes...

だけど待って、もっとあるんだ。関数を返すことができるということは、一つのパラメータとして渡すこともできるんだ。

def doSomethingBefore(func): 
    print "I do something before then I call the function you gave me"
    print func()

doSomethingBefore(scream)
# => I do something before then I call the function you gave me

さて、これでデコレータを理解するのに必要な全てが出揃った。デコレータはラッパー(wrapper)であり、関数それ自体を変更することなしに、実行前後のコードをデコレーションできるようにすることを意味する。

手作りデコレータ

どのようにデコレータを自作したらいいのだろう:

# デコレータは他の関数を引数として期待する関数である
def my_shiny_new_decorator(a_function_to_decorate):

    # 内部において、デコレータはその場で関数を定義する:ラッパーだ
    # この関数は元の関数をラップしようとしている
    # よって、それの前と後においてコードを実行することができる。
    def the_wrapper_around_the_original_function():

        # ここに元のコードの前に実行したいコードを書く
        # 関数が呼び出される
        print "Before the function runs"

        # ここで関数を呼ぶ (カッコを使ってね)
        a_function_to_decorate()

        # ここに元のコードの後に実行したいコードを書く
        # 関数が呼び出される
        print "After the function runs"

    # この時点ではa_function_to_decorateは実行されていない。
    # 我々はラッパー関数を作成しただけでそれをreturnする。
    # ラッパーには元の関数と前後に実行するコードが含まれている。
    # 使う準備は整っているのだ!
    return the_wrapper_around_the_original_function

# ここで以下のような二度と触れたくない関数を作成したと想像してみてくれ
def a_stand_alone_function():
    print "I am a stand alone function, don't you dare modify me"

a_stand_alone_function() 
#出力: I am a stand alone function, don't you dare modify me

# さて、こいつの振る舞いを拡張するためにデコレートすることができる。
# ただデコレータに引き渡すだけで、お望みのコードによって動的にラップし、
# 利用可能な新しい関数を返すのである。

a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#出力:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

今、あなたはおそらくa_stand_alone_functionを呼び出すたびに、代わりにa_stand_alone_function_decoratedを呼び出したいはずである。それは簡単だ。ただmy_shiny_new_decoratorによって返された関数でa_stand_alone_functionを上書きすればよい。

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#出力:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

# 分かったかな、これこそがまさしくデコレータの振る舞いなんだよ!

つづく:Pythonのデコレータ(decorator)を理解する 2

追記:質問者の名前が間違っていたのを修正。恥ずかしー。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証