Python中@property的用法

​ 在编写一些程序的时候,灵活的使用@property能使程序更加的pythonic。@property的作用是能够将类中的一个函数变成此类属性来使用。

​ 举一个简单的例子,我们要编写一个圆的类,可以这样写:

1
2
3
4
class Circle():
def __init__(self, radius):
self.radius = radius # 圆的半径
self.diameter = 2*self.radius # 圆的直径

这样当我们创建一个圆时,能通过半径来表示这个圆的大小:

1
2
3
my_circle = Circle(4)
print(my_circle.radius) # 打印圆的半径
print(my_circle.diameter) # 打印圆的直径

但是当我们创建的圆半径变成6时,我们直接修改它的半径属性:

1
my_circle.radius = 6

​ 这样修改以后,圆的直径却不会跟着半径的变化而变化,还需要我们手动去修改它的直径属性,这样用起来十分不方便。

​ 我们想要的是,当我们修改它的半径时直径也会跟着变成相应的2倍,同时我们修改它的直径时,半径会变成相应的1/2。

​ 这是@property的作用就显现出来了,我们可以这样修改代码:

1
2
3
4
5
6
7
8
9
10
11
class Circle():
def __init__(self, radius):
self.radius = radius

@property
def diameter(self):
return self.radius*2

@diameter.setter
def diameter(self, new_diameter):
self.radius = new_diameter / 2 # 当修改直径时自动修改半径

​ 重写了圆的类后,我们再来试一下:

1
2
3
4
5
6
7
8
9
>>> my_circle = Circle(4)
>>> print(my_circle.radius, my_circle.diameter) # 打印半径和直径
4 8
>>> my_circle.radius = 6
>>> print(my_circle.radius, my_circle.diameter) # 打印半径和直径
6 12
>>> my_circle.diameter = 20
>>> print(my_circle.radius, my_circle.diameter) # 打印半径和直径
10.0 20.0

​ 怎么样,这个圆变的智能了吧。

​ 我们分析一下圆类的代码,@property将挨着下面的diameter函数变成了Circle类的一个属性,访问 my_circle.diameter 时,得到的结果时diameter函数的返回值。下面的 @diameter.setter 装饰的函数,会在 my_circle.diameter = 20 修改diameter属性时被调用。这样我们的圆就能自动地调整半径和直径的大小了。


​ @property的另一个用法是隐藏类的属性,防止被随意修改。我们知道用双下划线定义的属性不能被外部访问和修改,但是这样做不绝对,双下划线只是修改了属性的名字来达到不能访问的目的,实际上在外部通过修改后的名字还是能访问的到的。

​ 当我们不想让别人访问修改我们的内部变量时,我们可以用@property将真是的变量隐藏起来,就像这样:

1
2
3
4
5
6
7
8
9
10
11
class DataSet():
def __init__(self):
self._images = 1
self._labels = 2

@property
def images(self):
return self._images
@property
def labels(self):
return self._labels

​ 这样,当别人在外部访问images时会以为它是一个正常的属性,但是修改它时就会报错 AttributeError: can't set attribute ,因为我们没有定义@images.setter装饰的函数,所以它是不能被修改的。


property 共有3中功能,访问、修改、删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Goods():
def __init__(self):
self._price = 50
# 访问
@property
def price(self):
return self._price

# 修改
@price.setter
def price(self, new_price):
self._price = new_price

# 删除
@price.deleter
def price(self):
print('已删除price属性')

测试三个方法:

1
2
3
4
5
6
7
8
9
10
>>> g = Goods()
>>> g.price # 自动执行@property修饰的方法
50
>>> g.price = 100 # 自动执行@price.setter修饰的方法
>>> g.price
100
>>> del g.price #自动执行@price.delete修饰的方法
已删除price属性
>>> g.price
100

上面你可能会疑问,del g.price 不是将price属性删除了吗,怎么下面访问一下还有呢?这是因为del g.price 只是执行一下@price.delete 修饰的方法,你在这个方法里面写什么,它就只执行什么。


看到这里,你可能已经知道@property 的作用和用法了。其实porperty并不只能通过装饰器来用,它还能在类中直接使用,property方法有4个参数:

静态属性 = property( get_函数, set_函数, del_函数, doc )

这4个参数中前三个与上面的访问、修改、删除相对应,最后一个doc参数用作说明用法。

看一个例子就能够明白:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Goods():
def __init__(self):
self._price = 50
# 访问
def get_price(self):
return self._price

# 修改
def set_price(self, new_price):
self._price = new_price

# 删除
def del_price(self):
print('已删除price属性')

price = property(get_price, set_price, del_price)

上面这个例子与前面那个Goods类所实现的效果相同。


​ 实际开发中,灵活的使用@property可以帮助开发者逐渐完善数据模型。

​ 但是在实际工作中,我们经常接触到的对象,恰恰就是这种接口设计的比较糟糕的,或仅仅充当数据容器的对象。若是代码越写越多、功能越来越膨胀,或是参与项目的人都不考虑长远的维护事宜,就容易出现糟糕的局面。

​ @property固然有效,但是也不能滥用,如果你发现自己正在不停地编写各种 @property 方法,那恐怕就意味着当前这个类的代码写的确实很差。此时,应该彻底重构该类,而不应该继续修补这套糟糕的设计。

引自:《Effective Python》