如何定义可变组?

How to define groups of mutables?

提问人:Yerbol Sapar 提问时间:6/30/2022 最后编辑:Yerbol Sapar 更新时间:7/1/2022 访问量:68

问:

列表是一种可变数据类型。如果我的 2d 列表中的子列表对同一对象有多个引用,那么更改任何一个子列表都会更改其他对象,这一事实表明了一个问题:我是否能够提取有关链接子列表的信息?

为了解释,让我们考虑一个二维列表:

import random
lst = [[0] for i in range(9)]  # create list of 9 sublists with value inside
lst
[[0], [0], [0], [0], [0], [0], [0], [0], [0], [0]] .

每个子列表都指向同一个对象,但又是独立的。如果任何给定子列表的内容发生更改,则其他子列表的内容将保持稳定。所有 ID 都不同的事实证明了这一点:0

[id(i) for i in lst]
[139963231930640, 139963235043920, 139963236442592, 139963222312992, 139963783242688, 139963234936784, 139963233636256, 139963233634176, 139963233635056] .

现在,让我们创建一个对象的随机样本:

indcs = random.sample(range(9), 9)    

,然后使用以下函数修改子列表:

def mutables(a):
    for i in range(len(a) - 1):
        lst[a[i+1]] = lst[a[i]]
        lst[a[i]][0] += 1

mutables(indcs[:3])
mutables(indcs[3:6])
mutables(indcs[6:])

lst
[[2], [2], [2], [2], [2], [2], [2], [2], [2]] .

该列表现在包含 3 组,每组 3 个子列表,组的每个元素都指向同一个对象。只有 3IDsx3 在:lst

[id(i) for i in lst]
[139963231930640, 139963235043920, 139963236442592, 139963235043920, 139963236442592, 139963231930640, 139963235043920, 139963231930640, 139963236442592] .

例如,如果我们更改列表的第三个元素,它会更改组的每个元素:

lst[2][0] += 1
lst
[[2], [2], [3], [2], [3], [2], [2], [2], [3]] .

在这里,子列表 (2,4,8) 已更改。第 2(0,5,7) 和第 3(1,3,6) 组的行为方式相同:

lst[0][0] += 1
lst
[[3], [2], [3], [2], [3], [3], [2], [3], [3]]
lst[1][0] += 1
lst
[[3], [3], [3], [3], [3], [3], [3], [3], [3]] .

子列表中整数值的更改不会更改 ID:

[id(i) for i in lst]
    [139963231930640, 139963235043920, 139963236442592, 139963235043920, 139963236442592, 139963231930640, 139963235043920, 139963231930640, 139963236442592],

但是,每次有新的组成员到达时添加,就可以提供有关组大小的有用信息。1

我的问题的关键是,在保留子列表之间链接的信息的同时,如何提取每个组的索引((2,4,8),(0,5,7),(1,3,6))而不需要参考以及它最初是如何分组的:?我可以更改第一个子列表值,跟踪其他子列表的更改并将它们弹出到循环中,但此信息应该是隐式可用的,在此基础上组中的元素会发生变化。有什么方法可以收集它吗?例如,我可以获取一个子列表并尝试通过此处或此处描述的方法之一查找链接的子列表,但这些选项返回值,而不是它们的名称。indcs[:3:6:]id(name)

列表 找到 可变

评论

0赞 Nathaniel Ford 6/30/2022
在你在这里的思路中有点不清楚,但你似乎想知道哪些列表项指向同一个对象?“受突变束缚”是一个奇怪的术语,更不用说“相互受突变束缚”了。目前还不清楚您希望如何表示索引 - 似乎更难跟踪列表指向的哪些基础对象是相同的。了解您为什么要这样做可能会有所帮助?
0赞 Barmar 6/30/2022
每个切片都是一个新列表。唯一的共享数据是您在第一行中创建的列表。请参阅 stackoverflow.com/questions/240178/...[0]
0赞 Grismar 6/30/2022
在我看来,您正在尝试创建与允许您通过使用一系列布尔值索引数据帧或允许使用掩码来执行的行为非常相似的行为。为什么要为一个在非常常用的软件包中已经非常有效地解决的问题创建一个复杂且难以理解(更不用说相对缓慢)的解决方案呢?pandas.DataFramenumpy
0赞 Yerbol Sapar 6/30/2022
@Grismar 你是对的,似乎带有掩码的麻木是适用的,但问题是我的列表是多维的,元素长度不同。
1赞 Grismar 7/1/2022
似乎你问一个问题的目的是让事情不仅更简单,而且更快,而任何有效的解决方案实际上都会产生更多的开销,虽然更短,但从用户的角度来看不一定更简单。您似乎正在寻找可重复使用的掩码或结构化复合指数 - 但构建这些掩码会增加复杂性,并且由于额外的间接性,使用它们可能会导致开销。如果您仍然看到一个案例,那么只创建一个基于列表的类,该类在索引上具有您需要的行为,这不是更清楚吗?

答:

1赞 Nathaniel Ford 7/1/2022 #1

我认为您对正在处理的列表的可变性在哪里感到困惑。

>>> class Obj:
...   name: str = None
...   def __init__(self, name):
...     self.name = name
...
>>> o = Obj("Athena")
>>> o.name
'Athena'
>>> a = Obj("Athena")
>>> b = Obj("Bacchus")
>>> c = Obj("Clotho")
>>>
>>> la = [a, b]
>>> lb = [a, b]
>>> ls = [la, lb]
>>> for sublist in ls:
...   for item in sublist:
...     print(f"{item.name}")
...
Athena
Bacchus
Athena
Bacchus
>>> ls[0][0] = c
>>> for sublist in ls:
...   for item in sublist:
...     print(f"{item.name}")
...
Clotho
Bacchus
Athena
Bacchus

这表明,如果单独创建子列表,即使作为该列表元素的对象相同,子列表也是单独的可变对象,更改一个子列表并不意味着更改其他对象。

相反,请考虑:

>>> la = [a, b]
>>> ls = [la, la]
>>> for sublist in ls:
...   for item in sublist:
...     print(f"{item.name}")
...
Athena
Bacchus
Athena
Bacchus
>>> ls[0][0] = c
>>> for sublist in ls:
...   for item in sublist:
...     print(f"{item.name}")
...
Clotho
Bacchus
Clotho
Bacchus

在这里我们看到,如果我们使用相同的子列表,当我们更改一个子列表时,我们会更改另一个子列表。更进一步:la

>>> lb = [a, c]
>>> ls[1] = lb
>>> for sublist in ls:
...   for item in sublist:
...     print(f"{item.name}")
...
Athena
Bacchus
Athena
Clotho
>>> ls[0][0] = d
>>> for sublist in ls:
...   for item in sublist:
...     print(f"{item.name}")
...
Demosthenes
Bacchus
Athena
Clotho

在这里,我们已将外部列表的第二个元素指向一个新列表。现在,当我们更改第一个子列表的元素时,它不会更改第二个子列表。这是有道理的,因为这些是不同的列表。

这里的结果是列表的每个元素都指向一个对象。多个元素可以指向同一个对象。因此,对于外部列表,您可以让多个元素指向同一个对象(一个子列表),对于任何此类子列表,对该子列表的更改都将在指向同一子列表对象的外部列表的任何其他元素中可见。但前提是情况如此。

这就是你在问题中的推理不太有意义的地方:

>>> lb = [a, b]
>>> ls = [la, lb, la]
>>> for sublistA in ls:
...   for sublistB in ls:
...     print("Equal") if sublistA is sublistB else print("Not equal")
...
Equal
Not equal
Equal
Not equal
Equal
Not equal
Equal
Not equal
Equal

在这里我们看到,虽然 和 是等价的,但它们不是同一个对象。实际上,在复杂的示例中,您所做的只是将子列表对象添加到外部列表的位置。类似于:lalb

>>> ls = [la, lb, lb, la]
>>> ls[0][0] = c
>>> for sublist in ls:
...   for item in sublist:
...      print(f"{item.name}")
...
Clotho
Bacchus
Athena
Bacchus
Athena
Bacchus
Clotho
Bacchus

这与你把它们放进去的“组”无关,只是外部列表中的每个元素都引用了一个特定的列表对象。这就是“隐式跟踪”发生的地方,你可以像我一样通过比较来获得这些信息。因为列表是不可散列的,所以如果不为列表创建包装器,就很难进行反向查找字典,而且每次都进行对象比较的成本很高(而不是比较两个对象之间的区别,尤其是列表!除了这两个选项(保留一个单独的变量来跟踪每个唯一对象的索引(在本例中,每个子列表本身具有多个元素)之外,您能做的并不多,可以避免您试图避免的工作。is==

评论

0赞 Yerbol Sapar 7/1/2022
感谢您的回答,它有助于理解 Python 可变性是什么。您说得对,“引用特定列表对象时会发生隐式跟踪”。关于组的目的,该问题意味着通过为特定子列表保留相同的 ID 来利用 Python 可变性来控制对象组的可能性,即应该将组分配给相同的 ID。为了更清楚地说明这一点,我已将 ID 的角色添加到一个问题中。mutables()