【问题标题】:Why is the set-method bypassed when property is modified?为什么修改属性时会绕过设置方法?
【发布时间】:2023-03-22 07:59:01
【问题描述】:

我对 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 方法(没有打印 set 消息,但属性被修改):

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

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

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

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

非常感谢!

【问题讨论】:

  • Matlab terms中,所有Python变量都是句柄变量,所有Python对象都是句柄对象。

标签: python get set python-decorators


【解决方案1】:

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 继承以有条不紊的方式完成,它根据您对__delitem____setitem____getitem____len__ 和@ 的实现提供了可用于list 的常用方法987654334@.

结果将是一个行为与list 完全相同的对象,但带有一些钩子来检查任何突变是否有效。

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]

【讨论】:

  • 好的,感谢您的快速答复!是的,这正是我所认识到的。太糟糕了,如果我通过索引更改属性,例如在 Matlab 中,则流程不是 get-->mod-->set 流程。所以可能你展示的方式适合我......我会考虑的。谢谢!
  • @ChrisT 没问题。在 StackOverflow 上,不要犹豫,通过单击左侧的绿色复选标记来接受允许解决您的问题的答案。这有助于其他用户找到有用的答案,并为提问者和回答者提供一些良好的声誉。
猜你喜欢
  • 2013-12-31
  • 2012-08-26
  • 2022-01-01
  • 2022-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-14
  • 2020-06-28
相关资源
最近更新 更多