File: custom_iterators.rst

package info (click to toggle)
python-future 0.18.2-6
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 4,264 kB
  • sloc: python: 43,246; makefile: 136; sh: 29
file content (94 lines) | stat: -rw-r--r-- 3,318 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
.. _custom-iterators:

Custom iterators
----------------

If you define your own iterators, there is an incompatibility in the method name
to retrieve the next item across Py3 and Py2. On Python 3 it is ``__next__``,
whereas on Python 2 it is ``next``.

The most elegant solution to this is to derive your custom iterator class from
``builtins.object`` and define a ``__next__`` method as you normally
would on Python 3. On Python 2, ``object`` then refers to the
``future.types.newobject`` base class, which provides a fallback ``next``
method that calls your ``__next__``. Use it as follows::

    from builtins import object

    class Upper(object):
        def __init__(self, iterable):
            self._iter = iter(iterable)
        def __next__(self):                 # Py3-style iterator interface
            return next(self._iter).upper()
        def __iter__(self):
            return self

    itr = Upper('hello')
    assert next(itr) == 'H'
    assert next(itr) == 'E'
    assert list(itr) == list('LLO')


You can use this approach unless you are defining a custom iterator as a
subclass of a base class defined elsewhere that does not derive from
``newobject``.  In that case, you can provide compatibility across
Python 2 and Python 3 using the ``next`` function from ``future.builtins``::

    from builtins import next

    from some_module import some_base_class

    class Upper2(some_base_class):
        def __init__(self, iterable):
            self._iter = iter(iterable)
        def __next__(self):                 # Py3-style iterator interface
            return next(self._iter).upper()
        def __iter__(self):
            return self

    itr2 = Upper2('hello')
    assert next(itr2) == 'H'
    assert next(itr2) == 'E'

``next()`` also works with regular Python 2 iterators with a ``.next`` method::

    itr3 = iter(['one', 'three', 'five'])
    assert 'next' in dir(itr3)
    assert next(itr3) == 'one'

This approach is feasible whenever your code calls the ``next()`` function
explicitly. If you consume the iterator implicitly in a ``for`` loop or
``list()`` call or by some other means, the ``future.builtins.next`` function
will not help; the third assertion below would fail on Python 2::

    itr2 = Upper2('hello')

    assert next(itr2) == 'H'
    assert next(itr2) == 'E'
    assert list(itr2) == list('LLO')      # fails because Py2 implicitly looks
                                          # for a ``next`` method.

Instead, you can use a decorator called ``implements_iterator`` from
``future.utils`` to allow Py3-style iterators to work identically on Py2, even
if they don't inherit from ``future.builtins.object``. Use it as follows::

    from future.utils import implements_iterator

    Upper2 = implements_iterator(Upper2)

    print(list(Upper2('hello')))
    # prints ['H', 'E', 'L', 'L', 'O']

This can of course also be used with the ``@`` decorator syntax when defining
the iterator as follows::

    @implements_iterator
    class Upper2(some_base_class):
        def __init__(self, iterable):
            self._iter = iter(iterable)
        def __next__(self):                 # note the Py3 interface
            return next(self._iter).upper()
        def __iter__(self):
            return self

On Python 3, as usual, this decorator does nothing.