(又一个)列出别名难题

时间:2012-01-25 03:45:12

标签: python

我以为我找到了整个列表别名的东西,但后来我遇到了这个:

    l = [1, 2, 3, 4]
    for i in l:
        i = 0
    print(l)

导致:

    [1, 2, 3, 4]

到目前为止一切顺利。

然而,当我尝试这个时:

    l = [[1, 2], [3, 4], [5, 6]]
    for i in l:
        i[0] = 0

我得到了

    [[0, 2], [0, 4], [0, 5]]

为什么会这样?

这与锯齿的深度有关吗?

5 个答案:

答案 0 :(得分:8)

第一个重新绑定名称。重新绑定名称仅更改本地名称。第二个变异对象。变异对象会在引用它的任何地方改变它(因为它总是相同的对象)。

答案 1 :(得分:6)

i = 0i[0] = 0非常不同。

伊格纳西奥已经简明扼要地解释了原因。因此,我只想用更简单的词语解释这里实际发生了什么。

在第一种情况下,i只是一个指向某个对象的标签(列表中的一个成员)。 i = 0更改对其他对象的引用,以便i现在引用整数0。该列表未经修改,因为您从未要求修改l[0]l的任何元素,您只修改了i

在第二种情况下,i也只是指向列表中某个成员的名称。那部分也不例外。但是,i[0]现在正在其中一个列表成员上调用.__getitem__(0)。同样,i[0] = 'other'就像在做i.__setitem__(0, 'other')一样。它不是简单地将i指向不同的对象,因为常规赋值语句实际上会使对象i发生变异。

一种简单的方法来思考它总是Python中的名称只是对象的标签。作用域或命名空间就像将dict映射到对象的dict。

答案 2 :(得分:1)

分配总是只是在Python中重新绑定名称,这意味着你最终会在同一个地方找到同一个对象的别名。 任何时间为任何类型的对象命名(您为现有对象分配一个新名称,或者您将其传递给一个函数,或者您将其拉出一些容器),你实际改变这个对象所做的任何事情都会影响它最初的位置(即将它传递给你的函数的调用者,或者任何其他人看着你把它拉出来的容器)。

如果需要,您可以使代码明确地复制内容;使用mylist[:]列表切片可能是您最熟悉的方式。许多内置操作都是这样做的;特别是通常是一个内置函数/方法的安全假设,如果它们返回一个对象,它们就没有修改过原始文件(这是一个非常好的规则,可以跟随大部分时间;如果你的函数或者方法通过改变现有对象有效,它应该返回None并让调用者只看他们给你的对象)。事实上,特别是对于列表,有许多方法/函数对做同样的事情;通常有一个函数返回列表的新修改副本和一个只返回列表的方法。例如比较sorted_list = sorted(mylist)mylist.sort()reversed_list = reversed(mylist)mylist.reverse()等。

但是在进行复制的情况下,你需要注意 deep 副本的运行方式;在大多数情况下,它仅限于最外层,因此可以从原始对象中看到变体中包含的任何内容。

新的Python程序员需要尽早处理这个问题,因为它遍及Python中编程的每一个方面。

不幸的是,这个问题被新程序员最自然的注意点所掩盖。首先要弄清楚如何对数字和文本字符串进行基本操作,但这些操作在Python中是 immutable 。这并不意味着它们不像列表那样“以相同的方式”工作,它只是意味着您无法执行任何操作会导致它们发生变化。所以你不需要关心他们是否被分享,因为即使他们是这样也不会有所作为。

其他答案更详细地解释了您的示例中正在发生的事情。但是每当你修改一个列表(或任何其他对象)时,你需要考虑“这个列表来自哪里?”。因为大多数情况下,除非它是刚刚创建的新列表,否则程序的其他部分将能够看到您所做的更改。列表操作几乎总是存在别名。大多数情况下无关紧要,特别是对于数字或文本列表。但是你需要一直注意它,以便你可以决定它是否重要。

它确实很容易成为第二天性,所以坚持不懈,它会变得容易多了。话虽如此,即使非常有经验的Python程序员仍然会遇到偶然的别名错误!

答案 3 :(得分:1)

for i in l:

这意味着“每次循环,i都应该是l的下一个元素的名称。”

i = 0

这意味着“i将不再是其当前名称的名称,并且开始成为整数对象0”的名称。

i[0] = 0

这意味着“i名称将替换为0”的第0个元素。 (你不能说“i[0]将不再是......的名字”,因为它不是一个名字。)

答案 4 :(得分:0)

有时我发现在C语言较不抽象的世界中思考更容易。在Python中,将每个变量都视为指针。当你执行i = 0; i = 1时,你不会这样做:

int * i = malloc(sizeof(int));
*i = 0;
*i = 1;

更像这样:

int * i = malloc(sizeof(int));
*i = 0;
free(i);
i = malloc(sizeof(int));
*i = 1;

您没有更改该值,而是将指针移动到新值。

但是使用列表,l指针保持不变,但是您正在更新指定索引处的值。因此l = [0]; l[0] = 1看起来像:

int * l[] = {0};
l[0] = 1;

(注意:我意识到Python内联和列表不是真正的C int和数组,但为了这个目的,它们是相似的。)

Protip:“alias”不是Python术语,所以请避免使用它。 “参考”或只是“变量”更好。