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

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

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

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


没有获取器和设置器的类

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

  1. class Celsius:
  2. def __init__(self, temperature = 0):
  3. self.temperature = temperature
  4. def to_fahrenheit(self):
  5. return (self.temperature * 1.8) + 32

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

  1. # Basic method of setting and getting attributes in Python
  2. class Celsius:
  3. def __init__(self, temperature=0):
  4. self.temperature = temperature
  5. def to_fahrenheit(self):
  6. return (self.temperature * 1.8) + 32
  7. # Create a new object
  8. human = Celsius()
  9. # Set the temperature
  10. human.temperature = 37
  11. # Get the temperature attribute
  12. print(human.temperature)
  13. # Get the to_fahrenheit method
  14. print(human.to_fahrenheit())

输出

  1. 37
  2. 98.60000000000001

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

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

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

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


使用获取器和设置器

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

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

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

  1. # Making Getters and Setter methods
  2. class Celsius:
  3. def __init__(self, temperature=0):
  4. self.set_temperature(temperature)
  5. def to_fahrenheit(self):
  6. return (self.get_temperature() * 1.8) + 32
  7. # getter method
  8. def get_temperature(self):
  9. return self._temperature
  10. # setter method
  11. def set_temperature(self, value):
  12. if value < -273.15:
  13. raise ValueError("Temperature below -273.15 is not possible.")
  14. self._temperature = value

如我们所见,以上方法引入了两个新的get_temperature()set_temperature()方法。

此外,temperature被替换为_temperature。 开头的下划线_用于表示 Python 中的私有变量。


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

  1. # Making Getters and Setter methods
  2. class Celsius:
  3. def __init__(self, temperature=0):
  4. self.set_temperature(temperature)
  5. def to_fahrenheit(self):
  6. return (self.get_temperature() * 1.8) + 32
  7. # getter method
  8. def get_temperature(self):
  9. return self._temperature
  10. # setter method
  11. def set_temperature(self, value):
  12. if value < -273.15:
  13. raise ValueError("Temperature below -273.15 is not possible.")
  14. self._temperature = value
  15. # Create a new object, set_temperature() internally called by __init__
  16. human = Celsius(37)
  17. # Get the temperature attribute via a getter
  18. print(human.get_temperature())
  19. # Get the to_fahrenheit method, get_temperature() called by the method itself
  20. print(human.to_fahrenheit())
  21. # new constraint implementation
  22. human.set_temperature(-300)
  23. # Get the to_fahreheit method
  24. print(human.to_fahrenheit())

输出

  1. 37
  2. 98.60000000000001
  3. Traceback (most recent call last):
  4. File "<string>", line 30, in <module>
  5. File "<string>", line 16, in set_temperature
  6. ValueError: Temperature below -273.15 is not possible.

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

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

  1. >>> human._temperature = -300
  2. >>> human.get_temperature()
  3. -300

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

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

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


属性类

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

  1. # using property class
  2. class Celsius:
  3. def __init__(self, temperature=0):
  4. self.temperature = temperature
  5. def to_fahrenheit(self):
  6. return (self.temperature * 1.8) + 32
  7. # getter
  8. def get_temperature(self):
  9. print("Getting value...")
  10. return self._temperature
  11. # setter
  12. def set_temperature(self, value):
  13. print("Setting value...")
  14. if value < -273.15:
  15. raise ValueError("Temperature below -273.15 is not possible")
  16. self._temperature = value
  17. # creating a property object
  18. temperature = property(get_temperature, set_temperature)

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

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

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

  1. # using property class
  2. class Celsius:
  3. def __init__(self, temperature=0):
  4. self.temperature = temperature
  5. def to_fahrenheit(self):
  6. return (self.temperature * 1.8) + 32
  7. # getter
  8. def get_temperature(self):
  9. print("Getting value...")
  10. return self._temperature
  11. # setter
  12. def set_temperature(self, value):
  13. print("Setting value...")
  14. if value < -273.15:
  15. raise ValueError("Temperature below -273.15 is not possible")
  16. self._temperature = value
  17. # creating a property object
  18. temperature = property(get_temperature, set_temperature)
  19. human = Celsius(37)
  20. print(human.temperature)
  21. print(human.to_fahrenheit())
  22. human.temperature = -300

输出

  1. Setting value...
  2. Getting value...
  3. 37
  4. Getting value...
  5. 98.60000000000001
  6. Setting value...
  7. Traceback (most recent call last):
  8. File "<string>", line 31, in <module>
  9. File "<string>", line 18, in set_temperature
  10. ValueError: Temperature below -273 is not possible

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

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

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

你能猜出为什么吗?

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

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

  1. >>> human.temperature
  2. Getting value
  3. 37
  4. >>> human.temperature = 37
  5. Setting value
  6. >>> c.to_fahrenheit()
  7. Getting value
  8. 98.60000000000001

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

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


@property装饰器

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

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

哪里,

  • fget是获取属性值的函数
  • fset是设置属性值的函数
  • fdel是删除属性的函数
  • doc是一个字符串(如注释)

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

  1. >>> property()
  2. <property object at 0x0000000003239B38>

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

  1. temperature = property(get_temperature,set_temperature)

可以细分为:

  1. # make empty property
  2. temperature = property()
  3. # assign fget
  4. temperature = temperature.getter(get_temperature)
  5. # assign fset
  6. temperature = temperature.setter(set_temperature)

这两段代码是等效的。

熟悉 Python 装饰器的程序员可以认识到上述构造可以实现为装饰器。

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

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

  1. # Using @property decorator
  2. class Celsius:
  3. def __init__(self, temperature=0):
  4. self.temperature = temperature
  5. def to_fahrenheit(self):
  6. return (self.temperature * 1.8) + 32
  7. @property
  8. def temperature(self):
  9. print("Getting value...")
  10. return self._temperature
  11. @temperature.setter
  12. def temperature(self, value):
  13. print("Setting value...")
  14. if value < -273.15:
  15. raise ValueError("Temperature below -273 is not possible")
  16. self._temperature = value
  17. # create an object
  18. human = Celsius(37)
  19. print(human.temperature)
  20. print(human.to_fahrenheit())
  21. coldest_thing = Celsius(-300)

输出

  1. Setting value...
  2. Getting value...
  3. 37
  4. Getting value...
  5. 98.60000000000001
  6. Setting value...
  7. Traceback (most recent call last):
  8. File "<string>", line 29, in <module>
  9. File "<string>", line 4, in __init__
  10. File "<string>", line 18, in temperature
  11. ValueError: Temperature below -273 is not possible

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