Fluent Python 9.6节讲到hashable Class,
为了使Vector2d类可散列,有以下条件:
(1)实现__hash__方法
(2)实现__eq__方法
(3)让Vector2d向量不可变
如何让Vector2d类实例的向量只读呢?可以使用property,如下所示:
1 class Vector2d: 2 def __init__(self, x, y): 3 self.__x = x 4 self.__y = y 5 6 @property # The @property decorator marks the getter method of a property. 7 def x(self): 8 return self.__x 9 10 @property # The @property decorator marks the getter method of a property. 11 def y(self): 12 return self.__y 13 14 def __hash__(self): 15 return hash(self.__x) ^ hash(self.__y) 16 17 def __eq__(self, other): 18 return hash(self) == hash(other) 19 20 def __iter__(self): 21 return (i for i in (self.__x, self.__y))
现在我们在控制台尝试修改x或者y:
>>> import Example9_7 >>> v1 = Example9_7.Vector2d(3, 4) >>> v1.x 3 >>> v1.x = 4 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute >>> v1.y = 5 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute
这是我们想要的行为,但是为什么加上@properly装饰器后就变为只读了呢?我们需要对property有更深入的了解。
An Example To Begin With:
在深入了解property之前,我们先来看看property的应用场景:
假设我们写了一个关于温度的类:
class Celsius: def __init__(self, temperature=0): self.temperature = temperature def get_fahrenheit(self): return self.temperature * 1.8 + 32
并且这个类渐渐变的很流行,被很多用户所调用,有一天,一个用户跑来建议说,温度不应该低于绝对温度-273摄氏度,他要求我们实现这个限制。
为了这样实现用户的要求,我们更新为v1.1:
class Celsius: def __init__(self, temperature=0): self.__temperature = temperature def get_fahrenheit(self): return self.__temperature * 1.8 + 32 def get_temperature(self): return self.__temperature def set_temperature(self, value): if value < -273: raise ValueError("Temperature below -273 is not possible.") self.__temperature = value
用户的要求是实现了 可以这里有个问题,用户的代码里任然是这样获取温度的:
c = Celsius(37)
c.temperature = 20
current_temperature = c.temperature
而且代码里有成百上千行如此的代码,这些代码不得不改为:
c.set_temperature(20)
c.get_temperature()
对于用户来说这是很头疼的问题,因为我们的修改不是backward compatible.
The Power of @property:
对于这个问题,更为Pythonic的解决方式如下:
1 class Celsius: 2 def __init__(self, temperature=0): 3 self.__temperature = temperature 4 5 def get_fahrenheit(self): 6 return self.__temperature * 1.8 + 32 7 8 def get_temperature(self): 9 return self.__temperature 10 11 def set_temperature(self, value): 12 if value < -273: 13 raise ValueError("Temperature below -273 is not possible.") 14 self.__temperature = value 15 16 temprature = property(get_temperature, set_temperature)
这样,用户任然像以前一样访问temprature:
>>> c1 = property_demo.Celsius(10) >>> c1.temprature 10 >>> c1.temprature = 20 >>> c1.temprature 20
因此我们既实现了对termperature的限制,有保证了向后兼容
Digging Deeper into Property:
在Python里,property()是一个内建函数(Built-in function),它返回property 对象,函数原型是:
property(fget=None, fset=None, fdel=None, doc=None)
# fget is function to get value of the attribute, fset is function to set value of the attribute, fdel is function to delete the attribute and doc is a string (like a comment).
>>> property()
<property object at 0x7f50058bc5e8>
我们之前的示例可以分解为:
# make empty property temperature = property() # assign fget temperature = temperature.getter(get_temperature) # assign fset temperature = temperature.setter(set_temperature)
我们也可以用装饰器来实现以上的功能:
class Celsius: def __init__(self, temperature=0): self.__temperature = temperature def get_fahrenheit(self): return self.__temperature * 1.8 + 32 @property def temperature(self): return self.__temperature @temperature.setter def temperature(self, value): if value < -273: raise ValueError("Temperature below -273 is not possible") self.__temperature = value
装饰器版本是更为简单,推荐的方式。