深入理解python属性

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wenzhou1219/article/details/83927663

在java等编译语言中,我们倾向于将成员变量设为private,通过public的get/set方法来访问对应的字段,这样做的好处在于get/set起到了拦截的作用,在这个点我们可以插入自己的逻辑,也就是常说的AOP(面向切面编程),比如日志、缓存、延迟创建等操作。

但是python并不推荐这种方式,主要是python的“约定大于配置“原则决定私有成员变量并不是完全不可访问,实际上只是变了下名字隐藏而已,所以设为私有没有意义,正常使用需要对外可读写的成员变量一律推荐设为public

注意这里说的的字段和属性的区别,在python语言中类成员字段也称为属性,而在C#等中二者严格区分(成员变量通常被称为字段,对应的包装方法或名称称为属性)。尽管python中不推荐private属性,但是它也有自己实现属性AOP的一套方式,效果类似get/set方法而且更为灵活。

@Property修饰器

@Property可以实现get/set方法,如下:

class Person(object):
    def __init__(self):
        self._name = None
        self._eng_name = None

    @property
    def name(self):
        print('---Get name value---')
        return self._name

    @name.setter
    def name(self, name):
        print('---Set name value---')
        self._name = name
        self._eng_name = name+'_eng'


if __name__ == '__main__':
    p = Person()
    p.name = 'wenzhou'
    print("eng_name=", p._eng_name)

这里:@property实现get方法 ,name_setter实现set方法,和get/set方法效果一致,对应name为新增的动态属性

注意:

1.python 2.X实现property的必须为新式类(继承object)

2.成员变量(_name)和动态属性(name)命名不能一样,否则会陷入死循环

描述符机制

尽管上述@property可以模拟set/get方法,但是只能在当前类和其子类中生效,不同的类之间没法公用。仔细思考这个问题,对每个属性的get/set实际上是对属性本身的一个约束,所以可以将不同的字段约束抽象到一个类中来描述,这就是描述符。

比如,我们定义如下类:

class ValueRow(object):
    value_1 = MyField()
    value_2 = MyField()

    def __init__(self):
        self.value_3 = 10

这里的value_1和value_2属性我们都指定成MyField类实例,在MyField类中实现对应的约束描述协议,如下:

from weakref import WeakKeyDictionary

class MyField(object):
    def __init__(self):
        print 'init MyField'
        self._values = WeakKeyDictionary()

    def __get__(self, instance, owner):
        return self._values.get(instance, None) if instance else None

    def __set__(self, instance, value):
        if not (0<=value<=100):
            raise ValueError("Must between 0 and 100.")

        self._values[instance] = value

实现这里的__get__和__set__方法的即为描述符,每当对应的字段取值和设值的时候就会调用这里的get/set方法完成。每个字段的描述符在这个类中导入完成后,就会在内存中创建一个对应描述符实例,就算有多个类实例也只会公用这一个描述符实例,因此必须按照instance为key来存储值

另外,注意这里_values按照Instance为key索引值,持有一份索引,会导致内存泄漏,因此改用weakref或者直接使用setattr/getattr来保存实例相关的数据。

如下调用:

if __name__ == '__main__':
    pass

输出如下:

init MyField
init MyField

可见,MyField在类导入完成后即创建了一份实例。

再如下调用:

if __name__ == '__main__':
    v1 = ValueRow()
    v1.value_1 = 90
    print 'value 1=%d ' %v1.value_1

    v2 = ValueRow()
    v2.value_1 = 91
    print 'value 2=%d ' %v2.value_1

    v2.value_1 = 101

输出如下:

Connected to pydev debugger (build 181.5087.37)
init MyField
init MyField
value 1=90 
value 2=91 
Traceback (most recent call last):...

可以看到两个ValueRow实例只初始化创建ValueRow一次,v2.value_1=101不满足0<=value<=100,所以抛出了异常。

属性Hook

python最大的特点在于其开放性,对于python类允许我们重写其属性获取和设置方法,如下重写各个魔术方法如下:

# __getattr__属性存在则不再调用
class LazyDB(object):
    def __getattr__(self, item):
        print('get attr ' + item)

        v = 'new '+item
        # self.item = v #不可行
        setattr(self, item, v)
        return v

# __getattribute__始终调用
class ValidateDB(object):
    def __getattribute__(self, item):
        print('get attribute ' + item)

        try:
            return super(ValidateDB).__getattribute__(item) # 从属性字典获取,避免循环嵌套
        except AttributeError:
            v = 'validate '+item
            setattr(self, item, v)
            return v

# __setattr__始终调用
class LoggingDB(object):
    def __setattr__(self, key, value):
        print("the value of %s is %s" % (key, value))
        self.__dict__[key] = value                           # 直接设置属性字典,避免循环嵌套

这里可以重写的函数方法为__getattr__、__getattribute__、__setattr__,其中2、3始终调用,1只有在属性不存在时才调用。这里分别实现数据库延迟创建,数据字段合法校验和简单日志记录等AOP功能。

演示代码下载链接

原创,转载请注明来自http://blog.csdn.net/wenzhou1219 

猜你喜欢

转载自blog.csdn.net/wenzhou1219/article/details/83927663