Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extract a list from itertools.cycle

I have a class which contains a itertools.cycle instance which I would like to be able to copy. One approach (the only one I can come up with), is to extract the initial iterable (which was a list), and store the position that the cycle is at.

Unfortunately I am unable to get hold of the list which I used to create the cycle instance, nor does there seem to be an obvious way to do it:

import itertools
c = itertools.cycle([1, 2, 3])
print dir(c)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', 
 '__hash__', '__init__', '__iter__', '__new__', '__reduce__', 
 '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', 
 '__subclasshook__', 'next']

I can come up with some half reasonable reasons why this would be disallowed for some types of input iterables, but for a tuple or perhaps even a list (mutability might be a problem there), I can't see why it wouldn't be possible.

Anyone know if its possible to extract the non-infinite iterable out of an itertools.cycle instance. If not, anybody know why this idea is a bad one?

like image 244
pelson Avatar asked Aug 20 '12 12:08

pelson


1 Answers

It's impossible. If you look at itertools.cycle code you'll see that it does not store a copy of the sequence. It only create an iterable and store the values contained in the iterable in a newly created list:

static PyObject *
cycle_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *it;
    PyObject *iterable;
    PyObject *saved;
    cycleobject *lz;

    if (type == &cycle_type && !_PyArg_NoKeywords("cycle()", kwds))
        return NULL;

    if (!PyArg_UnpackTuple(args, "cycle", 1, 1, &iterable))
        return NULL;
    /* NOTE: they do not store the *sequence*, only the iterator */
    /* Get iterator. */
    it = PyObject_GetIter(iterable);
    if (it == NULL)
        return NULL;

    saved = PyList_New(0);
    if (saved == NULL) {
        Py_DECREF(it);
        return NULL;
    }

    /* create cycleobject structure */
    lz = (cycleobject *)type->tp_alloc(type, 0);
    if (lz == NULL) {
        Py_DECREF(it);
        Py_DECREF(saved);
        return NULL;
    }
    lz->it = it;
    lz->saved = saved;
    lz->firstpass = 0;

    return (PyObject *)lz;
}

This means that when doing:

itertools.cycle([1,2,3])

The list you create has only 1 reference, that is kept in the iterator used by cycle. When the iterator is exhausted the iterator gets deleted and a new iterator is created:

    /* taken from the "cycle.next" implementation */
    it = PyObject_GetIter(lz->saved);
    if (it == NULL)
        return NULL;
    tmp = lz->it;
    lz->it = it;
    lz->firstpass = 1;
    Py_DECREF(tmp);   /* destroys the old iterator */

Which means that after doing one cycle the list is destroyed.

Anyway if you need access to this list, just reference it somewhere before calling itertools.cycle.

like image 148
Bakuriu Avatar answered Sep 18 '22 22:09

Bakuriu