1. 类属性和实例属性
Python中类和实例都是对象, 可以在定义时或者通过 . 语法设置和读取属性内容.
In [1]: class Demo:
...: cls_name = "Demo"
...: def __init__(self, name):
...: self.name = name
In [2]: demo = Demo('demo')
In [3]: demo.name
Out[3]: 'demo'
In [4]: demo.cls_name
Out[4]: 'Demo'
In [5]: Demo.cls_name
Out[5]: 'Demo'
In [6]: Demo.cls_name = 'New Class Name'
In [7]: demo.cls_name
Out[7]: 'New Class Name'
In [8]: demo.cls_name = 'Custom Name'
In [9]: demo.cls_name
Out[9]: 'Custom Name'
上面的代码示例, 先定义了一个Demo类, 包含一个类属性cls_name, __init__方法中初始化实例的时候, 定义了实例属性name.
demo是Demo类的一个实例, 可以看到通过demo可以访问实例属性name='demo', 当在实例中获取不到一个属性的值的时候, 会尝试去父类中获取, 类似"子债父偿"的概念, 直到所有父类中都不存在, 抛出AttributeError.
如果类属性修改, 则实例中获取到的值, 也会变化.
2. 实例属性的缺陷
直接把变量暴露出去, 好处是便于修改, 写起来方便. 随之而来的缺陷是, 可以被随意修改, 无法做参数检查,和一下动态的属性. 如下例,
In [1]: class Square:
...: def __init__(self, side):
...: self.side = side
...: self.area = side * side
In [2]: squ = Square(5)
In [3]: squ.side
Out[3]: 5
In [4]: squ.area
Out[4]: 25
定义一个正方形类, 有两个属性边长和面积. 对边长属性可以被置成负数显然是不可理的
In [5]: squ.side=-90
一个解决方法是, 通过添加方法set_side()来实现参数的校验.
In [1]: class Demo:
...: def __init__(self, side):
...: self._side = side
...: def set_side(self, length):
...: self._side = max(length, 0)
...: def get_side(self, length):
...: return self._side
...: def get_area(self):
...: return self._side * self._side
In [2]: demo = Demo(6)
In [3]: demo.get_area()
Out[3]: 36
In [4]: demo.set_side(-9)
In [5]: demo.get_side()
Out[5]: 0
这样写虽然实现了参数校验的功能, 需要调用函数来set_side设置和get_side返回属性的值, 调用起来复杂, 可读性来说也不是很友好.
3. 使用@property
@property装饰器会将方法转换为相同名称的属性, 使得你可以用获取属性的方式来调用方法.
In [1]: class Demo:
...: def __init__(self, side):
...: self._side = side
...: @property
...: def side(self):
...: return self._side
In [2]: demo =Demo(3)
In [3]: demo.side
Out[3]: 3
In [3]: demo.side = 90
AttributeError: can't set attribute
通过装饰器@property装饰了的side方法, 当执行demo.side的访问side属性时候,实际执行的是side()方法, side是只读属性, 尝试赋值的时候, 会报 AttributeError. 如果要对property赋值, 需要实现一个attr.setter的方法, 本例中attr就是side.
In [1]: class Demo:
...: def __init__(self, side):
...: self._side = side
...: @property
...: def side(self):
...: return self._side
...: @side.setter
...: def side(self, value):
...: self._side = max(value, 0)
...: @property
...: def area(self):
...: return self._side * self._side
In [2]: demo = Demo(5)
In [3]: demo.area
Out[3]: 25
In [4]: demo.side = -8
In [5]: demo.side
Out[5]: 0
In [6]: demo.area
Out[6]: 0
总结
使用@property, 可以把一个方法变成属性调用, 这样
① 可以在属性的赋值和获取的时候添加例如格式校验等额外功能,
② 提供更加直观, 调用友好的代码.
使用方式:
@property装饰器实现属性的获取, 属性名.setter实现属性的赋值, 属性名.delete 实现属性的删除.