如何使用迭代器修改可迭代对象的元素?即如何在Python中获取写入迭代器?

8

我非常喜欢Python的语法,但作为一个C++程序员,我对Python中的迭代器有些困惑。在C++中,有两种类型的迭代器-常量和修改(非常量)。在Python中,似乎(根据我所见)只有第一种类型,如果你想要修改元素,则必须使用索引,这让我感到不舒服并且不够通用。

让我用一个简单的例子来说明:

ab = ["a", "b"]
for (index, lett) in enumerate(ab):
    print "Firstly, lett is ab[index]?", lett is ab[index]
    lett = str(index)
    print lett
    print ab[index]
    print "But after, lett is ab[index]?", lett is ab[index]

所以我无法使用迭代器修改列表。 正如我通过使用is运算符发现的那样,它只会进行惰性复制(请参见维基百科),所以有没有办法使其成为直接修改迭代器而不是使用这种巧妙的方法?

for variable in iterable_object:

语法?

4个回答

9
语法
for x in iterable

不创建任何惰性副本--它将列表中的确切对象一一分配给x。如果这些对象是可变的,您可以修改它们:

a = [[1, 2], [3, 4]]
for x in a:
    x.append(5)
print a

打印

[[1, 2, 5], [3, 4, 5]]

您的示例使用了字符串列表。在Python中,字符串是不可变的,所以您不能修改它们。


嗯,你的帖子中有一些见解,但这不是我想要表达的。也许我应该使用“更改”或“替换”这个词,而不是“修改”,因为我想要更改整个元素。你可以在我的示例代码中看到它。我知道它可以更改元素的类型,但据我所知,它们都是“对象”,所以这并不重要。 - Huge

3
def iset(iterable):
    for i, x in enumerate(iterable):
        def setter(value):
            iterable[i] = value
        yield setter, x
a = range(10)
for set_x, x in iset(a):
    set_x(x * 2)
print a

打印

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

1
这更接近我想要的,但我担心并非每个可迭代对象都必须具有这个 [] 运算符,考虑一下图形(它不是 Python 标准库的一部分),[] 在其对象上会有什么意义呢?另外,你的解决方案并不是很直接,尽管它接近我所希望的。 - Huge
1
问题在于,你想要的并不是真正可能的。任何对象都可以定义一个迭代器,并使其按照自己的意愿行事。因此,在源代码中,没有通用的方法来修改(或者说替换)从迭代器返回的不可变对象 - 如果有这样的“源”。 (“迭代器”可以只生成值,如果您需要本地示例:生成器)。这就是为什么每个这样的解决方法必须特别设计以与您正在处理的可迭代对象相匹配的原因之一。当然,这限制了您交换对象的方式。 - phant0m
我明白了,我只是假设有一些元素迭代器,它们会返回对对象的某种“引用”,以便可以交换它。无论如何,感谢您的澄清。 - Huge
在Python中,只有标识符/名称(变量)是对象的引用。您不能让一个标识符始终引用不同标识符所引用的内容(类比:指针)。相反,两个标识符可以指向同一个对象,但它们本身并没有连接。 - phant0m
就我看来,标识符是由对象(数据)进行“连接”的,它们所引用的对象就像你写*ptr = new_value时一样,两个指针都得到了新值(旧值被覆盖)。而在Python中,我所缺少的是这种数据替换(反引用后)。 - Huge
不,它们并不会同时获得新值。相反,它们只是指向被写入的同一位置。如果它们连接在一起,你可以这样想:ptr = &value; ptr_ptr = &ptr; 现在 *ptr**ptr_ptr 将始终相同。如果我将 ptr 指向其他地方,*ptr == **ptr_ptr 仍然成立。在 Python 中,你永远不会得到这样的连接。变量可以指向完全相同的对象。一旦你给其中一个变量赋值,它就不再是这种情况了。 - phant0m

0

这里的问题实际上不是关于迭代器在Python中的工作方式与大多数Python类型上的赋值方式不同。当提问者尝试覆盖lett的值时,它确实是ab [index]的别名,并且应该在逻辑上起作用,但实际上并非如此。相反,lett(引用或指针lett,而不是它指向的值)被重新分配为指向其新值的常量,这与覆盖其指向的内存位置上的字节完全不同。这种工作方式是必要的,以允许Python的鸭子类型工作,其中变量名随时间可以指向具有不同大小的不同类型。请参阅此页面以获取有关此处发生的情况的更多解释:http://gestaltrevision.be/wiki/python/aliases

我们能够实现的最接近的方法是手动创建一个“可变整数”类型,它允许其基础值更改,而不像Python整数那样。然而,在这里手动解释这个问题没有什么意义,因为已经在这个问题中解释过了。虽然这个问题非常不同,但是根本问题相同,解决方案同样可行。但是,如果您要这样做,请首先考虑是否可以重构您的代码以避免这种情况,因为这是一种相当危险且容易出错的工作方式。
以下是来自“增量int对象”问题的回答的示例。有关如何将其组合在一起的完整说明,请参见上面链接的问题。请注意,此示例仅包括递减操作符,其他操作符必须以相同的方式实现。
import sys
class FakeInt(int):
    def __init__(self, *arg, **kwarg):
        self._decr = False
        int.__init__(self, *arg, **kwarg)
    def __neg__(self):
        if self._decr:
            upLocals = sys._getframe(1).f_locals
            keys, values = zip(*upLocals.items())
            i = list(values).index(self)
            result = FakeInt(self-1)
            upLocals[keys[i]]=result
            return result
        self._decr = not self._decr
        return self

0

一个类似的情况...

>>> a = b = 0
>>> a = 42
>>> a, b
(42, 0)

>>> a = b = [0]
>>> a[0] = 42
>>> a, b
([42], [42])

虽然Python在内部使用引用,而且ab都指向同一个0对象,但是说a = 42会替换a,而不是a所引用的东西。列表可以用作解决方法,但这绝不是优雅的方式。

迭代器就像a一样,它是实际的东西,但没有办法“取消引用”,我可以想象添加此功能会破坏许多其他东西。

我认为enumerate方法仍然是正确的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接