簡體   English   中英

傳遞切片作為參數時會發生什么?

[英]What happens when passing a slice as an argument?

我最近遇到了切片的問題。 檢查以下代碼:

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無效?

切片在應用於列表時返回一個新容器,但內部項仍然相同。 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 引用是容器值的一部分。 在大多數情況下,當我們談論容器的價值時,我們暗示的是價值,而不是所包含對象的身份; 但是,當我們談論容器的可變性時,只隱含了直接包含的對象的標識。 因此,如果不可變容器(如元組)包含對可變對象的引用,則如果更改了可變對象,則其值會更改。

首先,您可以有兩個引用相同元素的單獨列表:

>>> 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)等復制它 - 所有這些都創建了一個淺表副本 如果你想要一個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 ),您可能會再次獲得相同的對象。


最重要的是,對於內置不可變類型的文字, 編譯器可能會合並兩個相同的文字,所以當它到達解釋器時,源代碼中看起來像兩個不同的相等值實際上是一個單獨的常量值。字節碼。 例如,在交互式解釋器(分別編譯每個語句)中,試試這個:

>>> 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僅保證在對象的生命周期內是唯一的。 因此,對於Python實現來說,如果第一個在第二個對象創建之前已經消失,則返回兩個對象的相同id是完全有效的。 (在CPython中, id是底層PyObject*的地址,因此如果相同的內存從對象空閑列表中刪除並重用,則會得到相同的id 。)


*從理論上講,它可能如果它知道他們是不可改變的......但因為沒有辦法可以知道他們是不可改變的,這是不會發生的,甚至可能是實習生自己的自定義對象。

你正在打印整數的id。 整數總是具有相同的ID。

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

你不應該使用id或者is與整數或字符串。 Python保留了實習這些值的選項,也就是說它出於性能原因而執行緩存小整數和字符串之類的操作,因此即使理論上應該是不同對象的兩個值也可能具有相同的id。

當這些內容被實現時,由實現來決定,所以它應該被視為未定義的行為。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM