ホームページ >バックエンド開発 >Python チュートリアル >Python のマジック メソッドを深く理解する
私はしばらく Python に触れてきましたが、Python 関連のフレームワークやモジュールにもたくさん触れてきました。私がこれまで出会ってきた設計や実装について、皆さんと共有したいと思っています。より良いので、私は小さな「魅力的な Python」を取り上げました。これは私自身にとって良いスタートであり、皆さんが私を批判して修正してくれることを願っています。 :)
フラスコインポートリクエストから
Flask は非常に人気のある Python Web フレームワークで、著者も大小のプロジェクトを作成するためにこれを使用しました。それは、必要に応じてどこにいても使用できるという点です。現在のリクエストオブジェクトを取得するには、単純に次のようにします:
# 現在のリクエストからコンテンツを取得します
request.args
request.forms
request.cookie
... ...
質問が 2 つありますか?
下を向く前に、まず 2 つの質問をします:
質問 1: request は静的クラス インスタンスのように見えます。なぜ、現在のリクエストの args 属性を取得するために、次のような式を使用する代わりに、 request.args のような式を直接使用できるのですか。
request = get_request()
get_request().args
その秘密を知るには、flask のソースコードから始めるしかありません。
ソースコード、ソースコード、ソースコード
まず、flask のソース コードを開いて、最初の __init__.py からリクエストがどのように出力されるかを確認します。
# ファイル: flask/globals.py
functools インポート部分から
werkzeug.local からインポート LocalStack、LocalProxy
def _lookup_req_object(名前):
トップ = _request_ctx_stack.top
最上位が None の場合:
raise RuntimeError('リクエストコンテキスト外で動作')
getattr(top, name) を返す
# 個のコンテキストローカル
request = LocalProxy(partial(_lookup_req_object, 'request'))
しかし、partial(func, 'request') は別の関数を生成するために func の最初のデフォルトパラメータとして 'request' を使用するということは単純に理解できます。
したがって、partial(_lookup_req_object, 'request') は次のように理解できます。
呼び出し可能な関数を生成します。この関数は主に、スタックの先頭にある最初の RequestContext オブジェクトを _request_ctx_stack LocalStack オブジェクトから取得し、このオブジェクトのリクエスト属性を返します。
werkzeug の下にあるこの LocalProxy は私たちの注目を集めました。それが何であるかを見てみましょう:
それでは、このプロキシは Python を介してどのように実装されるのでしょうか?答えはソースコードにあります:
__slots__ = ('__local', '__dict__', '__name__')
def __init__(self, local, name=None):
# ここで __setattr__ メソッドが渡される、self の
に注意する必要がある点が 1 つあります。
# "_LocalProxy__local" 属性はローカルに設定されています。興味があるかもしれません
# この属性名はなぜ奇妙なのでしょうか? 実際、これは Python が実際の
をサポートしていないためです。
# プライベートメンバー、詳細については公式ドキュメントを参照してください:
# http://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references
# ここでは、self.__local = local :)
として扱う必要があるだけです。
object.__setattr__(self, '_LocalProxy__local', local)
object.__setattr__(self, '__name__', name)
def _get_current_object(self):
"""
得る
パフォーマンス上の理由から、このエージェントの実際のオブジェクトを取得するか、それを別の
に使用する必要があります。
場所。
"""
# ここでの主な目的は、request
を分析するときに、プロキシ オブジェクトが werkzeug のローカル オブジェクトであるかどうかを判断することです。
このロジックは # の処理では使用されません。
そうでない場合は、attr(self.__local, '__release_local__'):
#localproxy(partial(_lookup_req_object、 'request'))によると)
# self.__local() メソッドを呼び出すと、partial(_lookup_req_object, 'request')()
を取得します。
# それは「_request_ctx_stack.top.request」
です
return self.__local()
試してみてください:
return getattr(self.__local, self.__name__)
AttributeError を除く:
Raise RuntimeError('%s にバインドされたオブジェクトがありません' % self.__name__)
#組み込みのマジック メソッド。すべての独自の操作が _get_current_object()
を指すようにします。
#返されるオブジェクトは実際のプロキシ オブジェクトです。
__setattr__ = ラムダ x, n, v: setattr(x._get_current_object(), n, v)
__delattr__ = ラムダ x, n: delattr(x._get_current_object(), n)
__str__ = ラムダ x: str(x._get_current_object())
__lt__ = ラムダ x, o: x._get_current_object()
__le__ = ラムダ x, o: x._get_current_object()
__eq__ = ラムダ x, o: x._get_current_object() == o
__ne__ = ラムダ x, o: x._get_current_object() != o
__gt__ = ラムダ x, o: x._get_current_object() > o
__ge__ = ラムダ x, o: x._get_current_object() >= o
... ...
LocalProxy は、カスタム マジック メソッドを通じてプロキシとして機能します。実際のリクエスト オブジェクトを指し、リクエストに対するすべての操作をプロキシします。
どうでしょうか。request.args が思ったほど単純ではないことがわかりました。
次に、2 番目の質問を見てみましょう。マルチスレッド環境ではリクエストはどのように動作するのでしょうか? globals.py に戻りましょう:
def _lookup_req_object(名前):
トップ = _request_ctx_stack.top
最上位が None の場合:
raise RuntimeError('リクエストコンテキスト外で動作')
getattr(top, name) を返す
_request_ctx_stack = LocalStack()
request = LocalProxy(partial(_lookup_req_object, 'request'))
# 実際には、LocalStack は主に別の Local クラスを使用します
# その主要なメソッドの一部もこの Local クラスにプロキシされます
# Local クラスと比較して、プッシュ、ポップなどのスタック「スタック」関連のメソッドがさらに実装されています。
#したがって、ローカル コードを直接確認する必要があります
self._local = Local()
@プロパティ
def トップ(自分):
"""
スタックの一番上にあるオブジェクトを返します
"""
試してみてください:
return self._local.stack[-1]
(AttributeError、IndexError) を除く:
なしを返す
#したがって、_request_ctx_stack.top を呼び出すと、実際には _request_ctx_stack._local.stack[-1]
を呼び出します。
# Local クラスがどのように実装されているかを見てみましょう。その前に、
の下にある get_ident メソッドを見てみる必要があります。
# すべてのリクエストは、スレッドではなくグリーンレットを最小単位として使用します。
試してみてください:
greenlet から getcurrent を get_ident
としてインポートします
ImportError を除く:
試してみてください:
スレッドインポート get_ident
から
ImportError を除く:
_thread import get_ident
から
クラスローカル(オブジェクト):
__slots__ = ('__storage__', '__ident_func__')
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
# 問題の鍵は、Local クラスが 2 つのマジック メソッド __getattr__ と __setattr__ をオーバーロードしていることです
def __getattr__(自分, 名前):
試してみてください:
# ここで、現在の一意の ID である self.__ident_func__() を呼び出します
# __storage__
のキーとして使用
return self.__storage__[self.__ident_func__()][name]
KeyError を除く:
Raise AttributeError(name)
ident = self.__ident_func__()
ストレージ = self.__storage__
試してみてください:
ストレージ[識別子][名前] = 値
KeyError を除く:
storage[ident] = {名前: 値}
# これら 2 つのマジック メソッドをオーバーロードした後
# Local().some_value は見た目ほど単純ではなくなりました:
# まず get_ident メソッドを呼び出して、現在実行中のスレッド/コルーチン ID を取得します
# 次に、次のように、この ID スペースの下の some_value 属性を取得します:
#
# Local().some_value -> Local()[current_thread_id()].some_value
#
# 属性を設定するときにも同じ原則が適用されます
これらの分析を通じて、現在のスレッド/コルーチン ID を使用し、いくつかのマジック メソッドをオーバーロードすることにより、Flask はさまざまなワーカー スレッドがスタック オブジェクトの独自の共有を使用できるようになります。これにより、リクエストが正常に動作することが保証されます。
現時点で、この記事はほぼ完成しています。ユーザーの利便性を考慮すると、フレームワークやツールの開発者は多くの追加の作業を行う必要があることがわかります。この点では、Python も非常に優れたサポートを備えています。
私たちがしなければならないのは、Python の魔法の部分を学びマスターし、魔法を使ってコードをより簡潔で使いやすくすることです。
ただし、魔法はまばゆいばかりですが、悪用しないでください。