传递切片作为参数时会发生什么?

时间:2014-01-18 22:13:48

标签: python list slice

我最近遇到了切片的问题。请检查以下代码:

def clean(l):
    for i in range(len(l)):
        l[i] = 0

lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
clean(lst[:])
print lst

此代码打印出[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

似乎函数内的l[i] = 0无效。所以我猜Python在将切片传递给函数时正在制作列表的副本...然后我做了另一个测试......

def clean(l):
    for i in range(len(l)):
        print id(l[i])
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
clean(lst)
print
clean(lst[:])

令我惊讶的是输出结果如下:

140373225703016
140373225702992
140373225702968
140373225702944
140373225702920
140373225702896
140373225702872
140373225702848
140373225702824
140373225702800

140373225703016
140373225702992
140373225702968
140373225702944
140373225702920
140373225702896
140373225702872
140373225702848
140373225702824
140373225702800

这说明Python没有制作列表的副本,而是参考文献的副本...... 这变得很奇怪。由于函数内部的引用仍指向原始列表对象,为什么将值设置为0无效?

4 个答案:

答案 0 :(得分:5)

切片在应用于列表时返回一个新容器,但内部项仍然相同。将0分配给切片列表上的索引时,您将更改该新容器的值而不是原始列表。

>>> lis = [1, 2, 3]                                           
>>> lis_1 = lis[:]
>>> lis_1 is lis     # Outer lists are different
False
>>> lis_1[0] is lis[0]
True
>>> lis_1[0] = 10    # This assignment affects only `lis_1` not `lis`.
>>> lis
[1, 2, 3]
>>> lis_1
[10, 2, 3]
>>> 

以上列表仅包含不可变项目,当您的列表包含可变项目并且您对该项目执行就地操作时,可以在所有列表中看到更改:

>>> lis = [[1], 2, 3]
>>> lis_1 = lis[:]
>>> lis_1 is lis        #Outer container are different
False
>>> lis[0] is lis_1[0]  #But inner lists are same.
True
>>> lis[0].append(10)
>>> lis
[[1, 10], 2, 3]
>>> lis_1
[[1, 10], 2, 3]

来自docs

  

某些对象包含对其他对象的引用;这些被称为   的容器即可。容器的示例包括tupleslistsdictionaries。   引用是容器值的一部分。在大多数情况下,当我们   谈论一个容器的价值,我们暗示价值,而不是   所包含物体的身份;但是,当我们谈论的时候   容器的可变性,只有立即的身份   包含的对象是隐含的。所以,如果一个不可变的容器(如   tuple)包含对可变对象的引用,其值如果发生变化   可变对象被更改。

答案 1 :(得分:3)

首先,您可以有两个引用相同元素的单独列表:

>>> a, b, c = object(), object(), object()
>>> lst0 = [a, b, c]
>>> lst1 = list(reversed([c, b, a]))
>>> id(lst0), id(lst1)
(4418696712, 4399675656)
>>> id(lst0[0]), id(lst1[0])
(4298830320, 4298830320)

如果你从另一个列表中创建一个列表,那么通常是的情况。使用old_list[:]切换列表,使用list(old_list)构建新列表,使用copy.copy(old_list)复制它等等 - 所有这些都会创建shallow copy。如果您需要2级副本,则必须明确地执行此操作,例如[copy.copy(x) for x in old_lst]。如果您想要一个完全向下的深层复制,请使用copy.deepcopy(old_lst)

因此,当您检查两个列表中元素的id时,您不应该对它们看起来一样感到惊讶。 Ashwini Chaudhary的回答解释了这种后果。


最重要的是,Python总是被允许“实习”不可变对象,如整数,字符串,甚至元组和冻结集。*与Java不同,您可以使用new来控制它,在Python中,它完全取决于实现,当你要求它时,你永远不应该指望获得一个新的不可变对象。

CPython仅针对一小组值执行此操作,但它包括在编译时指定的一系列“微小整数”,默认为2.7中的-6到255。因此,即使您进行了深层复制 - 或者即使您尝试创建全新的整数对象(例如int(x*2.0+0.1)//2),您也可能仍然再次获得相同的对象。 / p>


最重要的是,对于内置不可变类型的文字,编译器可以合并两个相同的文字,所以当它到达解释器时,看起来像两个不同的相等值源代码实际上是字节码中的单个常量值。例如,在交互式解释器(分别编译每个语句)中,试试这个:

>>> 123456 is 123456
True
>>> a = 123456
>>> a is 123456
False

您无法保证获得这些结果,但这是我在笔记本电脑上使用CPython 2.7和3.4所获得的结果。但是在函数中,因为函数定义是一个单独的复合语句,它们都汇编在一起:

>>> def f():
...     a = 123456
...     return a is 123456
>>> f()
True

通常,您也不应指望获取新的不可变对象 - 除了少数内置常量的特殊情况,例如None


最后,请小心使用id进行测试。 id仅保证在对象的生命周期内是唯一的。因此,如果第一个对象在创建第二个对象之前已经消失,那么对于两个对象返回相同的id是完全有效的。 (在CPython中,id是底层PyObject*的地址,因此如果相同的内存从对象空闲列表中删除并重用,则会得到相同的id。)


*从理论上讲,如果它知道它们是不可变的,它甚至可能会实施你自己的自定义对象......但是因为无法知道它们是不可变的,所以这不会发生。< / p>

答案 2 :(得分:0)

您正在打印整数的ID。 整数总是具有相同的ID。

>>> id(9)
5126256
>>> id(9)
5126256
>>> id(123)
5126872
>>> id(122+1)
5126872
>>> 

答案 3 :(得分:0)

您不应将idis与整数或字符串一起使用。 Python保留了实现这些值的选项,也就是说它出于性能原因而执行缓存小整数和字符串之类的操作,因此即使理论上应该是不同对象的两个值也可能具有相同的id。

当这些内容被实现时,由实现来决定,所以它应该被视为未定义的行为。

http://me.veekun.com/blog/2012/03/24/python-faq-equality/