【问题标题】:Appending to tuple during for in loop在 for in 循环期间附加到元组
【发布时间】:2018-08-20 21:45:21
【问题描述】:

我需要在for in 循环期间修改一个元组,以便迭代器在该元组上进行迭代。

据我了解,元组是不可变的;所以tup = tup + (to_add,) 只是重新分配tup,而不是更改原始元组。所以这很棘手。

这是一个测试脚本:

tup = ({'abc': 'a'}, {'2': '2'})
blah = True
to_add = {'goof': 'abcde'}
for i in tup:
    if blah:
        tup = tup + (to_add,)
        blah = False
    print(i)

哪些打印:

{'abc': 'a'}
{'2': '2'}

我想要的是打印出来:

{'abc': 'a'}
{'2': '2'}
{'goof': 'abcde'}

据我了解,我需要“重新指向”隐式元组迭代器中间脚本,以便它指向新的元组。 (我知道这是一件非常糟糕的事情)。

此脚本访问有问题的 tuple_generator:

import gc

tup = ({'abc': 'a'}, {'2': '2'})
blah = True
to_add = {'goof': 'abcde'}
for i in tup:
    if blah:
        tup = tup + (to_add,)
        blah = False
        refs = gc.get_referrers(i)
        for ref in refs:
            if type(ref) == tuple and ref != tup:
                refs_to_tup = gc.get_referrers(ref)
                for j in refs_to_tup:
                    if str(type(j)) == "<class 'tuple_iterator'>":
                        tuple_iterator = j

    print(i)

如何修改这个 tuple_generator 使它指向新的 tup,而不是旧的?这甚至可能吗?

我知道这是一个非常奇怪的情况,我无法更改 tup 是一个元组或者我需要使用隐式 for in,因为我正在尝试插入我无法更改的代码。

【问题讨论】:

  • 您无法更改tuple_iterator 中的元组,就像您无法更改原始元组一样。因为它们是同一个元组。正如我在the answer that you copied this code from 中就您之前的问题已经解释过的那样。
  • 为什么不在while循环中基于元组的长度来循环呢?类似while i &lt; len(tuple): i += 1
  • 我知道我无法更改元组。我不是想改变元组。我试图将 tuple_iterator 指向新的元组。 “我如何修改这个 tuple_generator 使它指向新的 tup,而不是旧的?这甚至可能吗?”
  • 不,这是不可能的。 tuple_iterator 上没有 API 来更改它所指的元组,甚至没有一个私有和未记录的元组。
  • 编写你自己的协程并send新的元组添加到它。

标签: python iterator tuples


【解决方案1】:

在 CPython 中,无论是可移植的还是专门的,都无法在 Python 中执行您尝试执行的操作,即使通过 tuple_iterator 对象的未记录内部结构也是如此。元组引用存储在不暴露给 Python 的变量中,并且(与存储的索引不同)不会被 __setstate__ 或任何其他方法修改。

但是,如果您愿意开始在 CPython 背后使用 C 指针,并且知道如何调试不可避免的段错误……

在幕后,有一个代表tuple_iterator 的C 结构体。我认为它要么是seqiterobject,要么是具有完全相同形状的结构,但您应该通读 tupleobject 源代码以确保。

这是该类型在 C 中的样子:

typedef struct {
    PyObject_HEAD
    Py_ssize_t it_index;
    PyObject *it_seq; /* Set to NULL when iterator is exhausted */
} seqiterobject;

那么,如果您创建一个与此大小相同的 ctypes.Structure 子类,会发生什么情况,如下所示:

class seqiterobject(ctypes.Structure):
    _fields_ = (
        ('ob_refcnt', ctypes.c_ssize_t),
        ('ob_type', ctypes.c_void_p),
        ('it_index', ctypes.c_ssize_t),
        ('it_seq', ctypes.POINTER(ctypes.pyobject)))

……然后这样做:

seqiter = seqiterobject.from_address(id(j))

……然后这样做:

seqiter.it_seq = id(other_tuple)

……?好吧,您可能会通过引用新值(并且还会泄漏旧值)来破坏堆,因此您需要先增加新值并减少旧值。

但是,如果你这样做......很可能,它会在你下次调用 __next__ 时出现段错误,或者它会起作用。

如果您想要更多执行类似操作的示例代码,请参阅superhackyinternals。除了seqiterobject 甚至不是公共类型,所以这甚至更多 hacky,其他一切都基本相同。

【讨论】:

    【解决方案2】:

    您可以编写自己的 coroutine 并将新的 tup 发送给它。

    def coro(iterable):
        iterable = iter(iterable)
        while True:
            try:
                v = next(iterable)
                i = yield v
            except StopIteration:
                break
            if i:
                yield v
                iterable = it.chain(iterable, i)
    

    那么这就像你描述的那样工作:

    In []:   
    blah = True
    tup = ({'abc': 'a'}, {'2': '2'})
    to_add = {'goof': 'abcde'}
    
    c = coro(tup)
    for i in c:
        if blah:
            i = c.send((to_add,))
            blah = False
        print(i)
    
    Out[]:
    {'abc': 'a'}
    {'2': '2'}
    {'goof': 'abcde'}
    

    我敢肯定,我在上面遗漏了很多边缘情况,但它应该让您了解如何做到这一点。

    【讨论】:

      【解决方案3】:

      由于您计划在循环内修改元组,因此最好使用 while 循环来跟踪当前索引,而不是依赖迭代器。迭代器仅适用于循环遍历未在循环中添加/删除的集合。

      如果您运行下面的示例,生成的 tup 对象将添加项目,同时循环 3 次。

      tup = ({'abc': 'a'}, {'2': '2'})
      blah = True
      to_add = {'goof': 'abcde'}
      
      i = 0
      while i < len(tup):
          cur = tup[i]
          if blah:
              tup = tup + (to_add,)
              blah = False
          i += 1
      
      print(tup)
      

      【讨论】:

        猜你喜欢
        • 2017-03-23
        • 2017-03-04
        • 2019-05-06
        • 1970-01-01
        • 1970-01-01
        • 2018-03-24
        • 2018-01-14
        • 2016-10-20
        • 2021-06-24
        相关资源
        最近更新 更多