95 lines
3.2 KiB
ReStructuredText
95 lines
3.2 KiB
ReStructuredText
.. _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.
|