Python 为什么我可以调用所有这些非';在类中没有显式定义吗?

Python 为什么我可以调用所有这些非';在类中没有显式定义吗?,python,oop,methods,vk,Python,Oop,Methods,Vk,因此,我正在为vk(相当于欧洲的Facebook)使用python。上的文档包含所有可以使用的API调用,并且包装器能够正确调用它们。例如,要获取用户信息,您可以调用api.users.get(id)以通过id获取用户。我的问题是:如果users或相应的users.get()方法均未在api对象中定义,包装器如何正确处理此类调用 我知道它涉及到\uuu getattr\uuuuuuuuuuuuuuuuuuuuuuuuu()和\uuuuuuuuuuuuuuuuuuuu调用方法,但我找不到任何关于如

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

我知道它涉及到
\uuu getattr\uuuuuuuuuuuuuuuuuuuuuuuuu()
\uuuuuuuuuuuuuuuuuuuu调用方法,但我找不到任何关于如何以这种方式使用它们的好文档

编辑

api
对象通过
api=vk.api(id、电子邮件、密码)
实例化
users.get()
api
对象无关。对于
用户
,您是对的,如果没有定义这样的成员,那么肯定有一些逻辑在内部。因此,正如您在文档
\uuuu getattr\uuuu
中看到的那样

当属性查找未在常规位置找到属性时调用(即,它不是实例属性,也不是在类树中为self找到的属性)。name是属性名

因此,确切地说,由于没有为
api
类定义
用户
,因此调用
\uuuu getattr\uuuu
时将
'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
是中定义的
APISession
的一个实例,那么这是一个有趣的迷宫:

所以首先要访问
api.user
APISession
没有这样的属性,因此它调用
\uuuu getattr\uuuu('user')
,定义为:

def __getattr__(self, method_name):
    return APIMethod(self, method_name)
因此,这构造了一个
APIMethod(api,'user')
。现在,您希望在绑定到
api.users
APIMethod(api,'user')
上调用方法
get
,但是
APIMethod
没有
get
方法,因此它调用自己的
\ugetattr\uuuuu('get')
来确定要做什么:

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

def __call__(self, **method_kwargs):
    return self._api_session(self._method_name, **method_kwargs)
因此,它试图返回
api('users.get')
,但是
api
是一个
APISession
对象,因此它调用此类的
\uuuu call\uuuu
方法,定义为(为了简单起见去掉错误处理):


然后它调用一个方法请求('users.get'),如果你遵循这个方法,它实际上会执行一个POST请求,一些数据作为响应返回,然后返回。

让我们一起来看看这个,好吗

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

api.users
然后,它必须知道
api.users
。Python首先查看
api
实例的成员、其类的成员(
APISession
)以及该类的超类的成员(对于
APISession
,仅查看
object
)。在这些地方找不到名为
users
的成员,它会在这些地方查找名为
\uuuu getattr\uuuu
的成员函数。它将在实例上找到它,因为
APISession

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

api.users
相当于

api.__getattr__('users')
APIMethod(api, 'users')
APIMethod(api, 'users' + '.' + 'get')
让我看看

哦。所以

api.users # via api.__getattr__('users')
相当于

api.__getattr__('users')
APIMethod(api, 'users')
APIMethod(api, 'users' + '.' + 'get')
创建一个新实例

api
'users'
作为该实例的
\u api\u session
\u method\u name
成员。我想这是有道理的

api.users.get
Python仍然没有执行我们的语句。它需要知道
api.users.get()
才能这样做。与以前相同的游戏会重复,只是在
api中。用户
对象而不是
api
对象中:在
APIMethod
实例
api上找不到成员方法
get()
,也找不到成员
get
。用户
指向,也不指向其类或超类,因此Python转而使用
\uuuu getattr\uuuu
方法,该方法对此类执行一些特殊的操作:

    def __getattr__(self, method_name):
        return APIMethod(self._api_session, self._method_name + '.' + method_name)
同一类的新实例!让我们插入
api.users
的实例成员,然后

api.users.get
相当于

api.__getattr__('users')
APIMethod(api, 'users')
APIMethod(api, 'users' + '.' + 'get')
因此,我们将在
api.user.get
\u apisession
中也有
api
对象,并在其
\u方法名称中有字符串
'users.get'

api.users.get()
(注意
()
) 因此,
api.users.get
是一个对象。要调用它,Python必须假装它是一个函数,或者更具体地说,是一个
api.users
的方法。它通过调用
api.users.get
来实现,如下所示:

    def __call__(self, **method_kwargs):
        return self._api_session(self._method_name, **method_kwargs)
    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])
让我们来解决这个问题:

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
对象,就好像它是一个函数一样<代码>\uuuuuuuuuuuuuuuuuuu
再次呼叫营救,看起来像这样:

    def __call__(self, **method_kwargs):
        return self._api_session(self._method_name, **method_kwargs)
    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])
现在,这是很多错误处理。对于这个分析,我只对快乐之路感兴趣。我只对“快乐之路”的结果感兴趣(以及我们是如何做到的)。让我们开始吧

数据从哪里来?它是
response.text的第一个元素