property特性解决什么问题: 属性的存取检查 (封装)
Fluent Python中的经典例子就是商品价格设置,商品价格不能为负数,所以设置价格属性的时候必须进行检查。
方法一、如果没有特性,可以怎么做
- 禁止 ‘.’ 运算符直接访问属性。这个通过私有属性来完成。
- 设置存取方法。
这也是C++的做法。由于python中没有访问控制符,可以使用双下划线隐藏属性。
class GoodItem:
def __init__(self, price):
self.set_price(price)
def set_price(self, price):
if price > 0:
self.__price = price
else:
print("Error happened: the price \
has to greater than zero")
def get_price(self):
return self.__price
m1 = GoodItem(10)
print(m1.get_price())
m1.set_price(-1)
方法一已经能达到封装效果,但是python还想要个自行车。
我们希望封装细节这种东西,python在类里面自己解决,对外的时候 obj.attr 还是可行的,而不用调额外的接口。
也就是这样:
print(m1.price) #打印价格
m1.price = -1 #价格设置不合理,报错
所以这就需要特性了。
方法二、创建特性
# 设置属性的方法1
class GoodItem:
def __init__(self, price):
self.price = price
def set_price(self, price):
if price > 0:
self.__price = price
else:
print("Error happened: the price \
has to greater than zero")
def get_price(self):
return self.__price
price = property(get_price, set_price)
m2 = GoodItem(10)
print(m2.price)
m2.price = -1
这里注意到几个设置特性的问题
1、特性的名称怎么确定
名称完全由property返回值绑定到哪个变量来决定。
price = property(get_price, set_price)
# wtf = property(get_price, set_price)
上述例子绑定到price,因此调用属性的时候是使用obj.price。
如果我绑定到wtf,那么调用属性的时候就要用obj.wtf。
2、特性的值被存储在哪里
在set_price看到,属性的值实际上是存到了self. __price里面。从外部接口看,我创建了个属性price,实际上这个属性对实例来说并不存在,真正存在的是__price。而特性实际上是个类属性。
3、为什么price没有self,get_price和set_price也没有self
上面说过了特性其实是个类属性,具体是怎么回事详见续集3。至于get_price和set_price为什么没有self,暂时还不清楚。通常来讲调用类中的方法,不管是类、静态还是实例方法,要么使用self,要么使用cls,除非是内部函数,可以直接使用。这方面的问题还没看到相关资料可以解释。
class GoodItem:
def __init__(self, price):
self.price = price
@property
def price(self):
return self.__price
@price.setter
def price(self, p):
if p > 0:
self.__price = p
else:
print("Error happened: the price \
has to greater than zero")
另一种方法是使用特殊的装饰器(@property)。这时候叫啥取决于@property装饰的方法,这个例子里面是price,所以属性名称就是price,注意这是get方法。并且get和set方法必须是同名的。
一个需要注意的问题:
上面说过特性的值存储位置。这个变量的名称有两个选择:(这真是一句废话)
- 和特性同名
- 和特性不同名,这是上述例子的做法,使用__price保存特性的值。
那么假设我们要同名咋办,拍脑袋一想可能是这样
def price(self, p):
if p > 0:
self.price = p #这是个错误示范
else:
print("Error happened: the price \
has to greater than zero")
self.price=p是要调用price方法进行set的,在price如果还用self.price=p,那就是无穷递归了。所以同名情况下self.price=拍这种是不能在set方法里面用的。
self.__dict__[‘price’] = price #这是个正确示范
obj.__dict__保存的是实例的实例属性。这次price真的是实例属性了,但是由于被特性覆盖了,每次调用obj.price其实是由特性price来执行操作,而不是直接存取实例属性price。如果把特性删了,那么实例price就会暴露出来。具体详见2。
续集:
2、属性的存取的内在逻辑
3、property的到底是怎么回事