为什么我可以调用所有这些未在 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
假设注释是正确的,并且 api
是 this particular commit 中定义的 APISession
的实例,那么这是一个有点有趣的迷宫:
所以首先你要访问 api.user
。 APISession
没有这样的属性,所以它调用 __getattr__('user')
代替,定义为:
def __getattr__(self, method_name):
return APIMethod(self, method_name)
所以这构造了一个APIMethod(api,'user')
。现在您想在绑定到 api.users
的 APIMethod(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 调用,不需要任何花哨的回退。 (当然,在某些层面上,它的实施可能会。)
所以我正在使用一个 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
假设注释是正确的,并且 api
是 this particular commit 中定义的 APISession
的实例,那么这是一个有点有趣的迷宫:
所以首先你要访问 api.user
。 APISession
没有这样的属性,所以它调用 __getattr__('user')
代替,定义为:
def __getattr__(self, method_name):
return APIMethod(self, method_name)
所以这构造了一个APIMethod(api,'user')
。现在您想在绑定到 api.users
的 APIMethod(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 调用,不需要任何花哨的回退。 (当然,在某些层面上,它的实施可能会。)