📜  Python @property装饰器

📅  最后修改于: 2020-09-19 14:11:00             🧑  作者: Mango

在本教程中,您将学习Python @property装饰器。一种在面向对象程序设计中使用getter和setter的pythonic方式。

Python编程为我们提供了一个内置的@property装饰器,它使面向对象编程中的getter和setter的使用更加容易。

在详细介绍@property装饰器是什么之前,让我们首先了解一下为什么首先需要使用它的直觉。

没有getter和setter的类

让我们假设我们决定建立一个以摄氏度为单位存储温度的类。它还将实现一种将温度转换为华氏度的方法。一种方法如下:

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

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

我们可以根据需要制作对象,并根据需要操纵temperature属性:

# Basic method of setting and getting attributes in Python
class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature

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


# Create a new object
human = Celsius()

# Set the temperature
human.temperature = 37

# Get the temperature attribute
print(human.temperature)

# Get the to_fahrenheit method
print(human.to_fahrenheit())

输出

37
98.60000000000001

转换为华氏温度时,多余的小数位是由于浮点运算错误。要了解更多信息,请访问Python浮点运算错误。

每当我们如上所述分配或检索任何对象属性(例如temperature , Python都会在对象的内置__dict__字典属性中进行搜索。

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

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

使用getter和setter

假设我们想扩展上面定义的Celsius类的可用性。我们知道任何物体的温度都不能低于-273.15摄氏度(热力学中的绝对零)

让我们更新代码以实现此值约束。

上述限制的一个明显解决方案是隐藏属性temperature (使其私有)并定义新的getter和setter方法来对其进行操作。可以按照以下步骤进行:

# Making Getters and Setter methods
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 。开头的下划线_表示Python的私有变量。

现在,让我们使用以下实现:

# Making Getters and Setter methods
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


# Create a new object, set_temperature() internally called by __init__
human = Celsius(37)

# Get the temperature attribute via a getter
print(human.get_temperature())

# Get the to_fahrenheit method, get_temperature() called by the method itself
print(human.to_fahrenheit())

# new constraint implementation
human.set_temperature(-300)

# Get the to_fahreheit method
print(human.to_fahrenheit())

输出

37
98.60000000000001
Traceback (most recent call last):
  File "", line 30, in 
  File "", line 16, in set_temperature
ValueError: Temperature below -273.15 is not possible.

此更新成功实施了新限制。我们不再被允许将温度设置为低于-273.15摄氏度。

但是,上述更新的更大问题是,实现我们上一类的所有程序都必须将其代码从obj.temperature修改为obj.get_temperature()并将所有表达式从obj.temperature = valobj.set_temperature(val)

在处理成千上万行代码时,这种重构可能会引起问题。

总而言之,我们的新更新不向后兼容。这是@property救援的地方。

物业类别

解决上述问题的一种Python方法是使用property类。这是我们如何更新代码的方法:

# using property class
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

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

我们在get_temperature()set_temperature()内部添加了print() 函数 ,以清楚地观察它们是否正在执行。

代码的最后一行使属性对象temperature 。简而言之,property将一些代码( get_temperatureset_temperature )附加到成员属性访问( temperature )。

让我们使用以下更新代码:

# using property class
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

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


human = Celsius(37)

print(human.temperature)

print(human.to_fahrenheit())

human.temperature = -300

输出

Setting value...
Getting value...
37
Getting value...
98.60000000000001
Setting value...
Traceback (most recent call last):
  File "", line 31, in 
  File "", line 18, in set_temperature
ValueError: Temperature below -273 is not possible

如我们所见,任何检索temperature值的代码都会自动调用get_temperature()而不是字典(__dict__)查找。同样,任何为temperature分配值的代码都将自动调用set_temperature()

我们甚至可以在上面看到即使创建对象时也会调用set_temperature()

>>> human = Celsius(37)
Setting value...

你能猜出为什么吗?

原因是创建对象时,将调用__init__()方法。该方法的线为self.temperature = temperature 。该表达式自动调用set_temperature()

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

>>> human.temperature
Getting value
37
>>> human.temperature = 37
Setting value

>>> c.to_fahrenheit()
Getting value
98.60000000000001

通过使用property ,我们可以看到在实现值约束时不需要修改。因此,我们的实现是向后兼容的。

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

@property装饰器

在Python, property()是一个内置函数 ,该函数创建并返回property对象。该函数的语法为:

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

哪里,

  1. fget是获取属性值的函数
  2. fset是用于设置属性值的函数
  3. fdel是删除属性的函数
  4. doc是一个字符串 (如注释)

从实现中可以看出,这些函数参数是可选的。因此,可以简单地如下创建属性对象。

>>> property()

属性对象具有三种方法,即getter()setter()deleter()fdel在以后指定fgetfsetfdel 。这意味着,该行:

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 Decorators的程序员可以认识到上述构造可以实现为装饰器。

我们甚至无法定义名称get_temperatureset_temperature因为它们是不必要的,并污染了类名称空间。

为此,我们在定义getter和setter函数时重用了temperature名称。让我们看一下如何将其实现为装饰器:

# Using @property decorator
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


# create an object
human = Celsius(37)

print(human.temperature)

print(human.to_fahrenheit())

coldest_thing = Celsius(-300)

输出

Setting value...
Getting value...
37
Getting value...
98.60000000000001
Setting value...
Traceback (most recent call last):
  File "", line 29, in 
  File "", line 4, in __init__
  File "", line 18, in temperature
ValueError: Temperature below -273 is not possible

上面的实现是简单而有效的。这是推荐使用property