# Python `@property`装饰器

> 原文： [https://www.programiz.com/python-programming/property](https://www.programiz.com/python-programming/property)

#### 在本教程中，您将学习 Python `@property`装饰器。 在面向对象程序设计中使用获取器和设置器的 pythonic 方式。

Python 编程为我们提供了一个内置的`@property`装饰器，使面向对象编程中的获取器和设置器的使用变得更加容易。

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

* * *

## 没有获取器和设置器的类

让我们假设我们决定制作一个[类](/python-programming/class)，该类存储以摄氏度为单位的温度。 它还将实现一种将温度转换为华氏度的方法。 一种方法如下：

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

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

我们可以根据需要创建对象，并根据需要操纵`temperature`属性：

```py
# 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())
```

**输出**

```py
37
98.60000000000001
```

转换为华氏温度时，多余的小数位是由于浮点运算错误。 要了解更多信息，请访问 [Python 浮点算术错误](/python-programming/numbers#dec)。

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

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

因此，`man.temperature`在内部变为`man.__dict__['temperature']`。

* * *

## 使用获取器和设置器

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

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

上述限制的一个明显解决方案是隐藏属性`temperature`（将其设为私有），并定义新的获取器和设置器方法来对其进行操作。 可以按以下步骤完成：

```py
# 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 中的私有变量。

* * *

现在，让我们使用以下实现：

```py
# 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())
```

**输出**：

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

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

**注意**：私有变量实际上在 Python 中不存在。 只需遵循一些规范。 语言本身没有任何限制。

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

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

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

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

* * *

## 属性类

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

```py
# 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`。 简而言之，属性将一些代码（`get_temperature`和`set_temperature`）附加到成员属性访问（`temperature`）。

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

```py
# 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
```

**输出**：

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

如我们所见，任何检索`temperature`值的代码都会自动调用`get_temperature()`，而不是字典（__dict__）查找。 类似地，任何为`temperature`赋值的代码都会自动调用`set_temperature()`。

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

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

**你能猜出为什么吗？**

原因是在创建对象时，将调用`__init__()`方法。 该方法具有行`self.temperature = temperature`。 该表达式自动调用`set_temperature()`。

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

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

>>> c.to_fahrenheit()
Getting value
98.60000000000001
```

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

**注意**：实际温度值存储在私有`_temperature`变量中。`temperature`属性是为该私有变量提供接口的属性对象。

* * *

## `@property`装饰器

在 Python 中，`property()`是一个内置函数，可创建并返回`property`对象。 该函数的语法为：

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

哪里，

*   `fget`是获取属性值的函数
*   `fset`是设置属性值的函数
*   `fdel`是删除属性的函数
*   `doc`是一个字符串（如注释）

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

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

属性对象具有`getter()`，`setter()`和`deleter()`三种方法，以便在以后指定`fget`，`fset`和`fdel`。 这意味着，该行：

```py
temperature = property(get_temperature,set_temperature)
```

可以细分为：

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

这两段代码是等效的。

熟悉 [Python 装饰器](/python-programming/decorator)的程序员可以认识到上述构造可以实现为装饰器。

我们甚至无法定义名称`get_temperature`和`set_temperature`，因为它们是不必要的，并污染了类名称空间。

为此，我们在定义获取器和设置器函数的同时重用`temperature`名称。 让我们看一下如何将其实现为装饰器：

```py
# 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)
```

**输出**：

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

上面的实现是简单而有效的。 建议使用`property`的方式。