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

时间:2015-05-28 20:01:15

标签: python oop methods vk

所以我在python中使用API wrapper for vk,欧洲的Facebook等价物。 vk site上的文档包含可以使用的所有API调用,并且包装器能够正确调用它们。例如,要获取用户的信息,您可以调用api.users.get(id)按ID获取用户。我的问题是:当users对象中未定义users.get()或相应的api方法时,包装器如何正确处理此类调用?

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

修改

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

实例化

3 个答案:

答案 0 :(得分:2)

让我们一起走过这个,不是吗?

api

要执行api.users.get(),Python首先必须知道api。由于你的实例化,它 知道它:它是一个持有APISession实例的局部变量。

api.users

然后,它必须知道api.users。 Python首先查看api实例的成员,在其类(APISession)的成员和该类的成员中。超类(object的情况下只有APISession)。如果在这些地方找不到名为users的成员,它会在同一个地方寻找名为__getattr__的成员函数。它会在实例上找到它,因为APISession has an (instance) member function of this name

然后,Python用'users'(迄今为止缺少的成员的名称)调用它,并使用函数的返回值,就像它是该成员一样。所以

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 1}}实例APIMethod指向它的类或超类,因此Python转向api.users方法,对于这个类,它做了一些特殊的事情:

__getattr__

同一个班级的新实例!让我们插入 def __getattr__(self, method_name): return APIMethod(self._api_session, self._method_name + '.' + method_name) 的实例成员和

api.users

等同于

api.users.get

因此,APIMethod(api, 'users' + '.' + 'get') api中的api.user.get对象和_apisession中的字符串'users.get'也会有。{/ p >

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

所以()是一个对象。要调用它,Python必须假装它是一个函数,或者更具体地说,是api.users.get的方法。它是这样做的,而是调用api.users' s __call__ method,它看起来像这样:

api.users.get

让我们解决这个问题:

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

所以现在Python正在调用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') 对象,就像它是一个函数一样。再次api救援,this time看起来像这样:

__call__

现在,这是一个很多错误处理。对于这种分析,我只对快乐的道路感兴趣。我只对快乐路径的结果(以及我们如何到达那里)感兴趣。所以让我们开始at the result

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

return data['response'] 来自哪里?它是data被解释为JSON的第一个元素,其中包含一个'响应'宾语。因此,似乎从我们获得的response.text对象中,我们正在提取实际的响应部分。

response对象来自哪里? <{3}}来自

response

对于我们所关心的一切,这是一个简单的was returned电话,并不需要任何花哨的后备。 (当然,它的实施,在某些层面上,可能。)

答案 1 :(得分:1)

假设评论是正确的,apithis particular commit中定义的APISession的实例,那么这是一个有趣的迷宫:

首先,您要访问api.userAPISession没有此类属性,因此它会调用__getattr__('user'),其定义为:

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

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

__getattr__('get')

这会返回一个def __getattr__(self, method_name): return APIMethod(self._api_session, self._method_name + '.' + method_name) ,然后调用APIMethod(api,'users.get')类的__call__方法,该方法是:

APIMethod

所以这会尝试返回def __call__(self, **method_kwargs): return self._api_session(self._method_name, **method_kwargs) ,但api('users.get')是一个api对象,所以它会调用此类的APISession方法,定义为(去掉错误)处理简单):

__call__

然后它调用method_request(&#39; users.get&#39;),如果你遵循该方法实际上发出了一个POST请求,并且一些数据作为响应返回,然后返回。

答案 2 :(得分:0)

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

  

当属性查找未在通常位置找到属性时调用(即,它不是实例属性,也不是在类树中找到自己)。 name是属性名称。

确切地说,由于users的类没有定义api,因此调用__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