簡體   English   中英

Python:創建一個函數來通過引用而不是值來修改列表

[英]Python: create a function to modify a list by reference not value

我正在做一些性能關鍵的Python工作,並希望創建一個函數,如果符合某些條件,則從列表中刪除一些元素。 我寧願不創建列表的任何副本,因為它充滿了很多非常大的對象。

我想實現的功能:

def listCleanup(listOfElements):
    i = 0
    for element in listOfElements:
        if(element.meetsCriteria()):
            del(listOfElements[i])
        i += 1
    return listOfElements

myList = range(10000)
myList = listCleanup(listOfElements)

我不熟悉Python的低級工作方式。 myList是通過值還是通過引用傳遞的?

我怎樣才能讓它更快?

有可能以某種方式擴展列表類並在其中實現listCleanup()嗎?

myList = range(10000)
myList.listCleanup()

謝謝-

喬納森

Python以相同的方式傳遞所有內容,但是“按值”或“通過引用”調用它不會清除所有內容,因為Python的語義與這些術語通常適用的語言不同。 如果我要描述它,我會說所有傳遞都是按值,並且該值是對象引用。 (這就是我不想說的原因!)

如果要從列表中過濾掉一些內容,可以構建一個新列表

foo = range(100000)
new_foo = []
for item in foo:
    if item % 3 != 0: # Things divisble by 3 don't get through
        new_foo.append(item)

或者,使用列表推導語法

 new_foo = [item for item in foo if item % 3 != 0]

Python不會復制列表中的對象,而是foonew_foo都會引用相同的對象。 (Python從不隱式復制任何對象。)


您已建議您對此操作有性能問題。 使用舊列表中的重復del語句將導致代碼不再慣用且更難以處理,但它將引入二次性能,因為每次必須重新整理整個列表。

解決性能問題:

  • 啟動並運行。 除非你有代碼工作,否則你無法弄清楚你的表現是什么樣的。 這也將告訴您是否必須優化速度或空間; 你在代碼中提到了對這兩者的關注,但通常優化涉及以另一個為代價獲得一個。

  • 輪廓。 您可以使用stdlib工具及時獲得性能。 有各種第三方內存分析器可能有點用,但不太適合使用。

  • 測量。 當您進行更改以查看更改是否有所改進時, 時間或重新編碼內存,如果是,那么改進是什么。

  • 為了使您的代碼對內存更敏感,您通常需要在存儲數據的方式上進行范式轉換,而不是像不構建第二個列表來進行過濾的微觀優化。 (對於時間也是如此,真的:改為更好的算法幾乎總能提供最佳的加速。然而,更難以概括速度優化)。

    在Python中優化內存消耗的一些常見范例轉換包括

    1. 使用生成器。 生成器是懶惰的迭代:它們不會立即將整個列表加載到內存中,它們會在運行中找出它們的下一個項目。 要使用生成器,上面的代碼段看起來像

       foo = xrange(100000) # Like generators, xrange is lazy def filter_divisible_by_three(iterable): for item in foo: if item % 3 != 0: yield item new_foo = filter_divisible_by_three(foo) 

      或者,使用生成器表達式語法,

       new_foo = (item for item in foo if item % 3 != 0) 
    2. numpy用於同源序列,特別是那些數字化的序列。 這也可以加速執行大量矢量操作的代碼。

    3. 將數據存儲到磁盤,例如數據庫中。

在Python中,列表總是通過引用傳遞。

列表中對象的大小不會影響列表性能,因為列表僅存儲對對象的引用。 但是,列表中的項目數確實會影響某些操作的性能 - 例如刪除元素,即O(n)。

正如所寫的那樣,listCleanup是最壞情況的O(n ** 2),因為你在一個可能是O(n)本身的循環中有O(n)del操作。

如果元素的順序無關緊要,您可以使用內置set類型而不是列表。 set具有O(1)刪除和插入。 但是,您必須確保對象是不可變和可清除的。

否則,你最好重新創建列表。 那是O(n),你的算法需要至少為O(n),因為你需要檢查每個元素。 您可以在一行中過濾列表,如下所示:

listOfElements[:] = [el for el in listOfElements if el.MeetsCriteria()]

看起來像是過早優化。 在嘗試優化之前,您應該嘗試更好地理解python的工作原理。

在這種特殊情況下,您無需擔心對象大小。 復制列表是使用列表推導或切片將只執行表面復制(復制對象的引用,即使該術語不適用於python)。 但是列表中的項目數可能很重要,因為del是O(n)。 可能存在其他解決方案,例如用None或傳統Null對象替換項目,或者使用諸如集合或字典之類的其他數據結構,其中刪除項目的成本低得多。

我認為沒有人提到實際使用過濾器。 由於很多答案來自備受尊敬的人,我確信我是那個缺少某些東西的人。 有人可以解釋一下這會是什么問題:

new_list = filter(lambda o: o.meetsCriteria(), myList)

當你迭代它時修改你的數據結構就像在腳下射擊自己...迭代失敗。 你不妨拿別人的建議,然后做一個新的清單:

myList = [element for element in listOfElements if not element.meetsCriteria()]

舊列表 - 如果沒有其他引用 - 將被解除分配並回收內存。 更好的是,甚至不要復制清單。 將上面的內容更改為生成器表達式以獲得更加內存友好的版本:

myList = (element for element in listOfElements if not element.meetsCriteria())

所有Python對象訪問都是通過引用。 創建對象,變量只是對這些對象的引用。 但是,如果有人想問純粹問題,“Python使用什么類型的調用語義,按引用調用或按值調用?” 答案必須是“既不......又兩個。” 原因是因為調用約定對Python而言不如對象類型重要。

如果一個對象是可變的,那么無論你在哪個范圍內都可以修改它......只要你有一個有效的對象引用,就可以改變對象。 如果對象是不可變的 ,那么無論您身在何處或您擁有什么參考,都無法更改該對象。

可以在原位刪除列表元素,但不能在列表中向前刪除。 你的代碼簡單無效 - 隨着列表的縮小,你可能會錯過檢查元素。 你需要向后退,這樣縮小的部分就在你身后,代碼相當可怕。 在我向您展示之前,有一些初步的考慮因素:

首先,垃圾是如何進入清單的? 預防勝於治療。

其次,列表中有多少元素,可能需要刪除的百分比是多少? 百分比越高,創建新列表的可能性就越大。

好的,如果您仍想在原地進行,請考慮以下事項:

def list_cleanup_fail(alist, is_bad):
    i = 0
    for element in alist:
        print "i=%d alist=%r alist[i]=%d element=%d" % (i, alist, alist[i], element)
        if is_bad(element):
            del alist[i]
        i += 1

def list_cleanup_ok(alist, is_bad):
    for i in xrange(len(alist) - 1, -1, -1):
        print "i=%d alist=%r alist[i]=%d" % (i, alist, alist[i])
        if is_bad(alist[i]):
            del alist[i]

def is_not_mult_of_3(x):
    return x % 3 != 0

for func in (list_cleanup_fail, list_cleanup_ok):
    print
    print func.__name__
    mylist = range(11)
    func(mylist, is_not_mult_of_3)
    print "result", mylist

這是輸出:

list_cleanup_fail
i=0 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=0 element=0
i=1 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=1 element=1
i=2 alist=[0, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=3 element=3
i=3 alist=[0, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=4 element=4
i=4 alist=[0, 2, 3, 5, 6, 7, 8, 9, 10] alist[i]=6 element=6
i=5 alist=[0, 2, 3, 5, 6, 7, 8, 9, 10] alist[i]=7 element=7
i=6 alist=[0, 2, 3, 5, 6, 8, 9, 10] alist[i]=9 element=9
i=7 alist=[0, 2, 3, 5, 6, 8, 9, 10] alist[i]=10 element=10
result [0, 2, 3, 5, 6, 8, 9]

list_cleanup_ok
i=10 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] alist[i]=10
i=9 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] alist[i]=9
i=8 alist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] alist[i]=8
i=7 alist=[0, 1, 2, 3, 4, 5, 6, 7, 9] alist[i]=7
i=6 alist=[0, 1, 2, 3, 4, 5, 6, 9] alist[i]=6
i=5 alist=[0, 1, 2, 3, 4, 5, 6, 9] alist[i]=5
i=4 alist=[0, 1, 2, 3, 4, 6, 9] alist[i]=4
i=3 alist=[0, 1, 2, 3, 6, 9] alist[i]=3
i=2 alist=[0, 1, 2, 3, 6, 9] alist[i]=2
i=1 alist=[0, 1, 3, 6, 9] alist[i]=1
i=0 alist=[0, 3, 6, 9] alist[i]=0
result [0, 3, 6, 9]

只是要清楚:

def listCleanup(listOfElements):
    i = 0
    for element in listOfElements:
        if(element.meetsCriteria()):
            del(listOfElements[i])
        i += 1
    return listOfElements

myList = range(10000)
myList = listCleanup(listOfElements)

是相同的

def listCleanup(listOfElements):
    i = 0
    for element in listOfElements:
        if(element.meetsCriteria()):
            del(listOfElements[i])
        i += 1

myList = range(10000)
listCleanup(listOfElements)

暫無
暫無

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

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