デコレータ(Decorators)は Python の重要な一部です。簡単に言うと、他の関数の機能を変更する関数です。コードをより短く、より Pythonic( Python らしく)にしてくれます。多くの初心者はどこで使えばよいか分かりません。そこで、デコレータがコードをより簡潔にできる領域を共有しようと思います。まず、独自のデコレータの書き方について説明しましょう。
これはおそらく最も習得が難しい概念の一つです。完全に理解できるように、一度にひとつのステップずつ説明します。
すべてがオブジェクト
まず Python における関数を理解しましょう。
def hi(name="yasoob"):
return "hi " + name
print(hi())
greet = hi
print(greet())
del hi
print(hi())
print(greet())
関数の中で関数を定義する
以上が関数の基本知識です。知識をさらに一歩進めましょう。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()
greet()
これで、関数の中に別の関数を定義できることが分かりました。つまり、ネストされた関数を作成できるのです。次に学ぶ必要があるのは、関数も関数を返すことができるということです。
関数から関数を返す
実際には、ある関数の中で別の関数を実行する必要はなく、出力として返すこともできます:
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)
print(a())
もう一度このコードを見てみましょう。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)
これで、デコレータが実際に何であるかをさらに学ぶために必要な知識がすべて揃いました。デコレータを使うと、関数の前後でコードを実行できます。
あなたの最初のデコレータ
前の例で、実際にはすでにデコレータを作成していました!次に、前のデコレータを修正して、もう少し役立つプログラムを作成しましょう:
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()
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
a_function_requiring_decoration()
分かりましたか?私たちは今学んだ原理を適用しました。これこそが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()
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__)
これでずっと良くなりました。次に、デコレータの一般的な使用シーンを学びましょう。
ブループリントの基本形:
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())
can_run = False
print(func())
注意:@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)
あなたはすでにデコレータの別の賢い使い方を考えているに違いありません。
引数を持つデコレータ
この問題を考えてみましょう。@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)
with open(logfile, 'a') as opened_file:
opened_file.write(log_string + '\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
@logit()
def myfunc1():
pass
myfunc1()
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
デコレータクラス
これで、本番環境で使用できる 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)
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):
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):
pass
今後、@email_logit は @logit と同じ効果に加えて、ログに加えて管理者にメールも送信します。
原文URL:https://eastlakeside.gitbooks.io/interpy-zh/content/decorators/