欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python @property 详解

程序员文章站 2022-06-01 17:38:49
本文讲解了 Python 的 property 特性,即一种符合 Python 哲学地设置 getter 和 setter 的方式。 ......

本文讲解了 python 的 property 特性,即一种符合 python 哲学地设置 getter 和 setter 的方式。

python 有一个概念叫做 property,它能让你在 python 的面向对象编程中轻松不少。在了解它之前,我们先看一下为什么 property 会被提出。

一个简单的例子

比如说你要创建一个温度的类celsius,它能存储摄氏度,也能转换为华氏度。即:

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

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

我们可以使用这个类:

>>> # 创建对象 man
>>> man = celsius()

>>> # 设置温度
>>> man.temperature = 37

>>> # 获取温度
>>> man.temperature
37

>>> # 获取华氏度
>>> man.to_fahrenheit()
98.60000000000001

最后额外的小数部分是浮点误差,属于正常现象,你可以在 python 里试一下 1.1 + 2.2

在 python 里,当我们对一个对象的属性进行赋值或估值时(如上面的temperature),python 实际上是在这个对象的 __dict__字典里搜索这个属性来操作。

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

因此,man.temperature实际上被转换成了man.__dict__['temperature']

假设我们这个类被程序员广泛的应用了,他们在数以千计的客户端代码里使用了我们的类,你很高兴。

突然有一天,有个人跑过来说,温度不可能低于零下273度,这个类应该加上对温度的限制。这个建议当然应该被采纳。作为一名经验丰富的程序员,你立刻想到应该使用 setter 和 getter 来限制温度,于是你将代码改成下面这样:

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

    def to_fahrenheit(self):
        return (self.get_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

很自然地,你使用了“私有变量”_temperature来存储温度,使用get_temperature()set_temperature()提供了访问_temperature的接口,在这个过程中对温度值进行条件判断,防止它超过限制。这都很好。

问题是,这样一来,使用你的类的程序员们需要把他们的代码中无数个obj.temperature = val改为obj.set_temperature(val),把obj.temperature改为obj.get_temperature()。这种重构实在令人头痛。

所以,这种方法不是“向下兼容”的,我们要另辟蹊径。

@property 的威力!

想要使用 python 哲学来解决这个问题,就使用 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)

我们在class celsius的最后一行使用了一个 python 内置函数(类) property()。它接受两个函数作为参数,一个 getter,一个 setter,并且返回一个 property 对象(这里是temperature)。

这样以后,任何访问temperature的代码都会自动转而运行get_temperature(),任何对temperature赋值的代码都会自动转而运行set_temperature()我们在代码里加了print()便于测试它们的运行状态。

>>> c = celsius()  # 此时会运行 setter,因为 __init__ 里对 temperature 进行了赋值
setting value

>>> c.temperature  # 此时会运行 getter,因为对 temperature 进行了访问
getting value
0

需要注意的是,实际的温度存储在_temperature里,temperature只是提供一个访问的接口。

深入了解 property

正如之前提到的,property()是 python 的一个内置函数,同时它也是一个类。函数签名为:

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

其中,fget是一个 getter 函数,fset是一个 setter 函数,fdel是删除该属性的函数,doc是一个字符串,用作注释。函数返回一个 property 对象。

一个 property 对象有 getter()setter()deleter()三个方法用来指定相应绑定的函数。之前的

temperature = property(get_temperature,set_temperature)

实际上等价于

# 创建一个空的 property 对象
temperature = property()
# 绑定 getter
temperature = temperature.getter(get_temperature)
# 绑定 setter
temperature = temperature.setter(set_temperature)

这两个代码块等价。

熟悉 python 装饰器的程序员肯定已经想到,上面的 property 可以用装饰器来实现。

通过装饰器@property,我们可以不定义没有必要的 get_temperature()set_temperature(),这样还避免了污染命名空间。使用方式如下:

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

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

    # getter 装饰器
    @property
    def temperature(self):
        print("getting value")
        return self._temperature

    # setter 装饰器
    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise valueerror("temperature below -273 is not possible")
        print("setting value")
        self._temperature = value

你可以使用装饰器,也可以使用之前的方法,完全看个人喜好。但使用装饰器应该是更加 pythonic 的方法吧。

参考

python @property

(本文完)