簡體   English   中英

python迭代器是否需要額外的內存?

[英]Does python iterator cost additional memory?

我想知道my_iter = iter(iterable_obj)復制了iterable_obj 換句話說,上面的調用是否需要額外的內存?

它復制嗎? 也許。 但不應該。

可以復制,但不應該 它應該只在現有數據結構上提供迭代,並根據迭代所需的最小內存開銷。 例如, list迭代器僅存儲對列表的引用和索引。

它有什么作用

它有什么作用 那要看。 iter函數將提供一個迭代器,覆蓋整個世界中任何可能的可迭代對象,包括一個你明天只會編寫的具有復雜內部數據結構的可迭代類。 iter怎么可能做到這一點? 人工智能? 魔法? 不。嗯……實際上是的,魔法。 即用所謂的“魔法方法”(或“dunder方法”)。 在這種情況下, __iter____getitem__ 訣竅是, iter知道如何迭代可迭代對象。 可迭代的 並使用這兩種魔術方法之一來訪問迭代。 iter功能只是調用它的代碼(希望迭代)和迭代(它提供了迭代)之間簡單的中間人。

__iter__方法返回迭代器的示例:

class MyIterable:
    def __iter__(self):
        return iter('abcde')

print(list(MyIterable()))

輸出:

['a', 'b', 'c', 'd', 'e']

使用__getitem__方法返回索引 0、1、2 等元素的示例(直到IndexError ):

class MyIterable:
    def __getitem__(self, index):
        return 'abcde'[index]

print(list(MyIterable()))

輸出:

['a', 'b', 'c', 'd', 'e']

那么,是什么iter(iterable)嗎? 取決於迭代器的作用。 它可能會復制,也可能不會,它可能會試圖讓你的房子着火。

對於像列表迭代器這樣簡單的東西,選擇是顯而易見的:使用對列表的引用和迭代器所在位置的索引既簡單又高效。

更有趣的案例:二叉搜索樹迭代器

讓我們考慮一個不那么明顯並且您可能復制的情況:一個二叉搜索樹迭代器,它提供按排序順序迭代樹的值。 讓我們考慮三種可能的實現,其中 n 是樹中值的數量。 該樹將表示為BST節點對象的結構:

class BST:
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right

可能的實現方式一:遞歸迭代器

class BST:
    ...
    def __iter__(self):
        if self.left:
            yield from self.left
        yield self.value
        if self.right:
            yield from self.right

好處:

  • 簡單的代碼。
  • O(1) 迭代器創建的時間和空間。
  • 懶惰,只迭代請求的次數。
  • 迭代期間只有 O(h) 內存,其中 h 是樹的高度。 如果樹是平衡的,則可能低至 Θ(log n),如果樹非常不平衡,則可能高至 Θ(n)。

缺點:

  • 減緩。 每個值都通過整個迭代器堆棧傳遞到根。 所以迭代整個樹至少需要 Θ(n log n) 和最多 Θ(n²) 的時間。

可能的實現 2:將值復制到列表中

由於緩慢的迭代,尤其是二次時間,非常令人失望,我們可以將樹中的所有值復制到一個列表中,並在該列表上返回一個迭代器:

class BST:
    ...
    def __iter__(self):
        values = []
        def collect(node):
            if node:
                collect(node.left)
                values.append(node.value)
                collect(node.right)
        collect(self)
        return iter(values)

好處:

  • 簡單的代碼。
  • 線性時間迭代。

缺點:

  • Θ(n) 記憶。
  • Θ(n) 時間已經用於創建迭代器,甚至在開始實際迭代之前。

可能的實現方式 3:迭代

這是一個使用堆棧的迭代。 堆棧將保存其值和其子樹仍需要迭代的節點:

class BST:
    ...
    def __iter__(self):
        node = self
        stack = []
        while node or stack:
            while node:
                stack.append(node)
                node = node.left
            node = stack.pop()
            yield node.value
            node = node.right

結合了前兩種實現的優點(既節省時間又節省內存),但缺點是不容易。 與前兩個實現不同,我覺得有必要對它的工作原理添加一點解釋,如果你以前沒有見過它,你可能仍然需要考慮一下。

結論

如果這對您來說只是一個小練習,並且您沒有效率問題,那么前兩個實現很好且易於編寫。 盡管將值復制到列表中並不是真正的普通迭代器,因為復制值從根本上不是迭代的意思。 不過,這與內存無關。 遞歸生成器和迭代方法也需要 O(log n) 和 O(n) 內存,但這是組織數據,對於促進迭代有些必要。 他們沒有復制內容數據。

如果它是一個認真使用的 BST 包,那么我會發現前兩個實現的缺點是不可接受的,並且會使用迭代實現。 一次編寫更多的努力,但具有優勢和適當的迭代器。

順便說一句,如果節點也有對其父節點的引用,我認為迭代器可以使用它來使用 O(1) 內存進行高效迭代。 留給讀者練習:-P

代碼

可使用的 BST 代碼( 在線試用! ):

from random import shuffle

class BST:

    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right

    def insert(self, value):
        if value < self.value:
            if self.left:
                self.left.insert(value)
            else:
                self.left = BST(value)
        elif value > self.value:
            if self.right:
                self.right.insert(value)
            else:
                self.right = BST(value)

    def __repr__(self):
        return f'BST({self.value}, {self.left}, {self.right})'

    def __iter__(self):
        yield from self.left or ()
        yield self.value
        yield from self.right or ()

    def __iter__(self):
        values = []
        def collect(node):
            if node:
                collect(node.left)
                values.append(node.value)
                collect(node.right)
        collect(self)
        return iter(values)

    def __iter__(self):
        node = self
        stack = []
        while node or stack:
            while node:
                stack.append(node)
                node = node.left
            node = stack.pop()
            yield node.value
            node = node.right

# Build a random tree
values = list(range(20)) * 2
shuffle(values)
tree = BST(values[0])
for value in values[1:]:
   tree.insert(value)

# Show the tree
print(tree)

# Iterate the tree in sorted order
print(list(tree))

示例輸出:

BST(1, BST(0, None, None), BST(17, BST(10, BST(6, BST(2, None, BST(4, BST(3, None, None), BST(5, None, None))), BST(9, BST(7, None, BST(8, None, None)), None)), BST(15, BST(11, None, BST(13, BST(12, None, None), BST(14, None, None))), BST(16, None, None))), BST(18, None, BST(19, None, None))))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

暫無
暫無

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

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