ホームページ >バックエンド開発 >Python チュートリアル >Python のマジック メソッドを深く理解する

Python のマジック メソッドを深く理解する

WBOY
WBOYオリジナル
2016-06-16 08:43:191132ブラウズ

私はしばらく Python に触れてきましたが、Python 関連のフレームワークやモジュールにもたくさん触れてきました。私がこれまで出会ってきた設計や実装について、皆さんと共有したいと思っています。より良いので、私は小さな「魅力的な Python」を取り上げました。これは私自身にとって良いスタートであり、皆さんが私を批判して修正してくれることを願っています。 :)

フラスコインポートリクエストから

Flask は非常に人気のある Python Web フレームワークで、著者も大小のプロジェクトを作成するためにこれを使用しました。それは、必要に応じてどこにいても使用できるという点です。現在のリクエストオブジェクトを取得するには、単純に次のようにします:

コードをコピー コードは次のとおりです:

フラスコインポートリクエストから

# 現在のリクエストからコンテンツを取得します
request.args
request.forms
request.cookie
... ...


とてもシンプルで覚えやすく、とても使いやすいです。ただし、その背後にある単純な実装は少し複雑です。 私の記事に従って謎を確認してください!

質問が 2 つありますか?

下を向く前に、まず 2 つの質問をします:

質問 1: request は静的クラス インスタンスのように見えます。なぜ、現在のリクエストの args 属性を取得するために、次のような式を使用する代わりに、 request.args のような式を直接使用できるのですか。

コードをコピー コードは次のとおりです:
フラスコインポート get_request
から # 現在のリクエストを取得します

request = get_request()
get_request().args

この方法はどうでしょうか? Flask はリクエストを現在のリクエスト オブジェクトにどのようにマップしますか?
質問 2: 実際の運用環境では、同じワーカー プロセスの下に多数のスレッド (またはコルーチン) が存在する可能性がありますが、そのような環境ではリクエスト クラスのインスタンスをどのように使用できますか?普通に働いているもの?

その秘密を知るには、flask のソースコードから始めるしかありません。

ソースコード、ソースコード、ソースコード

まず、flask のソース コードを開いて、最初の __init__.py からリクエストがどのように出力されるかを確認します。


コードをコピー コードは次のとおりです:
# ファイル: flask/__init__.py
from .globals import current_app、g、request、session、_request_ctx_stack

# ファイル: flask/globals.py
functools インポート部分から
werkzeug.local からインポート LocalStack、LocalProxy

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'))

flask のリクエストは globals.py から導入されていることがわかります。ここでリクエストを定義するコードは request = LocalProxy(partial(_lookup_req_object, 'request')) です。 , まずは授業についていく必要があり、まずは部分的に理解する必要があります。

しかし、partial(func, 'request') は別の関数を生成するために func の最初のデフォルトパラメータとして 'request' を使用するということは単純に理解できます。

したがって、partial(_lookup_req_object, 'request') は次のように理解できます。

呼び出し可能な関数を生成します。この関数は主に、スタックの先頭にある最初の RequestContext オブジェクトを _request_ctx_stack LocalStack オブジェクトから取得し、このオブジェクトのリクエスト属性を返します。

werkzeug の下にあるこの LocalProxy は私たちの注目を集めました。それが何であるかを見てみましょう:

コードをコピー コードは次のとおりです: @implements_bool
クラス LocalProxy(オブジェクト):
"""ローカル工場のプロキシとして機能します。すべての操作を
に転送します。 プロキシされたオブジェクト。転送でサポートされていない唯一の操作
。 は、右手オペランドおよび任意の種類の代入です。
... ...


紹介文の最初の数文を読むと、LocalProxy は主に Proxy、つまり werkzeug の Local オブジェクトにサービスを提供するプロキシとして機能することがわかります。それ自体に影響を与えるすべての操作を、それが表すオブジェクトに「転送」します。

それでは、このプロキシは Python を介してどのように実装されるのでしょうか?答えはソースコードにあります:


コードをコピー コードは次のとおりです: # 説明の便宜上、コードを一部削除・変更しました

@implements_bool クラス LocalProxy(オブジェクト):

__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__)

# 次は、Python マジック メソッドの長いリストです。ローカル プロキシは (ほぼ) すべての Python でオーバーロードされています。

#組み込みのマジック メソッド。すべての独自の操作が _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
... ...

これで、記事の冒頭の 2 番目の質問に答えることができます。現在のリクエスト オブジェクトを取得するために get_request() などのメソッド呼び出しを使用する必要がない理由は、すべて LocalProxy によるものです。

LocalProxy は、カスタム マジック メソッドを通じてプロキシとして機能します。実際のリクエスト オブジェクトを指し、リクエストに対するすべての操作をプロキシします。

どうでしょうか。request.args が思ったほど単純ではないことがわかりました。

次に、2 番目の質問を見てみましょう。マルチスレッド環境ではリクエストはどのように動作するのでしょうか? globals.py に戻りましょう:


コードをコピー コードは次のとおりです:
functools インポート部分から
werkzeug.local からインポート LocalStack、LocalProxy


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'))

問題の鍵はこの _request_ctx_stack オブジェクトにあります。LocalStack のソース コードを見つけてください。

コードをコピー コードは次のとおりです:
クラス LocalStack(オブジェクト):
def __init__(self):

# 実際には、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 メソッドを見てみる必要があります。

# flask が gevent のようなコンテナーで実行されている場合は、まず greenlet から getcurrent メソッドをインポートしてみます

# すべてのリクエストは、スレッドではなくグリーンレットを最小単位として使用します。
試してみてください:
greenlet から getcurrent を get_ident
としてインポートします ImportError を除く:
試してみてください:
スレッドインポート get_ident
から ImportError を除く:
_thread import get_ident
から

# つまり、この get_ident メソッドは、リクエストごとに一意の現在のコルーチン/スレッド ID を返します

クラスローカル(オブジェクト):
__slots__ = ('__storage__', '__ident_func__')

def __init__(self):

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)

def __setattr__(self, name, value):

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 の魔法の部分を学びマスターし、魔法を使ってコードをより簡潔で使いやすくすることです。

ただし、魔法はまばゆいばかりですが、悪用しないでください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。