Python Property

Python有个非常好的概念叫做属性(property), 它让面向对象编程变得更加简单. 想详细说Python的属性之前,我们先看看为什么它是那么重要。

一个简单的例子假设有一天你需要一个类来存储摄氏度. 它还将实现一个方法将摄氏度转换为华氏度。这样做的方法之一是这样的。

class Celsius:    def __init__(self, temperature = 0):        self.temperature = temperature    def to_fahrenheit(self):        return (self.temperature * 1.8) + 32

像我们期望的那样,我们可以改变类的属性和操作温度。

>>> # create new object>>> man = Celsius()>>> # set temperature>>> man.temperature = 37>>> # get temperature>>> man.temperature37>>> # get degrees Fahrenheit>>> man.to_fahrenheit()98.60000000000001

额外的小数位是因为Python内部的浮点数算法问题 (在Python解释器中尝试 1.1 + 2.2). 每次我们操作像temperature这样的属性,Python会去查找对象的 __dict__字典。就像下面这样

>>> man.__dict__{'temperature': 37}

因此,man.temperature 在内部就变成 man.__dict__['temperature'].现在,我们继续假设我们的类在客户中流行,他们在他们的程序中使用我们的类。他们在将我们的类应用于各种各样的场景。直到有一天,一个用户反馈给我们,不应该接收小于-273的摄氏度(-273.15是绝对零度)。他希望我们对这个值进行约束,为了满足用户的需求,我们将我们的类升级到1.01版本。

使用 Getters 和 Setters

一个显而易见的方法是我们隐藏temperature属性,将它私有化。而使用gettersetter接口来操作它,像下面这样。

class Celsius:    def __init__(self, temperature = 0):        self.set_temperature(temperature)    def to_fahrenheit(self):        return (self.get_temperature() * 1.8) + 32    # new update    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

我们可以看到方法get_temperature()set_temperature() 的定义。 此外, temperature被替换成了_temperature. 在Python中,下划线在属性开头表示私有属性。

>>> c = Celsius(-277)Traceback (most recent call last):...ValueError: Temperature below -273 is not possible>>> c = Celsius(37)>>> c.get_temperature()37>>> c.set_temperature(10)>>> c.set_temperature(-300)Traceback (most recent call last):...ValueError: Temperature below -273 is not possible

这次升级成功的增加了一个新的约束,我们不在接受小于273的值

但是,Python本身是不支持私有属性的,添加下划线的做法只是一种约定。语言本身不提供任何约束。

>>> c._temperature = -300>>> c.get_temperature()-300

但是这不是重点。最大的问题是,对于上次的更新,我们的客户需要将他们的代码从obj.temperature改为obj.get_temperature()obj.temperature = val 改为obj.set_temperature(val). 这样也许会对他们造成成千上万行代码的修改量。总之,我们的更新不是向后兼容的。这需要property来帮忙。

Property的威力

pythonic的方法来处理这个问题是使用property.下面是具体的做法。

class Celsius:    def __init__(self, temperature = 0):        self.temperature = temperature    def to_fahrenheit(self):        return (self.temperature * 1.8) + 32    def get_temperature(self):        print("Getting value")        return self._temperature    def set_temperature(self, value):        if value < -273:            raise ValueError("Temperature below -273 is not possible")        print("Setting value")        self._temperature = value    temperature = property(get_temperature,set_temperature)

我们向get_temperature()get_temperature()各添加了一个print()函数,来观察他们的执行情况。最后一行,我们创建一个property对象temperature.

简而言之, property 连接get_temperatureset_temperature来负责成员属性访问(temperature). 任何代码想要取得temperature的值的时候,都会自动的调用get_temperature()代替__dict__字典_中查找。

同样的,任何代码想给temperature赋值的时候都会自动调用set_temperature().这个Python中一个非常cool的特性。让我们看看它的实际应用。

>>> c = Celsius()Setting value

我们可以看到当我们创建一个对象的时候set_temperature()被调用.你能猜到这事为什么吗。原因是创建一个对象的时候,会调用初始化方法__init__()(最先调用的方法是__new__()). __init__()中有一行

self.temperature = temperature. 这个语句会自动调用set_temperature().

>>> c.temperatureGetting value0

同样的,像c.temperature这样的会自动调用get_temperature(). 这就是property做的事情,我们来看更多的例子。

>>> c.temperature = 37Setting value>>> c.to_fahrenheit()Getting value98.60000000000001

通过使用属性,我们可以看到,我们修改类和实现约束没有任何需要客户端代码的地方。因此我们的实现是向后兼容的,每个人都快乐。

最后注意,实际温度的值存储在私有变量_temperature中。属性temperature是一个为私有变量提供接口的property对象。

深入 Property

在Python中property()是一个内建函数,创建并返回一个property对象。函数的定义如下。

property(fget=None, fset=None, fdel=None, doc=None)

fget是获取属性的值的函数,fset是设置属性值的函数,fdel是删除属性的函数,doc是一个字符串(like a comment).从实现来看,这些参数都是可选的,所以可以向下面这样简单的创建一个property对象。

>>> property()<property object at 0x0000000003239B38>

property有三个方法getter(), setter()delete() 来指定fget, fsetfdel。这表示以下这行

temperature = property(get_temperature,set_temperature)

可以被分解为

# make empty propertytemperature = property()# assign fgettemperature = temperature.getter(get_temperature)# assign fsettemperature = temperature.setter(set_temperature)

这两块代码是等价的。熟悉Python装饰器的可以发现,上面这种结构可以用装饰器来实现。我们可以不定义get_temperatureset_temperature 他们是不必要的,而且污染了类的命名空间。因此,我们重用temperature这个名字,当我们定义gettersetter的时候。这是可以做到的。

class Celsius:    def __init__(self, temperature = 0):        self._temperature = temperature    def to_fahrenheit(self):        return (self.temperature * 1.8) + 32    @property    def temperature(self):        print("Getting value")        return self._temperature    @temperature.setter    def temperature(self, value):        if value < -273:            raise ValueError("Temperature below -273 is not possible")        print("Setting value")        self._temperature = value

上面的是实现property的简单而又被推荐的方法。在Python中寻找property的时候你很有可能遇到这种设计。

Python Property

相关文章:

你感兴趣的文章:

标签云: