转载
如何自定义当你在Python类上赋值给一个特定的属性时会发生什么?
访问和更新一个类上的属性
这里我们有一个叫做Person 的类。
class Person: def __init__(self, name, location): self.name = name self.location = location
Person 对象有一个name 属性和一个location 属性。
>>> trey = Person("Trey", "San Diego") >>> trey.name 'Trey' >>> trey.location 'San Diego'
我们想让它成为这样的:每当我们给location 属性赋值时,所有以前的location 属性的值都会被_储存在_某个地方。
>>> trey.location = "Portland"
我们想做一个past_locations 属性,它将显示我们在一个特定的Person 对象上的所有先前值location 。
>>> trey.past_locations
获取器和设置器方法(不推荐
在许多编程语言中,比如Java,这个问题的解决方案是getter方法和setter方法。
所以我们没有一个location 属性,而是一个私有属性。Python 没有私有属性,但有时我们会在属性名称前加一个下划线 (_),以说明它按惯例是私有的。
这里是我们的Person 类的一个更新版本,它有一个 getter 和 setter 方法,以及一个私有的 (按照惯例)_location 属性。
class Person: def __init__(self, name, location): self.name = name self.past_locations = [] self.set_location(location) def get_location(self): return self._location def set_location(self, location): self._location = location self.past_locations.append(location)
在这个新的类中,我们将调用get_location 方法,而不是直接访问location (这已经不起作用了)。
>>> trey = Person("Trey", "San Diego") >>> trey.get_location() 'San Diego'
而要设置一个位置,我们会调用set_location 方法。
>>> trey.set_location("Portland") >>> trey.get_location() 'Portland'
getter和setter方法的好处是,我们可以钩住这些方法,把任何我们想要的代码放在里面。
例如,在我们的set_location 方法中,我们要追加到past_locations 列表中。
`def set_location(self, location): self._location = location self.past_locations.append(location)`
因此,past_locations 属性现在显示了我们曾经为这个Person 对象设置的所有位置。
>>> trey.past_locations ['San Diego', 'Portland']
使用属性来钩住一个属性的赋值
在 Python 中,我们不倾向于使用_getter_和_setter_方法。 我们没有一个get_name ,一个set_name ,一个get_location ,一个set_location 来表示每一个属性。相反,我们倾向于只分配属性,并按我们的意愿从属性中读取。
但是在这种特殊的情况下 (我们有一个属性,并且我们想改变它的工作方式) 我们有点被卡住了。 我们需要一些方法来钩住这个属性的赋值。 幸运的是,在 Python 中,有一种方法可以做到这一点:我们可以使用一个属性。
这里是我们修改过的带有属性的Person 类。
class Person: def __init__(self, name, location): self.name = name self.past_locations = [] self.location = location @property def location(self): return self._location @location.setter def location(self, location): self._location = location self.past_locations.append(location)
属性使我们能够钩住一个属性的获取。
>>> trey = Person("Trey", "San Diego") >>> trey.location 'San Diego'
当我们现在访问location 属性时,在引擎盖下它_实际上_是在访问_location 属性。
但是属性也允许我们自定义当我们分配给一个特定的属性时会发生什么。
默认情况下,如果我们向一个属性赋值,我们会得到一个错误。 但是在我们的例子中,我们没有得到一个错误。
>>> trey.location = "Portland" >>> trey.location 'Portland'
这是因为我们为我们的属性实现了一个_setter_。
`@location.setter def location(self, location): self._location = location self.past_locations.append(location)`
正如你所看到的,属性设置器的语法有点奇怪,所以与其记住这个语法,不如在需要的时候直接查找。
属性设置器的语法有点奇怪,所以我不建议记住它(当你需要的时候就去找它)。
分解属性设置器的语法
属性设置器的语法以我们的属性名称(location )开始,后面是.setter 。我们用它作为一个装饰器来装饰一个location 方法(与我们的属性命名相同)。
`@location.setter def location(self, location): ...`
我们接受一个参数,这个参数代表分配给这个属性的任何东西(在分配过程中等号的右边)。
然后我们在_location ,每当我们的location 发生变化时,我们将每个值追加到我们的past_location 列表中。
`self._location = location self.past_locations.append(location)`
我们从不直接赋值给我们的私有属性
所以当我们访问past_locations 列表时,我们的两个位置都被反映出来。
>>> trey.past_locations ['San Diego', 'Portland']
任何时候location 的变化,past_locations 都会被更新。
请注意,即使在我们的初始化器中,我们也是赋值给location (而不是_location )。
`def __init__(self, name, location): self.name = name self.past_locations = [] self.location = location`
这就是 "圣地亚哥 "是这个past_locations list 中第一个值的原因:当我们在初始化器中赋值给location 属性时,它调用了我们的属性设置器。
事实上,每次分配到location ,这个属性的setter都会被调用。
总结
如果你想自定义当你在Python中分配给你的类的一个特定属性时发生的事情,你可以使用一个带有setter的属性。
