2011-09-14 29 views
16

我原本以爲Python是一種純粹的通過引用的語言。Python List&for-each訪問(在內置列表中查找/替換)

來自C/C++我不禁想到內存管理,很難把它從我的腦海中解放出來。所以我試圖從Java的角度來思考它,並把除了原始類型之外的所有東西都視爲通過引用。

問題:我有一個列表,其中包含一堆用戶定義的類的實例。

如果我使用的for-each語法,即:

for member in my_list: 
    print(member.str); 

member實際的參考對象的相同呢?

它在幹什麼相當於:

i = 0 
while i < len(my_list): 
    print(my_list[i]) 
    i += 1 

我覺得不是,因爲當我在找做了更換,這是行不通的,那就是,這不起作用:

for member in my_list: 
    if member == some_other_obj: 
     member = some_other_obj 

一個簡單的查找和替換列表。這可以在for-each循環中完成,如果是這樣,怎麼辦?否則,我只需要使用隨機訪問語法(方括號),否則將無法工作,我需要刪除條目,並插入一個新的?即:

i = 0 
for member in my_list: 
    if member == some_other_obj: 
     my_list.remove(i) 
     my_list.insert(i, member) 
    i += 1 
+0

在迭代列表期間不會複製值。原因類似於爲什麼[作爲參數傳遞給函數時不將對象複製](http://stackoverflow.com/questions/575196/in-python-why-can-a-function-modify-some-arguments-作爲感知由這-呼叫者-b)。 – jfs

回答

35

回答這個一直不錯,作爲評論在我自己的Python變量的理解導致的改善。

正如在評論中指出,當你遍歷一個列表的東西,如for member in my_listmember變量綁定到每個連續的列表元素。但是,在循環內重新分配該變量不會直接影響列表本身。例如,該代碼將不會改變列表:

my_list = [1,2,3] 
for member in my_list: 
    member = 42 
print my_list 

輸出:

[1,2,3]

如果要更改包含不可變類型的列表,你需要做的是這樣的:

my_list = [1,2,3] 
for ndx, member in enumerate(my_list): 
    my_list[ndx] += 42 
print my_list 

輸出:

[43,44,45]

如果列表中包含可變對象,可以修改當前member直接對象:

class C: 
    def __init__(self, n): 
     self.num = n 
    def __repr__(self): 
     return str(self.num) 

my_list = [C(i) for i in xrange(3)] 
for member in my_list: 
    member.num += 42 
print my_list 

[42,43,44]

請注意,您仍然沒有更改列表,只是修改列表中的對象。

您可能會從閱讀Naming and Binding受益。

+0

很不幸,它被重新定義爲列表成員的副本。我認爲將它作爲參考顯得更有用,就像你設置它一樣,它可能會以某種方式或另一種方式操縱結構。 我測試了語法: '對於範圍內的idx(len,my_list): my_list [idx] = new_obj' 它符合我的喜好。謝謝。 – Syndacate

+0

-1:'member'不是值的副本。 //ideone.com/y7A9M – jfs

+0

@Syndacate:不知道C++如何處理這樣的事情,但每個循環的Java版本(或以他們的術語加強for循環)以類似的方式工作,至少是有效的。 – GreenMatt

6

您可以通過獲取索引以及該項目來替換內容。

>>> foo = ['a', 'b', 'c', 'A', 'B', 'C'] 
>>> for index, item in enumerate(foo): 
...  print(index, item) 
... 
(0, 'a') 
(1, 'b') 
(2, 'c') 
(3, 'A') 
(4, 'B') 
(5, 'C') 
>>> for index, item in enumerate(foo): 
...  if item in ('a', 'A'): 
...   foo[index] = 'replaced!' 
... 
>>> foo 
['replaced!', 'b', 'c', 'replaced!', 'B', 'C'] 

需要注意的是,如果你想從你必須遍歷列表的副本列表中刪除的東西,否則既然你想改變的東西你迭代的大小,你會得到錯誤。這可以用切片很容易地完成。

錯誤:

>>> foo = ['a', 'b', 'c', 1, 2, 3] 
>>> for item in foo: 
...  if isinstance(item, int): 
...   foo.remove(item) 
... 
>>> foo 
['a', 'b', 'c', 2] 

的2仍然在那裏,因爲我們修改了列表的大小,我們遍歷它。正確的方法是:

>>> foo = ['a', 'b', 'c', 1, 2, 3] 
>>> for item in foo[:]: 
...  if isinstance(item, int): 
...   foo.remove(item) 
... 
>>> foo 
['a', 'b', 'c'] 
+0

+1是pythonic – neurino

+0

@neurino:pythonic方法是:'foo = [c for foo if foo if condition(c)]' – jfs

+0

@ J.F。塞巴斯蒂安:坦率地說,我認爲所有可以適合單線程的東西都比三行代碼更加pythonic,我想強調的是,當GreenMat卡住時,Gilder使用'enumerate'([他編輯了他的答案](http ://stackoverflow.com/revisions/7423184/2))到'for x in xrange(len())'。乾杯 – neurino

13

Python是不是Java,也不是C/C++ - 你需要停下來想,這樣才能真正利用的Python的力量。

Python沒有傳遞值或傳遞引用,而是使用名稱傳遞(或傳遞對象) - 換句話說,幾乎所有東西都綁定到一個名稱你可以使用(兩個明顯的例外是元組和列表索引)。

當你做spam = "green"時,你已經把名字spam綁定到字符串對象"green";如果你這樣做eggs = spam你沒有複製任何東西,你還沒有做參考指針;您只需將另一個名稱eggs綁定到同一個對象(在這種情況下爲"green")。如果您將spam綁定到其他東西(spam = 3.14159eggs仍將綁定到"green"

當for循環執行時,它會使用您提供的名稱,並在運行循環時將其依次綁定到iterable中的每個對象;當你調用一個函數時,它會在函數頭中獲取這些名稱並將它們綁定到傳遞的參數上;重新分配一個名字實際上是在重新命名一個名字(它可能需要一段時間來吸收這個 - 無論如何,它對我來說都是如此)。

隨着for循環利用名單中,有分配回列表兩種基本方式:

for i, item in enumerate(some_list): 
    some_list[i] = process(item) 

new_list = [] 
for item in some_list: 
    new_list.append(process(item)) 
some_list[:] = new_list 

通知上最後some_list[:] - 這是造成some_list的元素(將整個事物設置爲new_list的元素)的突變,而不是將名稱some_list重新命名爲new_list。這很重要嗎?這取決於!如果除了綁定到相同列表對象的some_list之外還有其他名稱,並且希望它們查看更新,則需要使用切片方法;如果你沒有,或者如果你做不是想讓他們看到更新,那麼rebind - some_list = new_list

+0

名稱綁定與Java中的相同。我看不出有什麼區別。 –

+1

在java中,像ints和booleans這樣的基元是通過值傳遞的,而不是基元的東西都是通過引用傳遞的。綁定到名稱類似於通過引用傳遞,減去類型安全性和可能的​​其他一些細節。在java中,一個引用至少在編譯過程中保留了有關它可以引用的python名稱的類型數據沒有這種限制。 – Sqeaky