注釈の1つの使用例

ここで、注釈の下では、デコレータではないことを宣言します。 また、デコレータが注釈と呼ばれることもある理由はわかりません。



私は最近、長い時間関数の注釈を探しているpythonの機能があることを発見しました。 これは、それぞれの個別のパラメーターに関する情報を関数宣言に組み込む機会です。



PEPの標準的な例を次に示します。



def compile(source: "something compilable", filename: "where the compilable thing comes from", mode: "is this a single statement or a suite?"): ...
      
      







同じ場所で、以下に、パラメーターのコメントだけがこの機能の可能な使用法ではないことを明確にする例を示します。 これは、私の神経系をかなりの期間悩ませてきた古いトラブルについて考えるきっかけになりました。 つまり、 Flaskのフォームからデータを取得します







問題





クエリから引数を取得する場合、自分で引数を取得する必要があります。 毎回。 すべての議論。 さらに、エンドポイントの本体でこれらの引数を直接処理する必要があります。 原則として、あまりフレンドリーではないようです。



以下に例を示します。



 @app.route('/ugly_calc') def ugly_calc(): x, y = int(request.args['x']), int(request.args['y']) op = OPERATION[request.args['op']] #      . ,   —      (,   ) return str(op(x, y))
      
      







コントローラーで既にクリア、検証、および検証された引数を取得する方がはるかに論理的です。



 @app.route('/calc') def calc(x:Arg(int), y:Arg(int), op:Arg(_from=OPERATION)): return str(op(x, y))
      
      







コードは読みやすさとロジックで獲得し、コントローラーのサイズは実際の操作数まで減少しました。



よく運転した





まず、引数クラスをスローする必要があります。



ここからそれの基礎を取ります 。 今必要ないものを捨てて、出来上がり!



 class Arg(object): """ A request argument. """ def __init__(self, p_type=str, default=None): self.type = p_type self.default = default def _validate(self, value): """Perform conversion and validation on ``value``.""" return self.type(value) def validated(self, value): """ Convert and validate the given value according to the ``p_type`` Sets default if value is None """ if value is None: return self.default or self.type() return self._validate(value)
      
      







はい、引数のクラスは今のところ非常に最小限になります。 最終的に、必要なすべての種類のバリデーターと送信されたバリデーターをいつでも拡張できます。



ここで、「ダーティ」引数から辞書を受け取り、「クリーン」引数を返すことを行う必要があります。



ここでは、関数に割り当てられた注釈が__annotations__



属性に__annotations__



ディクショナリを形成することを__annotations__



ます。



 >>> def lol(yep, foo: "woof", bar: 32*2): pass >>> lol.__annotations__ {'foo': 'woof', 'bar': 64}
      
      







このように、処理する必要のあるすべての要素を含む辞書があります。 ただし、他の引数の存在も忘れてはなりません。 lol



関数がyep



取得しないと、あまり良くありません。



物語から引き下がったもの。 私たちは続けます:



 class Parser(object): def __call__(self, dct): """ Just for simplify """ return self.validated(dct) def __init__(self, structure): self.structure = structure def validated(self, dct): for key, arg_instatce in self.structure.items(): dct[key] = arg_instatce(dct.get(key, None)) return dct
      
      







このクラスは3ルーブルと同じくらい簡単です。 そのインスタンスは、名前が受信辞書とパラメーター構造にある各受信パラメーターを検証してから、変更された辞書を返します。 一般的に、それを返すことは意味がありません、それはただの習慣です:)



追加のパラメータ__annotations__



とデコレータを非常に積極的に使用しています。 したがって、問題wraps



回避wraps



ために標準のwraps



補足する方が良いでしょう。



 from functools import wraps as orig_wraps, WRAPPER_ASSIGNMENTS WRAPPER_ASSIGNMENTS += ('__annotations__',) wraps = lambda x: orig_wraps(x, WRAPPER_ASSIGNMENTS)
      
      







ここで、ターゲット関数をラップする単純なデコレータが必要です。 クラスとして作りましょう。 この方法は簡単です。



 class Endpoint(object): """              >>> plus = Endpoint(plus) >>> plus(5.0, "4") 9 """ def __call__(self, *args, **kwargs): return self.callable(*args, **kwargs) def __init__(self, func): self.__annotations__ = func.__annotations__ self.__name__ = func.__name__ self.set_func(func) def set_func(self, func): if func.__annotations__: #       self.parser = Parser(func.__annotations__) #     . #     ,  #   self.callable = self._wrap_callable(func) else: self.callable = func def _wrap_callable(self, func): @wraps(func) def wrapper(*args, **kwargs): #    ,  #   ,     . #   -       #     return func(*args, **self.parser(kwargs)) return wrapper
      
      







さて、すべての準備が整いました。 このことをFlaskにネジ止めする時が来ました。

ちなみに、これまで見てきたすべてのものは、他のフレームワークで同じコードフラグメントを使用するのに十分なほど抽象的に記述されています。 そして、フレームワークなしでも:)



始めましょう:

 class Flask(OrigFlask): #   .    froute = OrigFlask.route def route(self, rule, **options): """     . """ def registrator(func): #    : 1  - 1 . if 'methods' in options: method = options['methods'][0] else: method = 'GET' wrapped = self.register_endpoint(rule, func, options.get('name'), method) return wrapped return registrator def register_endpoint(self, rule, func, endpoint_name=None, method='GET'): endpoint_name = endpoint_name or func.__name__ endpoint = Endpoint(func) wrapped = self._arg_taker(endpoint) self.add_url_rule(rule, "%s.%s" % (endpoint_name, method), wrapped, methods=[method]) return wrapped def _arg_taker(self, func): """       .  . """ @wraps(func) def wrapper(*args, **kwargs): for key_name in func.__annotations__.keys(): kwargs[key_name] = request.args.get(key_name) return func(*args, **kwargs) return wrapper
      
      







すばらしい、基本的な機能は動作します。 これまでのところ_fromがなくても、今はそれなしでできると思います。



かぶ



質問をしたり、ねじ込み可能なさまざまな機能を提供したりできます。



UPD



この仕掛けの使用に関する短いマニュアルを書きました。



All Articles