Мне нужно изменить кортеж во время цикла 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 является кортежем или что мне нужно использовать неявный for in, так как я пытаюсь подключиться к коду, который не могу изменить.

0
Daniel Paczuski Bak 21 Авг 2018 в 00:45

3 ответа

Лучший ответ

В CPython нет способа - ни переносимого, ни специально - сделать то, что вы пытаетесь сделать из Python, даже через недокументированные внутренние объекты объекта tuple_iterator. Ссылка на кортеж хранится в переменной, которая не доступна Python, и (в отличие от сохраненного индекса) не изменяется __setstate__ или любым другим методом.

Тем не менее, если вы готовы начать обезьяны с указателями C за спиной CPython, и вы знаете, как отлаживать неизбежные ошибки сегмента…

Под обложками есть структура C, представляющая tuple_iterator. Я думаю, что это либо 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 даже не является публичным типом, так что это даже более хакерский, все остальное в основном то же самое.

1
abarnert 20 Авг 2018 в 22:09

Поскольку вы планируете модифицировать кортеж внутри цикла, вам, вероятно, лучше использовать цикл 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)
-1
Karl 20 Авг 2018 в 22:02

Вы могли бы написать свой собственный 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'}

Я уверен, что есть много крайних случаев, которые я пропускаю в вышеупомянутом, но это должно дать вам представление о том, как это можно сделать.

0
AChampion 20 Авг 2018 в 22:25
51938903