没有 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 tofahrenheit(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

假设我们想扩展 摄氏上面定义的类。我们知道任何物体的温度都不能低于-273.15摄氏度(热力学中的绝对零)
让我们更新我们的代码来实现这个值约束。
上述限制的一个明显解决方案是隐藏属性temperature(使其成为私有)并定义新的 getter 和 setter 方法来操作它。这可以按如下方式完成:
# Making Getters and Setter methods class Celsius: def init(self, temperature=0): self.settemperature(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.settemperature(temperature) def tofahrenheit(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 回溯(最近一次调用最后一次): 文件“”,第 30 行,在 中 文件“”,第 16 行,在 set_temperature 中 ValueError:温度低于 -273.15 是不可能的。
此更新成功实施了新限制。我们不再允许将温度设置为低于 -273.15 摄氏度。
注意:私有变量实际上并不存在于 Python 中。有一些简单的规范需要遵守。语言本身不适用任何限制。
>>> human._temperature = -300 >>> human.get_temperature() -300
然而,上述更新的更大问题是所有实现我们之前类的程序都必须修改它们的代码,从obj.temperaturetoobj.get_temperature()和所有像obj.temperature = valto 的表达式obj.set_temperature(val)。
在处理数十万行代码时,这种重构可能会导致问题。
总而言之,我们的新更新不向后兼容。这就是@property救援的地方。


属性类

处理上述问题的 Pythonic 方法是使用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)
我们print()在里面添加了一个函数get_temperature()并set_temperature()清楚地观察到它们正在被执行。
代码的最后一行创建了一个属性对象temperature。简单地说,property 将一些代码(get_temperature和set_temperature)附加到成员属性访问(temperature)。
让我们使用这个更新代码:

using property class class Celsius: def init(self, temperature=0): self.temperature = temperature def tofahrenheit(self): return (self.temperature 1.8) + 32 # getter def gettemperature(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
输出
设置值… 获取价值… 37 获取价值… 98.60000000000001 设置值… 回溯(最近一次调用最后一次): 中的文件“”,第 31 行 文件“”,第 18 行,在 set_temperature 中 ValueError:温度低于 -273 是不可能的
正如我们所见,任何检索 值的代码都temperature将自动调用get_temperature()而不是字典 (__dict
) 查找。类似地,任何为其赋值的代码temperature都会自动调用set_temperature().
我们甚至可以在上面看到set_temperature()即使我们创建了一个对象也被调用了。
>>> human = Celsius(37) Setting value…
*你能猜到为什么吗?

原因是当一个对象被创建时,该_init
()方法被调用。这个方法有一行self.temperature = temperature。此表达式自动调用set_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)
在哪里,

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

从实现中可以看出,这些函数参数是可选的。因此,可以简单地创建一个属性对象,如下所示。
>>> property()
一个属性对象有getter()、setter()、 和三个方法deleter()来指定fget,fset然后再指定fdel。这意味着,该行:
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 装饰器的程序员可以认识到,上述构造可以实现为装饰器。
我们甚至不能定义名称get_temperature,set_temperature因为它们是不必要的并且会污染类名称空间。
为此,我们temperature在定义 getter 和 setter 函数时重用名称。让我们看看如何将其实现为装饰器:

Using @property decorator class Celsius: def init(self, temperature=0): self.temperature = temperature def tofahrenheit(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)
*输出

设置值… 获取价值… 37 获取价值… 98.60000000000001 设置值… 回溯(最近一次调用最后一次): 中的文件“”,第 29 行 文件“”,第 4 行,在 _init
中 文件“”,第 18 行,温度 ValueError:温度低于 -273 是不可能的
以上实现简单高效。这是推荐的使用方式property。