LoginSignup
uehara-gen3
@uehara-gen3 (Gen Uehara)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Python の挙動について、正しく理解したい

解決したいこと

X Api v2 の Sample コードで、Bearer認証に関連して Python が、どんな挙動をしているのかが、分からなくて困っています。

発生している問題・エラー

該当コードは、

にある、X Api v2 のサンプルコードです。ちゃんと、動作しているので、その点では何も困っていませんが、自分で Python の挙動が理解できず、困っています。

先に定義されている関数 bearer_oauth() は、次の connect_to_endpoint() で利用されています。関数内で、requests.get() の auth パラメータで渡されています。関数オブジェクトとして、渡されていることまでは理解できた(?)と思っているのですが、bearer_oauth()側で必要なはずの 引数:r がどこにもないので、どんな挙動をしているのか、理解できていません。
レベルアップのために、きちんと理解をしたいのですが、Python で学ぶべきキーワードの提示など、簡単なヒントでもいいので、助け船をお願いいたします。

該当するソースコード

def bearer_oauth(r):
    """
    Method required by bearer token authentication.
    """

    r.headers["Authorization"] = f"Bearer {bearer_token}"
    r.headers["User-Agent"] = "v2RecentSearchPython"
    return r

def connect_to_endpoint(url, params):
    response = requests.get(url, auth=bearer_oauth, params=params)
    print(response.status_code)
    if response.status_code != 200:
        raise Exception(response.status_code, response.text)
    return response.json()

自分で試したこと

・bearer_oauth() が、requests で設定できるカスタム認証のための関数だと思っています。requests のドキュメントをあたってみましたが、その場合には、AuthBase のサブクラスとして定義してくださいとありますが、ここでは単純に関数として定義されています。

・bearer_oauth() 内では、Authorization ヘッダを利用した認証が実施されて、そのレスポンスを返したいことは、理解できています。

0

2Answer

bearer_oauth()側で必要なはずの 引数:r はget関数側が用意するので、表向き渡されてないように見えるのが混乱してるポイントなのかな?

get関数の中身をものすごく簡単に書くと以下のようになります

def get(url, auth):
	r = {'headers' : {}}
	auth(r)
	# いろいろ通信

なので、authに関数そのものを渡そうが関数オブジェクトを渡そうが 引数 r は渡されます

https://requests.readthedocs.io/en/latest/_modules/requests/auth/#AuthBase
継承するAuthBaseを見ても大したことはしていないので、関数を渡しても大丈夫です

2

Comments

  1. @uehara-gen3

    Questioner

    回答、ありがとうございます。

    おっしゃる通り、見えているコード内では何も引数がないので、混乱していました。

    __call__ を知らなかったので、大変勉強になりました。結局、AuthBaseを継承したところで、「callable」で実装してねという大枠の方針しか伝えてないので(純粋なコード部分では)、単に関数を定義して渡しているということですね。

    get の内容を読むのに、苦労しましたが、おかげで理解できました。

私は Auth 周りに詳しいわけではないのですが、requests.auth に対応するコードを見てみると、AuthBase は次のように定義されていることがわかります。

src/requests/auth.py
class AuthBase:
    """Base class that all auth implementations derive from"""

    def __call__(self, r):
        raise NotImplementedError("Auth hooks must be callable.")

ここで重要なのは、__call__メソッドでして、これはclassのインスタンスは関数のように呼ばれた時に実行されるメソッドです。
例えば、次のようなコードがPythonでは動作します。

class Hoge:
    def __call__(self, r):
        print(r)

hoge = Hoge()
hoge("Hello")    #→ "Hello"

よって、authに渡すものはrを引数に取る関数でも、__call__(self, r)を定義したクラスのインスタンスのどちらでも良いということがわかります。

実際に、requests.get()の処理を追いかけてみると、最終的に prepare_authというメソッドで処理されています。

src/requests/models.py
    def prepare_auth(self, auth, url=""):
        """Prepares the given HTTP auth data."""

        # If no Auth is explicitly provided, extract it from the URL first.
        if auth is None:
            url_auth = get_auth_from_url(self.url)
            auth = url_auth if any(url_auth) else None

        if auth:
            if isinstance(auth, tuple) and len(auth) == 2:
                # special-case basic HTTP auth
                auth = HTTPBasicAuth(*auth)

            # Allow auth to make its changes.
            r = auth(self)

            # Update self to reflect the auth changes.
            self.__dict__.update(r.__dict__)

            # Recompute Content-Length
            self.prepare_content_length(self.body)

(https://github.com/psf/requests/blob/2d5f54779ad174035c5437b3b3c1146b0eaf60fe/src/requests/models.py#L588)

この部分で実引数の request を渡して、処理しているということがわかりますね。

これで回答になっているでしょうか?
(調べてみて私も勉強になりました...)

1

Comments

  1. @uehara-gen3

    Questioner

    回答、ありがとうございます。

    __call__ が初見でしたので、勉強になりました。__init__.py からスタートして、教えていただいた prepare_auth() に辿り着くまでに、複数のソースを渡り歩く必要があり、他の知らない事に遭遇して大変でしたが、ゴールを与えていただいたので、たどり着けました。

    マラソンで伴走していただいた気持ちです。本当に、ありがとうございます。

  2. お役に立てたなら幸いです!

Your answer might help someone💌