毎日Learning

学んだことを共有します

エキスパート Python プログラミング メモ Vol.7

2.3.4 プロキシ

プロキシデコレータは関数にタグをつけたり、グローバルな仕組みへ登録したりします。
たとえば、実行中のユーザーごとにコードへのアクセスを保護するセキュリティレイヤは、呼び出し可能なオブジェクトに関連づけられたアクセス許可情報を利用する、集中制御型チェッカーとして実装することができます。

はい。いきなり文章から読解できません。コードを見てみましょう。

>>> class User(object):
...     def __init__(self, roles):
...         self.roles = roles
...
>>> class Unauthorized(Exception):
...     pass
...
>>> def protect(role):
...     def _protect(function):
...         def __protect(*args, **kw):
...             user = globals().get('user')
...             if user is None or role not in user.roles:
...                 raise Unauthorized("I won't tell you")
...             return function(*args, **kw)
...         return __protect
...     return _protect

はい。コードを見たら理解できました。Djangoのユーザー認証で使うデコレータもプロキシデコレータと呼べるんでしょうね。
って思った矢先に書籍にも書いてありました。

このモデルは、PythonのWebフレームワークで、Web上に公開されるクラスのセキュリティを定義するためによく使用されます。
たとえば、Djangoは、関数アクセスを安全にするためのデコレータを提供しています。

で、使い方のコードは以下。

>>> tarek = User(('admin', 'user'))
>>> bill = User(('user',))
>>> class MySecrets(object):
...     @protect('admin')
...     def waffle_recipe(self):
...         print('user tons of butter!')
...
>>> these_are = MySecrets()
>>> user = tarek
>>> these_are.waffle_recipe()
user tons of butter!
>>> user = bill
>>> these_are.waffle_recipe()
# Unauthorizedの例外がでる

プロキシデコレータはおしまい。

2.3.5 コンテキストプロバイダ

コンテキストデコレータは関数が正しい実行コンテキスト内で実行されることを保証したり、関数の前後である処理を実行します。
言い方を変えると、特別な実行環境の設定をしたり、外したりします。

なるほど。このデコレータは結構使いそう。

文中では、ロッキ機構を例に挙げて紹介されている。ある処理の前にロック、処理が完了した後にロック解放。ってーのをデコレータで統一してするってことか。

>>> from threading import RLock
>>> lock = RLock()
>>> def synchronized(function):
...     def _synchronized(*args, **kw):
...         lock.acquire()
...         try:
...             return function(*args, **kw)
...         finally:
...             lock.release()
...     return _synchronized
...
>>> @synchronized
... def thread_safe(): # リソースがロックされることを保証する
...     pass
...

コンテキストデコレータは、Python 2.6で追加されたwith文に置き換えることができます。
with文はtry..finallyパターンと、コンテキストデコレータが使用されるケースの一部を効率化するために作成されました。

なんでも気が効いてますなー。ちなみに、Python 2.5では、from __future__ import with_statement を書けば使えるようになるそうです。

また、 PythonDecoratorLibrary - Python Wiki に、色んなデコレータパターンが書いてあるんだって。

また、デコレータマニア向けに、文中ではもう少しコードが紹介してあるよ。おいおい。おなかいっぱいだよ。

>>> class MyClass:
...     """my docstring"""
...     pass
...
>>> my = MyClass()
>>>
>>> def addto(instance):
...     def _addto(f):
...         import new
...         f = new.instancemethod(f, instance, instance.__class__)
...         setattr(instance, f.func_name, f)
...         return f
...     return _addto
...
>>> @addto(my)
... def print_docstring(self):
...     print(self.__doc__)
...
>>> my.print_docstring()
my docstring

おー。なるほど。newってモジュールがあるんだな。インスタンスにメソッドを追加できるような関数を作成して、インスタンスにセットしとんか。
で、そのまま関数を返してるんか。へー。

次の例では、デコレータで関数の引数情報を出力しています。
このように関数の情報収集にデコレータを使用することができます。

>>> from __future__ import print_function
>>> def print_args(function):
...     def _print_args(*args, **kw):
...         print(function.func_name, args, kw)
...         return function(*args, **kw)
...     return _print_args
...
>>> @print_args
... def my_function(a, b, c):
...     print a + b, c * 2
...
>>> my_function(1, 2, c='key')
my_function (1, 2) {'c': 'key'}
3 keykey

この使い方を応用してログ出力に使えば良さげですね。デバッグに便利に使えそう。

同様に情報収集の使用例として、ハンドラ登録に使用するのにも便利です。

>>> def onexit(function):
...     import atexit
...     atexit.register(function)
...     return function
...
>>> @onexit
... def post_function():
...     print('post process')
...
>>> # インタラクティブシェルを終了させます
post process

プログラムが正常終了したときのログ出力に使いまわしできそう。

デコレータは、フレームワーク的な要素を提供するとか、ちょっとしたコーディングルールとして作っておくと便利そう。

以前関わったプロジェクト(Javaやったけど)で、バッチ処理をある規則に沿って開発しないといけないのだが、いちいち前後で呼び出さないといけない処理が決まっていた。
のに、毎回コピペしたりしてた。そんな時はデコレータを使えよ!!ってことだと自分で理解できた。