Python @property

你将学习有关Python @property的知识;更pythonic的方式去使用getters和setters的方法.

目录

Python有一个强大的概念叫做property,它使面向对象的生活变得更加简单。

在定义和详细讨论@property是什么之前,让我们先建立一个直觉,当初为什么需要它

从一个例子开始

让我们假设你决定制作一个能把温度储存在摄氏度的类。它还将实现一种将温度转换成华氏温度的方法。这样做的一个方法如下。

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

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

我们可以从这个类中生成对象,并按照我们的意愿操作温度属性。用Python shell试试。

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

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

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

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

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

当我们分配或检索任何对象的属性,如温度,如上面显示,Python搜索它在对象的__dict__词典。

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

因此,man.temperature内部成为__dict__ [ 'temperature ]

现在,让我们进一步假设我们的类在客户中很流行并且已经在他们的程序中使用它。他们对这个对象做了各种各样的分配。

在一个灾难性的一天,一个值得信赖的客户来到我们面前,建议温度不能低于273摄氏度(热力学的学生可能会争辩说实际上是273.15),也被称为绝对零度。他还要求我们实现这个值约束。作为一个努力争取客户满意的公司,我们愉快地听取了建议并发布了1.01版(升级现有的类)。

使用Getters和Setters

对上述约束的一个明显的解决方案是隐藏温度属性(使其为私有)并定义新的getter and 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("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 to 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)

在shell中发布以下的代码并运行它.

>>> c = Celsius()

我们清楚地观察到正在执行的get_temperature()set_temperature()内部增加了print()函数.

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

任何取回温度值的代码会自动调用get_temperature()代替字典查表。同样地,任何赋值温度会自动调用set_temperature()代码。这是python中的一个很酷的特性。

我们可以从上面看到当我们创建一个对象时会调用set_temperature().

你能猜出为什么吗?

原因是,当一个对象被创建时,__init__()方法被调用。该方法有行self.temperature = temperature。这种分配自动调用set_temperature()

>>> c.temperature
Getting value
0

同样,任何访问像c.temperature会自动调用get_temperature()。这就是 property 所做的。这里还有几个例子。

>>> c.temperature = 37
Setting value

>>> c.to_fahrenheit()
Getting value
98.60000000000001

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

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

深入挖掘Property

在Python中,property()是内置函数创建并返回一个对象的属性。此函数的签名是

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

在那里,fget是获取属性值的函数,fset是设置属性值的函数,fdel是删除属性的函数和doc是一个字符串(如评论)。从实现中可以看出,这些函数参数是可选的。因此,可以简单地创建属性对象如下所示。

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

一个对象的属性有三种方法, getter(), setter(), and delete() to specify fget, fset and fdel at a later point. This means, the line

temperature = property(get_temperature,set_temperature)

could have been broken down as

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

这两段代码是等价的。

Programmers familiar with decorators in Python can recognize that the above construct can be implemented as decorators.

We can further go on and not define names get_temperature and set_temperature as they are unnecessary and pollute the class namespace. For this, we reuse the name temperature while defining our getter and setter functions. This is how it can be done.

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

上面的实现是既简单又推荐的制作属性的方法。在python中查找属性时,您很有可能会遇到这些类型的构造。

好了,今天就到这里。

参考链接

https://www.programiz.com/python-programming/property

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值