【问题标题】:Impact of removing a list item on reversed() in python在 python 中删除列表项对 reversed() 的影响
【发布时间】:2021-07-08 07:28:43
【问题描述】:

据我所知,reversed() 函数提供了一个迭代器,其工作方式与iter() 类似,但会以相反的顺序提供项目。但是,我遇到了从 reversed() 函数返回的对象的奇怪行为。

通过查看:

lst = ['a', 'b', 'c', 'd']
iter_lst = iter(lst)
lst.remove('c')
print(list(iter_lst))

输出:['a', 'b', 'd']

正如预期的那样。但是:

lst = ['a', 'b', 'c', 'd']
rev_iter_lst = reversed(lst)
lst.remove('c')
print(list(rev_iter_lst))

输出:[]

不应该是:['d', 'b', 'a'] 吗?

列表对象中的__reversed__() 方法或迭代器对象中的__next__() 方法的实现是否阻止了这种情况?我的意思是,如果原始列表中的某些内容发生变化,它可能不会产生反向序列......

更新:我已经发布了一个可能修复它的答案here,我已经对此进行了测试,但我不知道是否存在这种实现会毫无例外的情况结果。

【问题讨论】:

  • 基本规则:永远不要改变你正在迭代的列表!
  • @KlausD。没错,我只是出于好奇。

标签: python python-3.x list


【解决方案1】:

根据list.__reversed__source code,迭代器记住最后一个索引地址,返回记住最后一个索引地址的迭代器。 现在,当您删除一个项目时,它将移动所有索引并使最后一个地址指向无处,并且它将返回一个空列表,因为没有什么可以迭代。 让我再描述一下: 考虑以下列表:lst = ['a','b','c'] 还让我们假设lst[0] 在 100 并且每个字符是一个字节,所以字符 'c' 在 102 中。 当您创建一个受人尊敬的迭代器时,它会记住 102 作为起点。 在下一步中,我们省略 'b' 现在字符 'c' 在地址 101 中。 当您要求迭代器进行迭代时,它会开始查看位置 102。它会在那里找到什么?实际上什么都没有,显然它会返回一个空列表。

我希望这会有所帮助:)

编辑:字地址不正确。我必须改用索引...

【讨论】:

  • 谢谢,它似乎是这样工作的。但是你能告诉这个源代码的哪一行说如果一个项目被删除然后返回一个空列表。从列表的长度重新生成列表的最后一个索引有那么难吗?
  • 我在回答中添加了更多细节,@SorousHBakhtiary
  • 像列表这样的序列类型,将对象的大小存储在一个属性中。每当调用 append()remove() 方法时,它们都会更改该属性。所以 Python 可以很容易地找出列表中最后一项的位置。如果'b' 被删除,该属性会减少到2。这应该不是问题。还是我在这里遗漏了什么?
  • 我不确定这个答案,但我认为实现这样的想法可能会导致一些歧义。请考虑这段代码:``` lst = ['a','b','c',d' ] rev = reversed(a) print(rev.__next__()) a.pop(2) print(list(rev)) ``` 现在呢?我们应该重新打印'd'吗?还是我们应该从“b”继续?
  • 既然实现了它应该从原始列表中获取项目,那么它应该重新打印'd'。这在正常情况下不会发生。因为我们动态地改变了项目的索引和顺序,当然会发生这种结果。事实上,这就是 python 处理这种情况的方式。例如如果我们遍历一个列表并同时追加到它,我们将看到无限循环。
【解决方案2】:

list 调用将迭代反向迭代器,其 index < PyList_GET_SIZE(seq) 检查 here 将失败,因为您同时缩小了 seq,因此不会产生值而是停止:

listreviter_next(listreviterobject *it)
{
    (some checks)
    index = it->it_index;
    if (index>=0 && index < PyList_GET_SIZE(seq)) {
        (decrease the index and return the element)
    }
    (stop the iteration)
}

【讨论】:

  • 谢谢,我仍然怀疑他们为什么决定不提供which I've provided here 的方法,会有什么问题?检索新列表的长度并不难。
  • 我猜他们不想明确支持这种“坏”的用法。您可以尝试追踪该支票是如何进入那里的,并希望作者留下一些评论:-)
【解决方案3】:

所以在与@kkasra12 讨论之后,我最终实现了 Python 的伪列表对象(没有进行所有必要的检查)来模仿它的行为,我只专注于 reverse() 操作。这是我的课:

class MyList:
    def __init__(self, n):
        self.length = n
        self._seq = list(range(n))

    @property
    def seq(self):
        return self._seq

    def __len__(self):
        return self.length

    def __getitem__(self, item):
        return self._seq[item]

    def __setitem__(self, idx, value):
        self._seq[idx] = value

    def __reversed__(self):
        return ReverseIterator(self)

    def __str__(self):
        return str(self._seq)

    def append(self, v):
        self._seq.append(v)
        self.length += 1

    def remove(self, v):
        self._seq.remove(v)
        self.length -= 1

还有我的ReverseIterator

class ReverseIterator:
    def __init__(self, org):
        self.org = org
        self._index = org.length

    def __iter__(self):
        return self

    def __next__(self):
        if 0 < self._index:
            try:
                item = self.org.seq[self._index - 1]
                self._index -= 1
                return item
            except IndexError:
                raise StopIteration()
        else:
            raise StopIteration()

结果:

obj = MyList(6)
iter_obj = iter(obj)
obj.remove(2)
print(list(iter_obj))

print('-----------------------')

obj = MyList(6)
rev_iter_obj = reversed(obj)
obj.remove(2)
print(list(rev_iter_obj))

输出:

[0, 1, 3, 4, 5]
-----------------------
[]

通过注释上面的remove 语句,我们可以看到它的工作方式与原始list 对象一样。

然后我创建了新的 SmartReverseIterator 迭代器,它可以处理从原始对象中删除项目的情况,并且可以动态生成值,就像iter() 在 OP 中的列表中工作一样。

唯一应该考虑的是,如果删除了一个项目(self._index 将小于原始对象的长度),则应重置 self._index

class SmartReverseIterator:
    def __init__(self, org):
        self.org = org
        self._index = org.length

    def __iter__(self):
        return self

    def __next__(self):
        if 0 < self._index:
            try:
                item = self.org.seq[self._index - 1]
                return item

            except IndexError:
                self._index = self.org.length
                item = self.org.seq[self._index - 1]
                return item

            finally:
                self._index -= 1
        else:
            raise StopIteration()

通过更改MyList 上的__reversed__ 方法以返回这个新的迭代器,结果将是:

obj = MyList(6)
iter_obj = iter(obj)
obj.remove(2)
print(list(iter_obj))

print('-----------------------')

obj = MyList(6)
rev_iter_obj = reversed(obj)
obj.remove(2)
print(list(rev_iter_obj))

输出:

[0, 1, 3, 4, 5]
-----------------------
[5, 4, 3, 1, 0]

我想知道这是否有任何缺点,或者换句话说,为什么 python 决定不对这样的 list 对象实现 __reversed__ 方法,以确切地导致 iter() 如果项目是删除。

在什么情况下我们会看到问题?

【讨论】:

  • 太好了,太好了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-18
  • 2021-06-19
  • 1970-01-01
  • 2017-09-12
  • 2022-12-20
  • 1970-01-01
  • 2014-06-01
相关资源
最近更新 更多