デコレータ(Decorators)は Python の重要な一部です。簡単に言うと、他の関数の機能を変更する関数です。コードをより短く、より Pythonic( Python らしく)にしてくれます。多くの初心者はどこで使えばよいか分かりません。そこで、デコレータがコードをより簡潔にできる領域を共有しようと思います。まず、独自のデコレータの書き方について説明しましょう。

これはおそらく最も習得が難しい概念の一つです。完全に理解できるように、一度にひとつのステップずつ説明します。

すべてがオブジェクト

まず Python における関数を理解しましょう。

def hi(name="yasoob"): return "hi " + name print(hi()) # output: 'hi yasoob' # 関数を変数に代入することもできます。たとえば greet = hi # ここでは括弧を使っていません。hi関数を呼び出しているのではなく # greet変数に格納しているだけです。これを実行してみましょう。 print(greet()) # output: 'hi yasoob' # 古いhi関数を削除すると、何が起こるか見てみましょう! del hi print(hi()) #outputs: NameError print(greet()) #outputs: 'hi yasoob'

関数の中で関数を定義する

以上が関数の基本知識です。知識をさらに一歩進めましょう。Python では関数の中に別の関数を定義できます:

def hi(name="yasoob"): print("now you are inside the hi() function") def greet(): return "now you are in the greet() function" def welcome(): return "now you are in the welcome() function" print(greet()) print(welcome()) print("now you are back in the hi() function") hi() #output:now you are inside the hi() function # now you are in the greet() function # now you are in the welcome() function # now you are back in the hi() function # 上記は、いつでもhi()を呼び出すと、greet()とwelcome()が同時に呼び出されることを示しています。 # そして、greet()とwelcome()関数はhi()関数の外からはアクセスできません。たとえば: greet() #outputs: NameError: name 'greet' is not defined

これで、関数の中に別の関数を定義できることが分かりました。つまり、ネストされた関数を作成できるのです。次に学ぶ必要があるのは、関数も関数を返すことができるということです。

関数から関数を返す

実際には、ある関数の中で別の関数を実行する必要はなく、出力として返すこともできます:

def hi(name="yasoob"): def greet(): return "now you are in the greet() function" def welcome(): return "now you are in the welcome() function" if name == "yasoob": return greet else: return welcome a = hi() print(a) #outputs: <function greet at 0x7f2143c01500> # 上記は、`a`がhi()関数内のgreet()関数を指すようになったことを明確に示しています。 # これを試してみてください print(a()) #outputs: now you are in the greet() function

もう一度このコードを見てみましょう。if/else文ではgreet()やwelcome()ではなく、greetとwelcomeを返しています。なぜでしょうか?これは、後ろに括弧を付けると関数が実行されるからです。しかし、括弧を付けなければ、関数は実行されずにどこへでも渡したり、他の変数に代入したりできます。分かりましたか?もう少し詳しく説明します。

  • 私たちが次のように書くとa = hi(),hi()が実行され、name 引数のデフォルトが yasoob であるため、関数greet()が返されます。

  • さらに、次のものを出力することもできます。hi()()、これにより次のように出力されます:now you are in the greet() function。

  • もし文を次のように変更するとa = hi(name = "ali")、その場合welcome()関数が返されます。

関数を別の関数の引数として渡す

def hi(): return "hi yasoob!" def doSomethingBeforeHi(func): print("I am doing some boring work before executing hi()") print(func()) doSomethingBeforeHi(hi) #outputs:I am doing some boring work before executing hi() # hi yasoob!

これで、デコレータが実際に何であるかをさらに学ぶために必要な知識がすべて揃いました。デコレータを使うと、関数の前後でコードを実行できます。

あなたの最初のデコレータ

前の例で、実際にはすでにデコレータを作成していました!次に、前のデコレータを修正して、もう少し役立つプログラムを作成しましょう:

def a_new_decorator(a_func): def wrapTheFunction(): print("I am doing some boring work before executing a_func()") a_func() print("I am doing some boring work after executing a_func()") return wrapTheFunction def a_function_requiring_decoration(): print("I am the function which needs some decoration to remove my foul smell") a_function_requiring_decoration() #outputs: "I am the function which needs some decoration to remove my foul smell" a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration) #now a_function_requiring_decoration is wrapped by wrapTheFunction() a_function_requiring_decoration() #outputs:I am doing some boring work before executing a_func() # I am the function which needs some decoration to remove my foul smell # I am doing some boring work after executing a_func()

分かりましたか?私たちは今学んだ原理を適用しました。これこそがPythonのデコレータが行うことです!デコレータは関数をラップし、何らかの方法でその動作を変更します。さて、私たちはコードの中で特定の記号を使っていないことに疑問を感じているかもしれません。@その記号?それは、デコレートされた関数を生成するための短い方法です。ここで、私たちがどのように@それを使って前のコードを実行するかを示します:

@a_new_decorator def a_function_requiring_decoration(): """Hey you! Decorate me!""" print("I am the function which needs some decoration to " "remove my foul smell") a_function_requiring_decoration() #outputs: I am doing some boring work before executing a_func() # I am the function which needs some decoration to remove my foul smell # I am doing some boring work after executing a_func() #the @a_new_decorator is just a short way of saying: a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

これで Python デコレータの動作原理について基本的な理解が得られたことを願います。次のコードを実行すると、ひとつの問題が発生します:

print(a_function_requiring_decoration.__name__)
# Output: wrapTheFunction

これは私たちが望んでいるものではありません!出力(Ouput)は"a_function_requiring_decoration"であるべきです。ここでの関数はwarpTheFunctionに置き換えられています。それは私たちの関数の名前とドキュメント文字列(docstring)を書き換えてしまいます。幸いなことに、Python にはこの問題を解決する簡単な関数が用意されており、それはfunctools.wrapsです。前の例を修正して functools.wraps を使いましょう:

from functools import wraps def a_new_decorator(a_func): @wraps(a_func) def wrapTheFunction(): print("I am doing some boring work before executing a_func()") a_func() print("I am doing some boring work after executing a_func()") return wrapTheFunction @a_new_decorator def a_function_requiring_decoration(): """Hey yo! Decorate me!""" print("I am the function which needs some decoration to " "remove my foul smell") print(a_function_requiring_decoration.__name__) # Output: a_function_requiring_decoration

これでずっと良くなりました。次に、デコレータの一般的な使用シーンを学びましょう。

ブループリントの基本形:

from functools import wraps def decorator_name(f): @wraps(f) def decorated(*args, **kwargs): if not can_run: return "Function will not run" return f(*args, **kwargs) return decorated @decorator_name def func(): return("Function is running") can_run = True print(func()) # Output: Function is running can_run = False print(func()) # Output: Function will not run

注意:@wraps装飾する関数を受け取り、関数名、ドキュメント文字列、引数リストなどをコピーする機能を追加します。これにより、デコレータ内部で装飾前の関数の属性にアクセスできます。


使用シーン

ここでは、デコレータが特に優れている点と、それを使うことでいくつかのことをより簡単に管理できるようになる点を見てみましょう。

認可(Authorization)

デコレータは、誰かがWebアプリケーションのエンドポイントを使用する権限を持っているかを確認するのに役立ちます。Flask や Django の Web フレームワークで広く使われています。以下は、デコレータベースの認可を使用する例です:

from functools import wraps def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): authenticate() return f(*args, **kwargs) return decorated

ログ(Logging)

ログはデコレータが活躍するもうひとつの場面です。次に例を示します:

from functools import wraps def logit(func): @wraps(func) def with_logging(*args, **kwargs): print(func.__name__ + " was called") return func(*args, **kwargs) return with_logging @logit def addition_func(x): """Do some math.""" return x + x result = addition_func(4) # Output: addition_func was called

あなたはすでにデコレータの別の賢い使い方を考えているに違いありません。


引数を持つデコレータ

この問題を考えてみましょう。@wraps もデコレータではありませんか?しかし、@wraps は通常の関数ができるように、ひとつの引数を受け取ります。では、なぜ私たちも同じようにしないのでしょうか?それは、@my_decorator 構文を使用するとき、単一の関数を引数として受け取るラッパー関数を適用しているからです。Python ではすべてのものがオブジェクトであり、関数も含まれることを覚えておいてください。これを踏まえて、ラッパー関数を返す関数を作成できます。

関数内にデコレータを埋め込む

ログの例に戻り、出力先のログファイルを指定できるラッパー関数を作成しましょう。

from functools import wraps def logit(logfile='out.log'): def logging_decorator(func): @wraps(func) def wrapped_function(*args, **kwargs): log_string = func.__name__ + " was called" print(log_string) # logfileを開いて、内容を書き込みます with open(logfile, 'a') as opened_file: # 指定されたlogfileにログを書き込みます opened_file.write(log_string + '\n') return func(*args, **kwargs) return wrapped_function return logging_decorator @logit() def myfunc1(): pass myfunc1() # Output: myfunc1 was called # これで out.log というファイルが作成され、その中身は上記の文字列になります @logit(logfile='func2.log') def myfunc2(): pass myfunc2() # Output: myfunc2 was called # これで func2.log というファイルが作成され、その中身は上記の文字列になります

デコレータクラス

これで、本番環境で使用できる logit デコレータができました。しかし、アプリケーションの一部がまだ脆弱な場合、例外はより緊急な対応が必要になることがあります。たとえば、単にログをファイルに書き出したいだけのこともあれば、注意を引く問題をメールで送信しつつ、ログも残しておきたいこともあります。これは継承を使用する場面ですが、これまでにデコレータを構築するために使用する関数しか見てきませんでした。

幸いなことに、クラスを使用してデコレータを構築することもできます。では、関数ではなくクラスとして logit を再構築しましょう。

from functools import wraps class logit(object): def __init__(self, logfile='out.log'): self.logfile = logfile def __call__(self, func): @wraps(func) def wrapped_function(*args, **kwargs): log_string = func.__name__ + " was called" print(log_string) # logfileを開いて書き込みます with open(self.logfile, 'a') as opened_file: # 指定されたファイルにログを書き込みます opened_file.write(log_string + '\n') # 次に、通知を送信します self.notify() return func(*args, **kwargs) return wrapped_function def notify(self): # logitはログを取るだけで、他には何もしません pass

この実装には、ネストされた関数を使用する方法よりもすっきりしているという利点があります。また、関数をラップする際は以前と同じ構文を使用します:

@logit()
def myfunc1():
    pass

次に、logit のサブクラスを作成して、メールの機能を追加します(ただし、メールの話題はここでは扱いません)。

class email_logit(logit): '''関数呼び出し時に管理者へメールを送信できる logit の実装バージョン''' def __init__(self, email='admin@myproject.com', *args, **kwargs): self.email = email super(email_logit, self).__init__(*args, **kwargs) def notify(self): # self.emailにメールを送信します # ここでは実装しません pass

今後、@email_logit は @logit と同じ効果に加えて、ログに加えて管理者にメールも送信します。

原文URL:https://eastlakeside.gitbooks.io/interpy-zh/content/decorators/