个性化阅读
专注于IT技术分析

python3 @property的用法 – Python3教程

上一章Python教程请查看:python3装饰器decorator

你将了解Python 的@property,python使用getter和setter的方法。

Python有一个非常好的概念叫做property,它使面向对象程序员的生活变得更加简单。

在定义和深入了解@property是什么之前,让我们首先建立一个关于为什么需要它的直觉。

@property第一个例子

假设你决定创建一个以摄氏度为单位存储温度的类,还将实现一种将温度转换为华氏温度的方法,其中一种方法如下。

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

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

我们可以从这个类中创建对象,并按自己的意愿操作属性temperature,在Python shell上试试这些。

>>> # 创建一个新对象
>>> man = Celsius()
>>> # 设置temperature
>>> man.temperature = 37
>>> # 获取temperature
>>> man.temperature
37
>>> # 得到华氏度
>>> man.to_fahrenheit()
98.60000000000001

在转换为华氏温度时,额外的小数位数是由于浮点算术错误(在Python解释器中尝试1.1 + 2.2)造成的。

当我们分配或检索任何对象属性(如上面所示)时,Python会在对象的__dict__字典中搜索它。

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

因此,man.temperature在内部变成了man.__dict__[‘temperature’]。

现在,让我们进一步假设我们的类在客户中很受欢迎,并且他们开始在他们的程序中使用它,他们对对象做了各种各样的赋值。

有一天,一个值得信赖的客户来找我们,建议温度不能低于-273摄氏度(热力学专业的学生可能会说实际上是-273.15摄氏度),也叫绝对零度。他进一步要求我们实现这个值约束,作为一家追求客户满意度的公司,我们很高兴地听取了这个建议,并发布了1.01版本(对现有类的升级)。

使用getter和setter

对上述约束的一个明显的解决方案是隐藏属性temperature(使其成为私有),并定义新的getter和setter接口来操作它,可以这样做。

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("零下273度是不可能的")
        self._temperature = value

我们可以看到,上面定义了新的方法get_temperature()和set_temperature(),而且还用_temperature代替了temperature。在Python中,开头的下划线(_)用于表示私有变量。

>>> c = Celsius(-277)
Traceback (most recent call last):
...
ValueError: 零下273度是不可能的
>>> c = Celsius(37)
>>> c.get_temperature()
37
>>> c.set_temperature(10)
>>> c.set_temperature(-300)
Traceback (most recent call last):
...
ValueError: 零下273度是不可能的

这次更新成功地实现了新的限制,我们不再被允许将温度设置在-273以下。

请注意,在Python中不存在私有变量,有一些简单的准则可以遵循,语言本身没有任何限制。

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

但这不是很大的问题。 上述更新的最大问题在于,所有在程序中实现了上一类的客户端都必须将其代码从obj.temperature修改为obj.get_temperature(),并将所有分配(如obj.temperature = val修改为obj.set_temperature( val)。

这种重构可能会给客户带来数十万行代码的麻烦。

总而言之,我们的新更新不向后兼容,这就是使用@property的地方了。

@ property的功能

处理上述问题的python方法是使用属性,这是我们可以如何实现它。

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("获取值")
        return self._temperature

    def set_temperature(self, value):
        if value < -273:
            raise ValueError("零下273度是不可能的")
        print("设置值")
        self._temperature = value

    temperature = property(get_temperature,set_temperature)

然后,在shell中运行以下代码。

>>> c = Celsius()

我们在get_temperature()和set_temperature()中添加了print()函数,以便清楚地观察它们的执行情况。

代码的最后一行,表示属性对象的温度。简单地说,property将一些代码(get_temperature和set_temperature)附加到成员属性访问(temperature)。

检索温度值的任何代码都将自动调用get_temperature(),而不是字典查找,类似地,任何为temperature赋值的代码都会自动调用set_temperature(),这是Python中一个很酷的特性。

我们可以看到,即使在创建对象时也调用了set_temperature()。

@property是如何工作的?

原因是创建对象时,将调用__init __()方法,此方法有一行代码为self.temperature = temperature,此分配自动为set_temperature()。

>>> c.temperature
获取值
0

同样,任何诸如c.temperature之类的访问都将自动调用get_temperature(),这就是@property的作用,这里还有一些例子。。

>>> c.temperature = 37
设置值
>>> c.to_fahrenheit()
获取值
98.60000000000001

通过使用属性,我们可以看到,我们修改了类并实现了值约束,而不需要对客户机代码进行任何更改。因此,我们的实现是向后兼容的,每个人都很高兴。

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

深入理解@property

在Python中,property()是一个内置函数,它创建并返回一个property对象。这个函数的特征是

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

其中,fget是获取属性值的函数,fset是设置属性值的函数,fdel是删除属性的函数,doc是字符串(如注释)。从实现中可以看出,这些函数参数是可选的,因此,可以简单地按照以下方式创建属性对象。

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

属性对象有三个方法,getter()、setter()和deleter(),用于稍后指定fget、fset和fdel,这意味着,下面的代码:

temperature = property(get_temperature,set_temperature)

可以分解成

temperature = property()
# 分配fget
temperature = temperature.getter(get_temperature)
# 分配fset
temperature = temperature.setter(set_temperature)

这两段代码是等价的。

熟悉Python中decorator的程序员可以认识到,上面的结构可以作为decorator实现。

我们可以更进一步,不定义名称get_temperature和set_temperature,因为它们是不必要的,并且会影响类命名空间。为此,我们在定义getter和setter函数时重用了名称temperature,这是可以做到的。

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("获取值")
        return self._temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("零下273度是不可能的")
        print("设置值")
        self._temperature = value

上面的实现是创建属性的一种简单而推荐的方法,在Python中查找属性时,你很可能会遇到这些类型的构造。

赞(0)
未经允许不得转载:srcmini » python3 @property的用法 – Python3教程

评论 抢沙发

评论前必须登录!