python-property、__get__、__set__
property
property装饰器的应用来自这样一个问题:如果对实例的属性值不加以限制,那么实例的属性值会出现明显不合理的情况,为了解决这个问题也许你会思考在对属性的修改时利用实例方法加以限制,但python中引入了@property 装饰器更方便的解决这个问题。
class Person(object):
def __init__(self, age=1):
self.age = age
p = Person()
p.age = -1 # 这显然是不合理的
class Person(object):
def __init__(self):
self._age = 1 # 私有属性,外部无法直接修改和访问
def age_setter(self, age):
if 0 < age < 120 and isinstance(age, int):
self._age = age
else:
raise ValueError('age must between 0-120 and be integer')
def age_getter(self):
return self._age
# 这时虽然做到了属性值的控制,但是每次设置和获取值的时候要调用不同的方法,不太方便
@property 装饰器兼顾了方便和控制,让实例的使用显得更加优雅,提高了可用性。另外,@property装饰器不设置setter时,就是一个只读属性,相当于对属性起到了保护作用,如下实验
class Person(object):
def __init__(self):
self._height = 1
@property # 这里就是height的getter
def height(self):
return self._height
@height.setter
def height(self, height):
if 0 < height < 220 and isinstance(height, int):
self._height = height
else:
raise ValueError('height must between 0-220 and be an integer')
@property
def normal_weight(self):
return round(22.86*(self.height/100)**2*2, 2)
p = Person()
p.height = 190
print(p.height)
print(p.normal_weight)
------ 结果 ——————
190
165.05
__set__ 和 _get_
理解set和get方法,实际上必须知道描述器是什么,成为一个描述器,一个类必须至少有__get__
,__set__
,__delete__
方法被实现。如果一个对象同时定义了 __get__()
和 __set__()
,它叫做资料描述器(data descriptor)。仅定义了 __get__()
的描述器叫非资料描述器(non-data descriptor)。
__get__(self, obj, type=None) --> value
定义了当描述器的值被取得的时候的行为。instance是拥有该描述器对象的一个实例。owner是拥有者本身
__set__(self, obj, value) --> None
定义了当描述器的值被改变的时候的行为。instance是拥有该描述器类的一个实例。value是要设置的值。
__delete__(self, instance) --> None
定义了当描述器的值被删除的时候的行为。instance是拥有该描述器对象的一个实例。
几个注意事项:资料描述器的执行顺序优先于实例字典,而实例字典的执行顺序优先于非资料描述器,重写getattribute可能会阻止描述器的使用。关于属性查找优先顺序的问题,后面会写一篇博客描述和实验,这里不重复。
class Descriptor:
def __get__(self, instance, owner):
print('1 get called,', 'instance is', instance, ',owner is', owner)
return instance._a
def __set__(self, instance, value):
print('2 set called,', 'instance is', instance, ',value is', value)
instance._a = value * 2
class T:
desc = Descriptor() # 类方法
def __init__(self):
self._a = 123
t = T()
t.desc = 5
print('result:', t.desc)
其实这里的使用已经很像@property了,但@property则更加简单方便。
--------结果------------
2 set called, instance is <__main__.T object at 0x00000182D90DC630> ,value is 5
1 get called, instance is <__main__.T object at 0x00000182D90DC630> ,owner is <class '__main__.T'>
result: 10
描述器协议是一个理解python内部机制的知识点,属性(property), 方法(bound和unbound method), 静态方法和类方法都是基于描述器协议的。
参考:
https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html