为什么在修改属性时会绕过set-method?

时间:2018-06-13 16:14:28

标签: python get set python-decorators

我并不是Python的新手,但有时我仍然遇到Python的逻辑或思考方式的问题。我在testclass.py文件中有以下名为TestClass的类,它有一个属性x和相应的get和set方法。

class TestClass(object):

    @property
    def x(self):
        print('get x')
        return self.__x

    @x.setter
    def x(self, x):
        print('set x')
        self.__x = x

如果我运行一个简单的例子,一切都按预期工作。因此调用get和set方法并打印其确认消息:

>>> from testclass import TestClass
>>> newObject = TestClass()
>>> newObject
<testclass.TestClass object at 0x0298B9D0>
>>> newObject.x = [1, 2, 3, 4]
set x
>>> newObject.x
get x
[1, 2, 3, 4]

我的问题是,如果我只想通过索引来修改属性,则调用get方法(我期望)从对象获取属性,但是绕过set方法(没有打印设置消息,但财产被修改了):

>>> newObject.x[1] = 99
get x
>>> newObject.x
get x
[1, 99, 3, 4]

对我来说,这种行为不太符合逻辑。我来自Matlab(这不是OOP最优雅的语言)。 Matlab中的相同结构将导致以下过程:

  • 获取x的get方法已被调用以获取x
  • 使用新值
  • 替换特定索引处的值
  • 使用新x
  • 调用set方法覆盖旧版本的x

这只是一个小例子。在我的代码中,我需要在每次修改属性时进入set方法。有没有pythonic方式吗?

非常感谢!

1 个答案:

答案 0 :(得分:0)

property.getter控制绑定到实例时属性返回的内容。一旦返回该值,getter就无法控制您使用它做什么。特别是,如果对象是可变的,那么您可以在不经过property.setter的情况下更新它。

返回数据副本

解决这个问题的一个简单方法是让您的属性返回内部数据的视图,例如返回数据的副本而不是数据本身。

这将强制用户get值,更新后再set

import copy

class TestClass(object):
    @property
    def x(self):
        print('get x')
        return copy.deepcopy(self.__x)

    @x.setter
    def x(self, x):
        print('set x')
        self.__x = copy.deepcopy(x)

实施例

newObject = TestClass()
newObject.x = [1, 2, 3, 4]
newObject.x[1] = 99
print(newObject.x)

输出

set x
get x
get x
[1, 2, 3, 4]

使用控制器类

尽管每次复制数据的成本很高,但另一种解决方案是将其包装在控制器类中。

这可以通过继承collections.MutableSequence以有条不紊的方式完成,list根据您对__delitem____setitem__,{__getitem__的实施提供__len__可用的常用方法{1}},insertlist

结果将是一个行为与from collections import MutableSequence class ListController(MutableSequence): def __init__(self, data): self._data = data def __setitem__(self, key, value): print('index {} is being set to {}'.format(key, value)) self._data[key] = value def __delitem__(self, key): print('index {} is being deleted'.format(key)) del self._data[key] def __getitem__(self, item): return self._data[item] def __len__(self): return len(self._data) def __repr__(self): return repr(self._data) def insert(self, index, value): print('item {} is being inserted at index {}'.format(value, index)) self._data.insert(index, value) class TestClass(object): def __init__(self): self.__x = [1, 2, 3, 4] @property def x(self): print('get x') return ListController(self.__x) @x.setter def x(self, x): print('set x') self.__x = list(x) 完全相同的对象,但有一些钩子可以检查任何突变是否有效。

newObject = TestClass()
newObject.x[1] = 99
print(newObject.x)

实施例

get x
index 1 is being set to 99
get x
[1, 99, 3, 4]

输出

{{1}}