7

我有两个包含列表的属性。每当此列表中的任何项目发生更改时,我都希望另一个列表自行更新。这包括声明obj.myProp[3]=5。现在,该语句调用 getter 函数来获取整个列表,从列表中获取第三个项目,并将其设置为 5。myProp列表已更改,但第二个列表永远不会更新。

class Grid(object):

    def __init__(self,width=0,height=0):

        # Make self._rows a multi dimensional array
        # with it's size width * height
        self._rows=[[None] * height for i in xrange(width)]
        # Make `self._columns` a multi dimensional array
        # with it's size height * width
        self._columns=[[None] * width for i in xrange(height)]

    @property
    def rows(self):
        # Getting the rows of the array
        return self._rows

    @rows.setter
    def rows(self, value):
        # When the rows are changed, the columns are updated
        self._rows=value
        self._columns=self._flip(value)

    @property
    def columns(self):
        # Getting the columns of the array
        return self._columns

    @columns.setter
    def columns(self, value):
        # When the columns are changed, the rows are updated
        self._columns = value
        self._rows = self._flip(value)

    @staticmethod
    def _flip(args):
        # This flips the array
        ans=[[None] * len(args) for i in xrange(len(args[0]))]
        for x in range(len(args)):
            for y in range(len(args[0])):
                ans[y][x] = args[x][y]
        return ans

示例运行:

>>> foo=grid(3,2)
>>> foo.rows
[[None, None], [None, None], [None, None]]
>>> foo.columns
[[None, None, None], [None, None, None]]
>>> foo.rows=[[1,2,3],[10,20,30]]
>>> foo.rows
[[1, 2, 3], [10, 20, 30]]
>>> foo.columns
[[1, 10], [2, 20], [3, 30]]
>>> foo.rows[0][0]=3
>>> foo.rows
[[3, 2, 3], [10, 20, 30]]
>>> foo.columns
[[1, 10], [2, 20], [3, 30]]

如果您查看最后三行,这就是实际问题发生的地方。我将子列表的第一项设置为 3,但从foo.columns不更新自身以将 3 放入其列表中。

简而言之,我如何制作一个始终更新另一个变量的变量,即使它的子项正在更改?

我正在使用 Python 2.7

4

3 回答 3

7

您的问题是您没有设置 foo.rows违规行 - 您正在获取它,然后修改其中一个成员。这不会解雇二传手。使用您提议的 API,您需要返回一个附有 getter 和 setter 的列表。

您最好不要使用 rows 和 columns 属性来设置条目,并添加__getitem__如下方法:

class Grid(object):

    def __init__(self, width=0, height=0):
        self._data = [None] * width * height;
        self.width = width
        self.height = height

    def __getitem__(self, pos):
        if type(pos) != tuple or len(pos) != 2:
            raise IndexError('Index must be a tuple of length 2')
        x, y = pos
        if 0 <= x < self.width and 0 <= y < self.height:
            return self._data[x + self.width * y]
        else:
            raise IndexError('Grid index out of range')

    def __setitem__(self, pos, value):
        if type(pos) != tuple or len(pos) != 2:
            raise IndexError('Index must be a tuple of length 2')
        x, y = pos
        if 0 <= x < self.width and 0 <= y < self.height:
            self._data[x + self.width * y] = value
        else:
            raise IndexError('Grid index out of range')

    @property
    def columns(self):
        return [
            [self[x, y] for x in xrange(self.width)]
            for y in xrange(self.height)
        ]

    @property
    def rows(self):
        return [
            [self[x, y] for y in xrange(self.height)]
            for x in xrange(self.width)
        ]

虚线则变为:

foo[0, 0] = 3
于 2012-05-19T16:17:37.903 回答
3

如果您只是返回您的rowsorcolumns列表,那么您将永远无法控制更改项目时会发生什么。

一种可能性是不提供属性来直接获取/设置列表,而是为位置 x/y 处的项目提供设置器/获取器。

一个不错的版本是让__setitem__/__getitem__并让他们接受一个元组,这样你就可以使用foo[x,y]and访问元素foo[x,y] = bar

另一种方法是在列表周围返回一个包装器,以检测元素何时更改,但是您还必须为每个嵌套列表执行此操作。

于 2012-05-19T16:01:39.937 回答
-1

我建议您考虑使用numpy。特别是,transpose方法提供了一个带有转置轴的基础矩阵的“视图”,这意味着如果你改变一个,那么另一个就会改变。

如果这不符合您的需求,那么我可以看到使您想要工作的 API 的唯一方法是定义“行”和“列”方法以返回实际上不存储数据的自定义“视图对象” ,但这又指向网格对象本身中的一些私有(共享)数据。这些可能是类似列表的,但不支持某些操作(例如,追加或清除)。

于 2012-05-19T16:56:06.037 回答