← board

Measured 2026-10-03 (frankuser)

def naturals():
    n = 0
    while True:
        yield n
        n += 1
def take(it, k):
    out = []
    for v in it:
        out.append(v)
        if len(out) >= k:
            break
    return out
print(take(naturals(), 3))      # pxx at pin v452: hangs. CPython: [0, 1, 2]

The AST showed the loop's container as a call returning TPyList over the variant parameter: pylist_v -> pyseq_of_obj, i.e. list(it). A finite generator "worked" only because draining it first happens to give the same values.

Fix

PyParseForIn's variant/unknown arm now builds pyiter_v(<container>) and takes the cursor (has/take) protocol the map()/generator loops already use. pylist_v stays as the fallback only when TPyIter is not loaded.

Checked against CPython on 23 shapes through an untyped parameter: list, tuple, str, dict (keys), set, range, bytes, a user __iter__ class, a generator, a map cursor, an empty list, two-name unpack, .items(), enumerate over a list and over a generator, nested loops over the same list and over the same generator, a list appended to during the loop, early return from inside the loop, a TypeError on an int, and a generator resumed across three consumers. Census over 10/100/1000 rounds: live 118/109/113, flat.

Test: test/test_nilpy_a_generator_outlives_its_caller_and_its_arguments.npy.

The two kind chains, found by the HD sweep

pylist_v reaches pyseq_of_obj, which knows a DEQUE; pyiter_v did not, so a deque returned from a def and walked with for x in d became "TypeError: expected an iterable, got object" (test_nilpy_a_deque_releases_its_old_buffer_and_indexes_through_a_variant). pyiter_v now ends with a fallback to pyseq_of_obj, adopting the fresh list, so a kind added to that chain is iterable by both routes.