Python @property 装饰器

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

1、没有getters和setters的类

假设我们想要实现一个类来存储摄氏温度,并且这个类提供一个方法能够将摄氏温度转化为华氏温度。一种直观且快速的想法是:

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

那么,我们可以按照如下的方式使用这个类:

# 创建一个对象
human = Celsius()

# 设置温度
human.temperature = 37

# 获取温度的值
print(human.temperature)

# 转化为华氏度
print(human.to_fahrenheit())

输出:

37
98.60000000000001

实事上,当我们对实例的属性(如上述temperature)进行赋值或者取值操作时,Python会在该对象内置的字典对象__dict__查找对应的属性。

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

所以,human.temperature实际上与human.__dict__['temperature']是等价的。

2、使用getters和setters

现在,我们要扩展一下上述Celsius的功能。我们知道,任何物体都无法达到绝对零度(-273.15℃),因此,在给temperature属性赋值时,需要加上这个限制。

一种显而易见的优化方式是,将temperature属性隐藏(设置为受保护的属性),然后定义新的getter和setter方法来操作temperature属性。代码如下:

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

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

    # getter method
    def get_temperature(self):
        return self._temperature

    # setter method
    def set_temperature(self, value):
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible.")
        self._temperature = value

引入的get_temperature()set_temperature()属性,分别用于获取_temperature的值和向_temperature赋值。

另外,temperature被替换成了_temperature,以_开头的变量,属于类的私有变量。

我们可以按如下方式来使用这个类:

human = Celsius(37)

# 获取 _temperature
print(human.get_temperature())

# 转化为华氏度
print(human.to_fahrenheit())

# 设置 _temperature
human.set_temperature(-300)

输出如下:

37
98.60000000000001
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 15, in set_temperature
ValueError: Temperature below -273.15 is not possible.

可以看出,当尝试将温度设置为-300时,会触发程序里面设置的限制规则。

注意:所谓的私有变量,更多的是作为Python编程中的一种约定,而没有限制用户对它的操作,因为用户仍然可以去修改变量的值:

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

上述方法确实是有效的,但需要花费的代价有点大:我们需要将之前程序中所有的obj.temperature改为obj.get_temperature(),所有的obj.temperature = val改为obj.set_temperature(val)。当你需要更该成百上千行这样的代码时,你肯定会感觉烦躁。

也就是说,我们的更改不是向后兼容的。这时,就到了@property大显身手的时候了。

3、property

使用property类,可以让我们的代码更加的Pythonic:

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

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

    # getter
    def get_temperature(self):
        print("Getting value...")
        return self._temperature

    # setter
    def set_temperature(self, value):
        print("Setting value...")
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible")
        self._temperature = value
        print("Setting value success")

    # creating a property object
    temperature = property(get_temperature, set_temperature)

通过在get_temperature()set_temperature()加入打印信息,可以更加清楚地看到代码的调用顺序。最后一行,实际上是通过property类实例化了一个对象temperature,简单来说,property将一些方法附加到成员属性上进行访问。

按如下形式调用上述代码:

human = Celsius(37)

print(human.temperature)

print(human.to_fahrenheit())

human.temperature = -300

输出如下:

Setting value...
Setting value success
Getting value...
37
Getting value...
98.60000000000001
Setting value...
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 18, in set_temperature
ValueError: Temperature below -273.15 is not possible

可以看到,任何尝试获取temperature值的代码都会调用get_temperature()方法,而不是从__dict__中取值;类似的,任何尝试向temperature赋值的代码都会调用set_temperature()方法(可以看到,在用Celsius实例化一个对象时,也是如此)。

通过使用property,我们无需更改原先的代码就实现了温度值的验证,也就是说,我们实现了向后兼容。

注意:温度的值实际上存储在私有变量_temperature所指向的内存空间中,而temperature只是一个为该私有变量提供了一个调用接口而已。

4、@property装饰器

在Python中,property()是一个用于创建property对象的内建类,其调用格式为:

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

其中,

  • fget:指代用于获取属性值的函数
  • fset:指代用于设置属性值的函数
  • fdel:指代用户删除属性的函数
  • doc:表示注释的字符串

在使用时,可以先实例化一个空的property对象,然后再调用property对象的getter()setter()delter()方法来指代fgetfsetfdel。也就是说,下面的写法:

temperature = property()
temperature = temperature.getter(get_temperature)
temperature = temperature.setter(set_temperature)

temperature = property(get_temperature,set_temperature)

是等价的。

对于熟悉Python装饰器的程序员来说,上述功能完全可以用装饰器来实现。简单来说,装饰器的作用就是对其作用域内的函数进行「装饰」,例如,可以用装饰器来将上述代码改写成下面的样子:

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):
        print("Setting value...")
        if value < -273.15:
            raise ValueError("Temperature below -273 is not possible")
        self._temperature = value
        print("Setting value success")

在上面的代码里,在定义取值和赋值操作时,我们重新使用了变量名temperature

同样,使用方式也是向后兼容的:

human = Celsius(37)

print(human.temperature)

print(human.to_fahrenheit())

coldest_thing = Celsius(-300)

输出:

Setting value...
Setting value success
Getting value...
37
Getting value...
98.60000000000001
Setting value...
Traceback (most recent call last):
  File "<input>", line 7, in <module>
  File "<input>", line 3, in __init__
  File "<input>", line 17, in temperature
ValueError: Temperature below -273 is not possible
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值