为什么我可以调用所有这些未在 class 中明确定义的方法?

Why can I call all these methods that aren't explicitly defined in a class?

所以我正在使用一个 API wrapper in python for vk, Europe's Facebook equivalent. The documentation on the vk site 可以使用的所有 API 调用,并且包装器能够正确调用它们。例如,要获取用户的信息,您可以调用 api.users.get(id) 通过 id 获取用户。我的问题是:当 users 或相应的 users.get() 方法都没有在 api 对象中定义时,包装器如何正确处理这样的调用?

我知道它涉及 __getattr__()__call__() 方法,但我找不到任何关于如何以这种方式使用它们的好文档。

编辑

api 对象通过 api = vk.API(id, email, password)

实例化

users.get()api对象无关。至于users,你是对的,如果没有定义这个成员,那么__getattr__里面肯定有一些逻辑。因此,正如您在文档中看到的那样 __getattr__ 是...

Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name.

确切地说,由于没有为 api 的 class 定义 users,因此调用 __getattr__ 并传递 'users'作为 name 参数。然后,最有可能动态地,根据传递的参数,为 users 组件构造一个对象并返回,它将负责以类似的方式定义或处理 get() 方法。

要了解整个想法,请尝试以下操作:

class A(object):
    def __init__(self):
        super(A, self).__init__()

        self.defined_one = 'I am defined inside A'

    def __getattr__(self, item):
        print('getting attribute {}'.format(item))

        return 'I am ' + item


a = A()

>>> print(a.some_item)  # this will call __getattr__ as some_item is not defined
getting attribute some_item
I am some_item
>>> print(a.and_another_one)  # this will call __getattr__ as and_another_one is not defined
getting attribute and_another_one
I am and_another_one
>>> print(a.defined_one)  # this will NOT call __getattr__ as defined_one is defined in A
I am defined inside A

假设注释是正确的,并且 apithis particular commit 中定义的 APISession 的实例,那么这是一个有点有趣的迷宫:

所以首先你要访问 api.userAPISession 没有这样的属性,所以它调用 __getattr__('user') 代替,定义为:

def __getattr__(self, method_name):
    return APIMethod(self, method_name)

所以这构造了一个APIMethod(api,'user')。现在您想在绑定到 api.usersAPIMethod(api,'user') 上调用方法 get,但是 APIMethod 没有 get 方法,因此它调用它自己的 __getattr__('get') 来弄清楚要做什么:

def __getattr__(self, method_name):
    return APIMethod(self._api_session, self._method_name + '.' + method_name)

这个 return 是一个 APIMethod(api,'users.get') 然后被调用,调用 APIMethod class 的 __call__ 方法,即:

def __call__(self, **method_kwargs):
    return self._api_session(self._method_name, **method_kwargs)

所以这尝试 return api('users.get'),但是 api 是一个 APISession 对象,所以它调用了这个 [=48] 的 __call__ 方法=],定义为(为简单起见去掉错误处理):

def __call__(self, method_name, **method_kwargs):
    response = self.method_request(method_name, **method_kwargs)
    response.raise_for_status()

    for data in json_iter_parse(response.text):
        if 'response' in data:
            return data['response']

然后它会调用 method_request('users.get'),如果您按照该方法实际执行 POST 请求,一些数据会作为响应返回,即然后 returned.

让我们一起来了解一下,好吗?

api

要执行api.users.get(),Python首先要知道api。由于您的实例化,它 确实 知道它:它是一个局部变量,包含 APISession.

的一个实例

api.users

然后,它必须知道api.users。 Python 首先查看 api 实例的成员,其 class (APISession) 的成员以及 class' 的超级成员classes(在 APISession 的情况下只有 object)。在这些地方都找不到名为 users 的成员,它会在这些相同的地方寻找名为 __getattr__ 的成员函数。它会在实例上找到它,因为 APISession has an (instance) member function of this name.

Python 然后用 'users'(到目前为止丢失的成员的名称)调用它并使用函数的 return 值,就好像它是那个成员一样。所以

api.users

相当于

api.__getattr__('users')

让我们看看what that returns

    def __getattr__(self, method_name):
        return APIMethod(self, method_name)

哦。所以

api.users # via api.__getattr__('users')

相当于

APIMethod(api, 'users')

正在创建一个新的 APIMethod 实例。

api'users' end up 作为该实例的 _api_session_method_name 成员。我想有道理。

api.users.get

Python 仍然没有执行我们的语句。它需要知道 api.users.get() 才能这样做。重复之前的游戏,这次只是在 api.users 对象而不是 api 对象中:没有成员方法 get() 并且在 [= 上找不到成员 get 37=] instance api.users 指向,也不在它的 class 或 superclasses 上,所以 Python 转向 __getattr__ 方法,对于这个 class 做了一些奇怪的事情:

    def __getattr__(self, method_name):
        return APIMethod(self._api_session, self._method_name + '.' + method_name)

相同 class 的新实例!让我们插入 api.users

的实例成员
api.users.get

等同于

APIMethod(api, 'users' + '.' + 'get')

因此我们将在 api.user.get_apisession 中也有 api 对象,在其 _method_name 中也有字符串 'users.get'

api.users.get()(注意()

所以api.users.get是一个对象。要调用它,Python 必须假装它是一个函数,或者更具体地说,是 api.users 的一个方法。它通过调用 api.users.get__call__ method 来实现,它看起来像这样:

    def __call__(self, **method_kwargs):
        return self._api_session(self._method_name, **method_kwargs)

让我们解决这个问题:

api.users.get()
# is equivalent to
api.users.get.__call__() # no arguments, because we passed none to `get()`
# will return
api.users.get._api_session(api.users.get._method_name)
# which is
api('users.get')

所以现在 Python 正在调用 api 对象,就好像它是一个函数一样。 __call__ 再一次来救援,this time 看起来像这样:

    def __call__(self, method_name, **method_kwargs):
        response = self.method_request(method_name, **method_kwargs)
        response.raise_for_status()

        # there are may be 2 dicts in 1 json
        # for example: {'error': ...}{'response': ...}
        errors = []
        error_codes = []
        for data in json_iter_parse(response.text):
            if 'error' in data:
                error_data = data['error']
                if error_data['error_code'] == CAPTCHA_IS_NEEDED:
                    return self.captcha_is_needed(error_data, method_name, **method_kwargs)

                error_codes.append(error_data['error_code'])
                errors.append(error_data)

            if 'response' in data:
                for error in errors:
                    warnings.warn(str(error))

                return data['response']

        if AUTHORIZATION_FAILED in error_codes:  # invalid access token
            self.access_token = None
            self.get_access_token()
            return self(method_name, **method_kwargs)
        else:
            raise VkAPIMethodError(errors[0])

现在,有很多错误处理。对于此分析,我只对快乐之路感兴趣。我只对快乐路径的结果(以及我们如何到达那里)感兴趣。那么让我们开始 at the result.

return data['response']

data从哪里来的?它是 response.text 被解释为 JSON 的第一个元素,它确实包含一个 'response' 对象。所以看起来我们从 response 对象中提取了 actual 响应部分。

response对象是从哪里来的?它 was returned

api.method_request('users.get')

就我们而言,这是一个普通的 normal method 调用,不需要任何花哨的回退。 (当然,在某些层面上,它的实施可能会。)