在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