平凡

python中的@property

2018/09/30 Share

@property是python中的gettersetter,它使python的面向对象编程更加简单。


本文译自:
https://www.programiz.com/python-programming/property

一个例子

创建一个摄氏度的类,这个类中可以存储temperature,也可以将其转换成华氏温度

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

我们可以创建这个类的对象,并操纵temperature属性(读和写):

>>> # create new object
>>> man = Celsius()

>>> # set temperature
>>> man.temperature = 37

>>> # get temperature
>>> man.temperature
37

>>> # get degrees Fahrenheit
>>> man.to_fahrenheit()
98.60000000000001

当我们修改或访问类似于temperature的属性,python都会查找这个对象的__dict__字典,甚至可以直接访问这个字典:

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

man.temperature = man.__dict__['temperature']


现在假设我们将代码上线,一群客户使用上面的代码。

一天,一个客户突然跑到你跟前,说:“温度最低只能达到-273.15度!你最好把你的代码再改一下”。

用户的需求就是一切,而且这个用户说的又特别有道理。没办法,只能发布新版本了.

使用getter和setter

一个很简单的解决方案就是将temperature属性设计为私有变量,再使用一个gettersetter来访问它。javaC#常这么干。

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中变量加一道下划线表示私有变量,但如果你想强行访问修改,霸王硬上弓,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

现在代码看起来好像没有什么问题了。但所有使用代码的人都要将obj.temperature更新为obj.get_temperature(),将obj.temperature更新为obj.set_temperature(val)。如果代码量很大,这是很令人头疼的事。

于是property应运而生。

property的优点

解决上面的问题,可以使用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)

代码的最后一行,将temperature设置为property对象。property在代码里下了毒,当你访问或修改temperature属性时,会自动与get_temperatureset_temperature做关联

例如,当你访问temperature,现在不是查找__dict__里的值,而是执行get_temperature()方法;同样,修改它的值会自动执行set_temperature()方法。这是python一个很酷的特性。

甚至,当我们初始化一个对象时,都会自动执行set_temperature()方法。

这是为什么呢?
当初始化一个对象,__init__()方法被调用。其中有一行self.temperature = temperature, 于是自动调用了set_temperature()方法。

同样,只要代码里调用了c.temperature,都会自动调用get_temperature(),例如:

>>> c.to_fahrenheit()
Getting value
98.60000000000001

通过使用property, 没有更改代码就可以增强功能(实现温度的最低限制)。因此代码的向后兼容性是不错的。

最后要注意一点,现在实际存储温度的变量是_temperature. temperature只是一个property对象,它是与私有变量提供通讯的接口。

我们查看dict,能证实这点:

print(c.__dict__)

# result
{'_temperature': 23}

深入了解property

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

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

fget是获取属性的值;fset是设置属性的值;fdel是删除属性。从上面的实现代码,可以看到这几条参数都是可选项。所以,一个property对象可以这样创建:

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

一个property对象有三个方法:getter(), setter()deleter().
也就是说,下面的代码:

temperature = property(get_temperature,set_temperature)

可以拆分成:

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

上面的两种代码都是等价的。

如果你对python装饰器比较了解,就会知道,上面的代码可以通过装饰器来实现。

现在我们不使用get_temperatureset_temperature方法,因为它们污染了类命名空间。所谓污染了类命名空间,就是指我们后面还可能写其他函数,希望这个函数使用get_temperature或者set_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

上面的这种实现方法是推荐大家使用的,它真的非常的简单。

发表日期: September 30th 2018

版权声明: 本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 一个例子
  2. 2. 使用getter和setter
  3. 3. property的优点
  4. 4. 深入了解property