__setstate__, a generic __getstate__, listiter.__setstate__, (typed) arrays
and memoryviews

```python
import collections.abc
from array import array

list_ = [0, 10, 20]
assert [list_[i] for i in range(len(list_))] == [0, 10, 20]

iterator = iter(list_)
assert [next(iterator) for n in range(2)] == [0, 10]


iterator = iter(list_)
assert iterator.__reduce__() == (iter, (list_,), 0)
assert next(iterator) == 0
assert iterator.__reduce__() == (iter, (list_,), 1)
assert next(iterator) == 10
assert iterator.__reduce__() == (iter, (list_,), 2)
iterator.__setstate__(0)
assert iterator.__reduce__() == (iter, (list_,), 0)
assert next(iterator) == 0
assert next(iterator) == 10
assert next(iterator) == 20
assert iterator.__reduce__() == (iter, (list_,), 3)
assert iterator.__reduce__() == (iter, (list_,), len(list_))
try:
    next(iterator)
except StopIteration:
    pass
assert iterator.__reduce__() == (iter, ([],))
iterator.__setstate__(1)
try:
    assert next(iterator) == 10
except StopIteration:
    pass
iterator = iter(list_)
iterator.__setstate__(1)
assert next(iterator) == 10
assert iterator.__reduce__() == (iter, (list_,), 2)


try:
    [1, 2, 3].__reduce__()
    [1, 2, 3].__reduce_ex__(0)
    [1, 2, 3].__reduce_ex__(1)
except TypeError as e:
    assert e.args[0] == "can't pickle list objects"
[1, 2, 3].__reduce_ex__(2)


def __getstate__(obj):
    if (not isinstance(obj, collections.abc.Iterable)
            or isinstance(obj, list)):
        raise TypeError('__getstate__ only works with iterables', type(obj))
    reducefunc = getattr(obj, '__reduce__ex__', False)
    reduceoutput = reducefunc(2) if reducefunc else obj.__reduce__()
    if len(reduceoutput) < 3:
        raise StopIteration  # ?
    return reduceoutput[2]


iterator = iter(list_)
assert __getstate__(iterator) == 0
next(iterator)
assert __getstate__(iterator) == 1
next(iterator)
assert __getstate__(iterator) == 2
next(iterator)
assert __getstate__(iterator) == 3
try:
    next(iterator)
except StopIteration:
    pass
try:
    __getstate__(iterator)
except StopIteration:
    pass

iterator = iter(list_)
assert __getstate__(iterator) == 0
assert next(iterator) == 0
assert __getstate__(iterator) == 1
iterator.__setstate__(0)
assert __getstate__(iterator) == 0
assert next(iterator) == 0
assert __getstate__(iterator) == 1

try:
    __getstate__([1, 2, 3])
except TypeError as e:
    assert e.args[0] == "__getstate__ only works with iterables"
    assert e.args[1] == list, e.args[1]  # list_iterator; type(iter(list()))
    pass


# arrays must be typed;
# otherwise random access isn't possible
# because skipping ahead or back by n*size requires n calls to
size(array[n])
list_ary = array('i', list_)
iterator = iter(list_ary)
assert [next(iterator) for n in range(2)] == [0, 10]

ary_memoryview = memoryview(list_ary)
iterator = iter(ary_memoryview)
assert [next(iterator) for n in range(2)] == [0, 10]

assert ary_memoryview.obj == list_ary
assert ary_memoryview.tolist() == list_

assert ary_memoryview[1] == 10
ary_memoryview[1] = 100
assert ary_memoryview[1] == 100
assert list_ary[1] == 100
assert ary_memoryview[:2].tolist() == [0, 100]
list_ary[1] = 1000
assert ary_memoryview[1] == 1000
assert ary_memoryview[:2].tolist() == [0, 1000]


ary_memoryview.release()
try:
    ary_memoryview[:2].tolist()
except ValueError as e:
    assert e.args[0] == "operation forbidden on released memoryview object"


list_ = [0, 10, 20]
iterable = iter(list_)
assert next(iterable) == 0
list_.insert(1, 5)
assert next(iterable) == 5
```

- https://docs.python.org/3/library/pickle.html#object.__setstate__

- listiter_setstate:

https://github.com/python/cpython/blob/v3.10.0a1/Objects/listobject.c#L3215-L3229
:

```c
static PyObject *
listiter_setstate(listiterobject *it, PyObject *state)
{
    Py_ssize_t index = PyLong_AsSsize_t(state);
    if (index == -1 && PyErr_Occurred())
        return NULL;
    if (it->it_seq != NULL) {
        if (index < 0)
            index = 0;
        else if (index > PyList_GET_SIZE(it->it_seq))
            index = PyList_GET_SIZE(it->it_seq); /* iterator exhausted */
        it->it_index = index;
    }
    Py_RETURN_NONE;
}
```c


On Wed, Oct 7, 2020 at 11:32 AM Guido van Rossum <gu...@python.org> wrote:

> On Wed, Oct 7, 2020 at 2:13 AM Steven D'Aprano <st...@pearwood.info>
> wrote:
>
>> [about `__setstate__`]
>> (Aside: I'm actually rather surprised that it's exposed as a dunder.)
>>
>
> It's used for pickling. Someone long ago must have complained that list
> iterators weren't picklable, and we complied.
>
> I'm not sure that either len or next are good precedents? As far as I
>> can tell, len() does not call `__length_hint__`, and next() only
>> dispatches to `__next__`.
>>
>
> I just meant that these are examples of a common pattern in Python, of a
> *function* wrapping a dunder *method*. Your example (`in` -> `__contains__`
> with a fallback if that doesn't exist) is better because it shows that a
> fallback is a known pattern; but it isn't exactly a function.
>
> As for the buffering issue, sure, that's a point against those
>> proposals, but itertools provides a tee function that buffers the
>> iterator. So "needs a buffer" is not necessarily a knock-down objection
>> to these features, even for the std lib.
>>
>
> Well, the buffering requires forethought (you can't call go back unless
> you had the forethought to set up a buffer first) and consumes memory
> (which iterators are meant to avoid) so the argument against these is much
> stronger, and different from the argument against advance() -- the latter's
> presence costs nothing unless you call it.
>
>
>> What's the interface? Is this a skip ahead by N steps, or skip directly
>> to state N? I can imagine uses for both.
>>
>
> Not all iterators remember how often next() was called, so "skip to state
> N" is not a reasonable API. The only reasonable thing advance(N) can
> promise is to be equivalent to calling next() N times.
>
>
>> Can we skip backwards if the underlying list supports it?
>>
>
> We shouldn't allow this, since it wouldn't work if the input iterator was
> changed from a list iterator to e.g. a generator.
>
>
>> `listiter.__setstate__` supports the second interface. There's no
>> getstate dunder that I can see. Should there be?
>>
>
> It's called `__reduce__`. These are used for pickling and the state they
> pass around is supposed to be opaque.
>
>
>> Here's a cautionary tale to suggest some caution. [...]
>>
>
> I guess the worst that could happen in our case is that some class used to
> be implemented on top of a list and at some point changed to a linked list,
> and the performance of advance(N) changed from O(1) to O(N). But that's not
> going to happen to Python's fundamental data types (list, tuple, bytes,
> str, array), since (for better or for worse) they have many other aspects
> of their API (notably indexing and slicing) that would change from O(1) to
> O(N) if the implementation changed to something other than an array.
>
> I'm not arguing against this proposal, or for it. I'm just mentioning
>> some considerations which should be considered :-)
>>
>
> Same here, for sure. Still waiting for that real-world use case... :-)
>
> --
> --Guido van Rossum (python.org/~guido)
> *Pronouns: he/him **(why is my pronoun here?)*
> <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
> _______________________________________________
> Python-ideas mailing list -- python-ideas@python.org
> To unsubscribe send an email to python-ideas-le...@python.org
> https://mail.python.org/mailman3/lists/python-ideas.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-ideas@python.org/message/3H2XFZWNQ5DSZKFX6S3MP7MAJG7KMEX4/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/ZMHJJXTMQAF6ST5NBXMBAU43X6XUU4K2/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to