python类属性访问魔法方法

python类属性访问魔法方法

本文主要讲述类(python3.6)属性访问的魔法方法: __get__, __getattr__, __getattribute__, ___set__, __setattr__
(本文对类属性和实例属性不加严谨的表述)

1. __getattr__, __setattr__

先定义一个类:

class Person(object):

    name = ''
    
    def __getattr__(self, item):
    # 当获取这个属性不存在就会访问这个方法
        print('__getattr__ is called')
        # raise AttributeError('class Person don\' have the attr {0}'.format(item))
        return 'hello'


if __name__ == '__main__':
    p = Person()
    print(p.name) 
    p.name = 'Mike' 
    print(p.name)  
    print(p.age)  
"""
输出: 
(空)
Mike
__getattr__ is called
hello
"""

可以看出__getattr__只有在属性不存在的时候才会被访问,建议返回值3个种情况:

  • 不返回(None)
  • 返回某种值 但需要注意的是,千万不能返回可能不存在的属性,否则会造成无限递归, 如上面return self.age 。除此之外, p.age虽然输出了’hello’, 但实际上,p实例依然是没有age这个属性的
  • (建议)直接引发异常

接下来我们看看__setattr__, 继续使用上面的类

class Person(object):

    name = ''
    
    def __getattr__(self, item):
    # 当获取这个属性不存在就会访问这个方法
        print('__getattr__ is called')
        # raise AttributeError('class Person don\'t have the attr {0}'.format(item))
        return 'hello'
        
    def __setattr__(self, key, value):
        print('__setattr__ is called')
        value += str(int(time.time()))  # 时间戳
        # setattr(self, key, value)
        super(Person, self).__setattr__(key, value)


if __name__ == '__main__':
    p = Person()
    print(p.name)
    p.name = 'Mike'
    print(p.name)
"""
(空)
__setattr__ is called
Mike1542524409	
"""

看输出结果就很容易明白这个方法的作用,但给属性赋值时,会去调用这个方法,当自己实现了这个方法了要注意以下的点:

  • 必须去给属性赋值,可靠的方法是直接继承父类方法,否则像p.name='Mike’将会失去作用,p.name的值永远是空
  • 千万不要尝试自己去给属性赋值,如上面的注释的setattr(self, key, value),这样会导致无限嵌套调用
  • __setattr__的一个作用,可以限制给类实例动态增加属性,python允许给实例动态增加属性,如上属例子,我们尝试p.name2 = ‘Ben’,一样可以给赋值成功,我们可以通过这个方法进行限制:
def __setattr__(self, key, value):
	if not hasattr(self, key):
		raise AttributeError
    print('__setattr__ is called')
    value += str(int(time.time()))  # 时间戳
	# setattr(self, key, value)
	super(Person, self).__setattr__(key, value)

2. 、__getattribute__

class Person(object):

    name = ''
    def __getattr__(self, item):
        print('__getattr__ is called')
        raise AttributeError('Person don\'t have the attr {0}'.format(item))

    def __getattribute__(self, item):
        print('__getattribute__ is called')
        return super(Person, self).__getattribute__(item)


if __name__ == '__main__':
    p = Person()
    p.name = 'Mike'
    print(p.name)
    print(p.name2)
"""
#     print(p.name)
__getattribute__ is called
Mike
#    print(p.name2)
__getattribute__ is called
__getattr__ is called
...
AttributeError: Person don't have the attr name2
"""

可以看出__getattribute__无论属性存在与否都会被调用, 和之前都一样,要注意无限嵌套调用, 它的优先级在属性访问的时候级别是最高的。

3. __get__、__set__

把这个方法放到最后来说,是因为这个方法的行为十分特别,实现了__get__我们称为Descriptor,直接看例子:


class Person(object):

    name = ''

    def __get__(self, instance, typed):
        print('__get__ is called')
        return self.name


class Blog:
    per = Person()
    per.name = 'Mike'

if __name__ == '__main__':
    b = Blog()
    print(b.per)

"""
输出:
__get__ is called
Mike
"""

当一个类的属性赋予成了Descirptor(类),当这个类再去访问这个属性当时候,就会去访问这个Descriptor的__get__方法,如上面,当去访问b的per属性,会直接返回name的值。
__get__除了self还有必须两个参数:

  • instance: 表示调用Descirptor的类实例或None。如上面当实例访问就是b;如果是直接Blog.per这样访问,那么它的值为None
  • typed: 表示调用Descirptor的类,如上面,就是Blog,但直接使用类访问时

需要注意的时候,如上面,__get__方法直接返回了self.name的值,所以当你想这样去b.per.name是不可行的,因为b.per已经不是Person的实例了。如果需要这样访问,可以直接return self;

同时实现了__get__和__set__则称为Data Descriptor,如果只实现了__get__则称为Non-data Descriptor。

# 省略其他
	def __set__(self, instance, value):
        print('__set__ is called')
        self.name = value + '123'


if __name__ == '__main__':
    b = Blog()
    print(b.per)
    print('-------------')
    b.per = 'Ben'
    print(b.per)

"""
__get__ is called
Mike
-------------
__set__ is called
__get__ is called
Ben123
"""

__set__除了self还有2个必须参数:

  • instance: 表示调用Descirptor的类实例,如上面,就是b
  • value:要赋予的值,如上面的’Ben’

4. __get__、__set__案例

这两个方法再某些场合十分有用,下面是类Flask的Config配置时的一个简化代码,它的设计巧妙运用Descriptor

	
class ConfigAttr:

    def __init__(self, name):
        self._name = name

    def __get__(self, obj, typed=None):
        if obj is None:
            return 'None of app'
        return obj.config[self._name]

    def __set__(self, obj, value):
        obj.config[self._name] = value


class App:

    default_config = {
        'name': 'app',
        'date': '2018-11-15',
        'lang': 'zh',
    }
    name = ConfigAttr('name')
    date = ConfigAttr('date')

    def __init__(self, name=None):
        self.config = dict()
        self.config.update(self.default_config)
        if name is not None:
            self.config['name'] = name


if __name__ == '__main__':
    # --- 测试__get__
    app = App('hello')
    print(app.name)
    app2 = App()
    print(app2.name)
    print(App.name)
    
"""
输出
	hello
	app
	None of app
"""
  • 将App的name和date属性和Config的name和date绑定
  • 当访问或者操作app.name实际上是访问或者操作app.config[‘name’],与以下相比:
self. name = self.config['name']

上述代码,如果name只是一个只用于读(访问)的属性,那么与使用Descriptor区别不大,但如果name可以被改变,但执行app.name = ‘xxx’, 那么name属性和config将彻底失去关联。

5 . 总结

  • __getattr__在访问实例属性不存在的时候,会被调用

  • __setattr__当实例属性被赋值时会被调用

  • __getattribute__在访问任何存在与否的属性都会被调用
    谨慎使用这三个魔法方法, 使用不当会产生很多负面后果,如无限嵌套

  • 实现了__get__的类被称为Descriptior,通过其他类(或者实例)去访问这个类实例的时候会被调用

  • __set__, 与__get__一起使用,通过其他类(或者实例)去赋值这个类实例的时候会被调用

  • 通过类访问属性,通过A.attr访问属性的规则为:

    1. 如果MetaClass中有__getattribute__,则直接返回该__getattribute__的结果。
    2. 如果attr是个Descriptor,则直接返回Descriptor的__get__的结果。
    3. 如果attr是通过属性,则直接返回attr的值
    4. 如果类中没有attr,且MetaClass中定义了__getattr__,则调用MetaClass中的__getattr__
      如果类中没有attr,且MetaClass中没有定义__getattr__,则抛出异常AttributeError
  • 通过类设置属性, 通过A.attr=val给属性赋值时:

    1. 如果MetaClass中定义了__setattr__,则执行该__setattr__
    2. 如果该属性是Descriptor,且定义了__set__,则执行Descriptor的__set__
    3. 如果是普通属性或None-data Descriptor,则直接令attr=val
    4. 如果属性不存在,则动态给类添加该属性,然后进行赋值

6. 参考文档

  • python类属性的访问、设置和删除

你可能感兴趣的:(python)