Discuss / Python / 笨办法来理解__getattr__动态调用

笨办法来理解__getattr__动态调用

Topic source

以类Student为例,再更新为Chain

#第一步,初始化Student类, 利用__getattr__将新增属性名称更新至默认属性的取值中,这样就实现了利用新属性的名称(实际上没有新建属性)更新原属性的取值
class Student(object):
    def __init__(self, name=''):        #默认name输入值为空,此处name并不是真正的属性,只是输入变量;真正的属性real_name按照name值更新
        self.real_name = name       #真正的属性名称为real_name,真正的属性名称是跟随着self.后出现的变量名称
    def __getattr__(self, attr):        #通过__getattr__功能判断输入的attr变量名称是否和已有属性(real_name)一样,如果不一样则运行下面语句
        if attr.startswith('_'):        #解决‘_ipython_canary_method_should_not_exist_’问题,可忽略
            raise AttributeError(attr)      #解决‘_ipython_canary_method_should_not_exist_’问题,可忽略
        return Student(self.real_name + ' and ' + attr)     #如果attr为新属性名称则返回一个Student()实例,这个实例中的输入变量name按照()内的内容设置

Student()       #输出<__main__.Student at 0x地址>,新建了一个实例
Student().real_name     #输出'', 返回该实例的属性real_name的数值
Student().A     #输出<__main__.Student at 0x地址>,说明输出的仍然是1个实例,这个实例其实就是Student(''+' and '+'A')
Student().A.real_name       #输出' and A',返回实例Student(''+' and '+'A')的属性real_name的数值
Student().A.B       #输出<__main__.Student at 0x地址>,说明输出的是1个实例,这个实例其实就是Student(Student(' and A' and '+'B'))
Student().A.B.real_name       #输出' and A and B',返回实例Student(Student(' and A' and '+'B'))的属性real_name的数值
#第二步,通过print()看一下中间过程发生了什么
class Student(object):
    def __init__(self, name=''):
        self.real_name = name
        print('Students Names are: %s' % self.real_name)        #通过打印来看一下每一步生成的real_name属性值
    def __getattr__(self, attr):
        if attr.startswith('_'):        #解决‘_ipython_canary_method_should_not_exist_’问题,可忽略
            raise AttributeError(attr)      #解决‘_ipython_canary_method_should_not_exist_’问题,可忽略
        return Student(self.real_name + ' and ' + attr)

Student().A.B
#输出为4行结果
# Students Names are:                       #这一行是运行Student()后打印属性real_name的结果
# Students Names are:  and A                #这一行是运行Student(''+' and '+'A')后打印属性real_name的结果
# Students Names are:  and A and B          #这一行是运行Student(Student(' and A' and '+'B'))后打印属性real_name的结果
# Out[1]: <__main__.Student at 0x地址>      #Student().A.B输出结果为实例
#第三步,为了让<__main__.Student at 0x地址>变为属性real_name的数值,需要增加__repr__功能
class Student(object):
    def __init__(self, name=''):
        self.real_name = name
    def __getattr__(self, attr):
        return Student(self.real_name + ' and ' + attr)
    def __repr__(self):     #将内部打印结果变为下面的输出内容
        return self.real_name       #输出属性real_name的数值

Student().A     #因为__repr__替换了内部打印,可以直接输出结果' and A'
Student().A.B       #因为__repr__替换了内部打印,可以直接输出结果' and A and B'
#第四步,按照第3步改写成Chain()的要求
class Chain(object):
    def __init__(self, path=''):
        self._path = path
    def __getattr__(self, attr):
        return Chain(self._path + '/' + attr)
    def __repr__(self):
        return self._path

Chain().status.user.timeline.list       #输出 /status/user/timeline/list
#第五步,通过增加__call__方法实现API功能,即实例C调用方法:Chain('user')输出/:user
class Chain(object):
    def __init__(self, path='GET '):     #默认输入改为GET
        self._path = path
    def __getattr__(self, attr):
        return Chain(self._path + '/' + attr)
    def __repr__(self):
        return self._path
    def __call__(self, user=''):        #增加实例调用方法即Chain()
        return Chain(self._path + '/:' + user)      #输出格式为.../:user

Chain().users('Michael').repos      #输出GET /users/:Michael/repos

  • 1

Reply