为什么Python在这个函数中修改源代码?

时间:2014-09-25 00:17:22

标签: python

如果我使用列表作为函数的参数,我希望原始列表不会被修改。为什么当我使用下面的代码时,当{em> x == z为真时,x == range(10)为True?

In [83]: def pop_three(collection):
   ....:     new_collection = []
   ....:     new_collection.append(collection.pop())
   ....:     new_collection.append(collection.pop())
   ....:     new_collection.append(collection.pop())
   ....:     return new_collection, collection
   ....:

In [84]: x = range(10)

In [85]: x
Out[85]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [86]: y, z = pop_three(x)

In [87]: y
Out[87]: [9, 8, 7]

In [88]: z
Out[88]: [0, 1, 2, 3, 4, 5, 6]

In [89]: x
Out[89]: [0, 1, 2, 3, 4, 5, 6]

4 个答案:

答案 0 :(得分:3)

  

如果我使用列表作为函数的参数,我希望原始列表不会被修改。

没有。我认为你在这里缺少的是Python永远不会自动复制任何东西

如果您习惯使用像C ++这样的语言,那么Python就非常不同了。 Ned Batchelder一如既往地解释了这一点,但让我总结一下:

  • 在C ++中,函数参数或变量或数组元素是内存中具有类型的位置。当您编写a = b或调用f(b)时,会将内存位置b中的值复制到内存位置a(可能调用转换运算符或转换构造函数或复制构造函数或复制赋值运算符 - 或移动那些一半的版本......但我们不是在这里了解C ++的复杂性。)
  • 在Python中,函数参数,变量或列表元素只是一个名称。该值在其他位置具有自己的类型内存位置。您可以为同一对象提供任意数量的名称。当您编写a = b或致电f(b)时,只会为同一个对象创建另一个名称。

特别是在您的情况下,collectionx是同一对象的名称。如果您在通话前添加print(id(collection))并在功能内添加了print(id(x)),则会同时打印相同的号码。


所以,在Python中,如果你想要一个副本,你必须明确地要求它 - 例如,pop_three(x[:])pop_three(copy.copy(x))

或者,当然,你可以在pop_three内完成。

或者,最重要的是,你可以首先避免使用像pop这样的变异方法:

def pop_three(collection):
    return collection[-3:][::-1], collection[:-3]

  

我很困惑,因为传递给函数的字符串或整数可以这种方式处理。

嗯,他们可以按照这种方式处理,就像列表一样。如果你从不改变任何东西,单独的副本和共享引用之间的区别是无关紧要的。*因为Python字符串和整数是不可变的,所以任何带有字符串或整数的代码都不能出现区别。另一方面,列表是可变的,因此区分可以出现 - 但是完全可能,实际上通常最好,编写使用它们而不会改变任何内容的代码,如上面的最后一个例子

请注意,这假定了上面的第一点:与C ++赋值不同,Python赋值不是一种突变形式。但它的边缘可能会变得棘手。例如,x[0] = 1是一个突变吗?嗯,它不是x[0]的变异,而是x的变异。那么x += y呢?语言留下了x的类型,对于可变类型,它通常(但不总是)是一个变异,而对于不可变类型,当然不是。

*这不是真的;您可以编写依赖于身份的代码,即使它不相关,例如,使用id函数或is运算符。这样的代码几乎不是Pythonic,或者是一个好主意......但它可以帮助学习如何工作。尝试使用print(id(x))进行上述x = "a" + "b"建议。但是,这可能会产生误导,因为Python解释器被允许“实习”它知道保证不可变的对象,并且它使用例如小整数来实现,并允许Python编译器折叠常量,并且它例如,使用文字字符串。

答案 1 :(得分:1)

因为当您将x传递给pop_tree()并且您调用collection.pop()时。这有效地做了x.pop() 因此当你从函数转向时,x只有7个元素

答案 2 :(得分:0)

z = pop_three(x)表示您传递了列表x,然后使用x collection.pop()更改了该列表x,因此x正在变异因此不能是== range(10)

y变为new_collectionz变为collection,这是x的变异返回值。

In [2]: x = range(10)

In [3]: y, z = pop_three(x)

In [4]: z is x # z is the object x
Out[4]: True

答案 3 :(得分:0)

列表是可变的。从列表中弹出时,可以修改列表。

最初指向列表的任何变量将继续指向相同的修改列表。您在列表中指向的任何新变量也将指向相同的修改列表。