ホームページ > 記事 > ウェブフロントエンド > PyTips 0x0f
プロジェクトアドレス: https://git.io/pytips
Python の修飾子は一種の糖衣構文です。つまり:
@decorator@wrapdef func(): pass
は次の文法の略語です:
def func(): passfunc = decorator(wrap(func))
に関する 2 つの主な質問デコレータ:
デコレータは誰を使用しますか?
デコレータとして使用できるのは誰ですか? コンテキスト マネージャでは、主に
後の作業をよりエレガントに完了するために使用されますが、デコレータは通常、関数の動作またはプロパティを拡張します:def log(func): def wraper(): print("INFO: Starting {}".format(func.__name__)) func() print("INFO: Finishing {}".format(func.__name__)) return wraper@logdef run(): print("Running run...")run()
INFO: Starting runRunning run...INFO: Finishing run
from time import sleep, timedef timer(Cls): def wraper(): s = time() obj = Cls() e = time() print("Cost {:.3f}s to init.".format(e - s)) return obj return wraper@timerclass Obj: def __init__(self): print("Hello") sleep(3) print("Obj")o = Obj()
HelloObjCost 3.005s to init.デコレータとしてのクラス 関数を呼び出すことができるため、上記の 2 つの例は両方ともデコレータとして関数を使用しています。 callable) デコレータ(wrap(func)) 。関数に加えて、呼び出し可能なクラスを定義することもできます。__call__ メソッドを追加するだけです:
class HTML(object): """ Baking HTML Tags! """ def __init__(self, tag="p"): print("LOG: Baking Tag <{}>!".format(tag)) self.tag = tag def __call__(self, func): return lambda: "<{0}>{1}</{0}>".format(self.tag, func(), self.tag)@HTML("html")@HTML("body")@HTML("div")def body(): return "Hello"print(body())
LOG: Baking Tag <html>!LOG: Baking Tag <body>!LOG: Baking Tag <div>!<html><body><div>Hello</div></body></html>
実際の使用では、デコレータにパラメータを渡す必要がある場合があります。関数 (またはクラス) はパラメータを渡します。構文規則によれば、デコレータ @decorator 内のデコレータが呼び出し可能である限り、decorator(123) が新しい呼び出し可能な関数を返すのは妥当です。上記の @HTML('html') を例に挙げます。デコレータにパラメータを渡す方法を示す例として、flask のルート デコレータを使用します。
RULES = {}def route(rule): def decorator(hand): RULES.update({rule: hand}) return hand return decorator@route("/")def index(): print("Hello world!")def home(): print("Welcome Home!")home = route("/home")(home)index()home()print(RULES)
Hello world!Welcome Home!{'/': <function index at 0x10706f730>, '/home': <function home at 0x10706f8c8>}
@route("/login")def login(user = "user", pwd = "pwd"): print("DB.findOne({{{}, {}}})".format(user, pwd))login("hail", "python")
DB.findOne({hail, python})デコレータ内で実行する必要がある場合は、少し変更する必要があります:
def log(f): def wraper(*args, **kargs): print("INFO: Start Logging") f(*args, **kargs) print("INFO: Finish Logging") return wraper@logdef run(hello = "world"): print("Hello {}".format(hello))run("Python")
INFO: Start LoggingHello PythonINFO: Finish Logging
デコレータは関数 (またはクラス) をラップし、それを再度返します: func =decorator(func)。上記の HTML デコレータを例にとると、元の関数 (またはクラス) の一部の情報を変更することができます。 body = HTML("body")(body)、HTML("body").__call__() はラムダ関数を返すため、body はラムダ関数に置き換えられています。これらはすべて実行可能な関数ですが、一部の属性は最初に定義された関数です。 __doc__ / __name__ / __module__ などの本体が置き換えられています (この場合、__module__ はすべて同じファイル内にあるため変更されていません)。この問題を解決するために、Python は、update_wrapper とラップ メソッド (ソース コード) を含む functools 標準ライブラリを提供します。その中で、update_wrapper は、デコレーターで返された関数に元の関数の情報を割り当てるために使用されます:
@HTML("body")def body(): """ return body content """ return "Hello, body!"print(body.__name__)print(body.__doc__)
LOG: Baking Tag <body>!<lambda>None興味深いのは、update_wrapper 自体の使用法がデコレーターと非常に似ているため、functools.wraps は functools.partial を使用することです。 (プログラミングの部分適用関数を思い出してください!) それを修飾子に変えます:
from functools import update_wrapper"""functools.update_wrapper(wrapper, wrapped[, assigned][, updated])"""class HTML(object): """ Baking HTML Tags! """ def __init__(self, tag="p"): print("LOG: Baking Tag <{}>!".format(tag)) self.tag = tag def __call__(self, func): wraper = lambda: "<{0}>{1}</{0}>".format(self.tag, func(), self.tag) update_wrapper(wraper, func) return wraper@HTML("body")def body(): """ return body content! """ return "Hello, body!"print(body.__name__)print(body.__doc__)
LOG: Baking Tag <body>!body return body content!パブリック アカウント PyHub のフォローへようこそ!
リファレンス
Python デコレータを使用した関数型プログラミング