David
David

Reputation: 131

Can I make my class play nice with the Python '"in" keyword?

I'm heading into hour 5 or so of my experience with Python, and so far I've been pretty impressed with what it can do. My current endeavor is to make a short attempt at a Stream class, the code for which follows:

class Stream:

    """A Basic class implementing the stream abstraction. """

    def __init__(self,data,funct):
        self.current = data
        self._f = funct

    def stream_first(self):
        """Returns the first element of the stream"""
        return self.current

    def stream_pop(self):
        """Removes and returns the first element of the stream. """
        temp = self.current
        self.current = self._f(self.current)
        return temp

Enjoying modest success there, I tried to make a BoundedStream class which behaves basically like the unbounded one, except that at a certain point it runs out of elements. My question now is, seeing that any such Bounded Stream has some finite number of elements, it should be possible to iterate over them. If I were using an explicit list, I could use Python's in keyword and a for loop to do this cleanly. I would like to preserve that cleanliness for my own class. Is there some method I could implement, or any other language feature, that would allow me to do this? Any answers or other help you might provide to a rookie would be greatly appreciated!

-David

P.S.

For those who want to know, the impetus for a Bounded Stream is that I tried the builtin range function, but Python claimed that the range I wanted to look at was too large. I have turned to Streams in the interest of memory efficiency.

Upvotes: 8

Views: 4541

Answers (4)

Kos
Kos

Reputation: 72241

For iterating as in for x in object, you need to provide an __iter__ method which will return a new iterator.

An iterator is an object which has a method next() (Python 2) or __next__ (Python 3) which either returns the next element or raises StopIteration exception when there are no more elements. (An iterator should also have a method __iter__ that returns itself.)

Tip: You don't often need to code an iterator by yourself; the simplest way to implement __iter__ on a class is to make it a generator function or to return a generator expression from it (all generators are iterators). Also you can just return an iterator obtained from another object; the iter builtin is helpful here.


For testing if x in object, you need to provide a __contains__ method.

Further reading: Python data model - emulating container types

Upvotes: 15

John La Rooy
John La Rooy

Reputation: 304147

To turn your class into an iterable, you need to add this method

def __iter__(self):
    while True:
        yield self.stream_pop()

Note that this doesn't require the stream to be bounded. It will work fine for unbounded streams too

Upvotes: 2

Erik Youngren
Erik Youngren

Reputation: 811

As an addendum to @Kos's answer, the Python Data model has more functions that allow you to emulate other types, including the documentation for the __contains__ and __iter__ methods mentioned.

Upvotes: 3

Mark Byers
Mark Byers

Reputation: 838186

I tried the builtin range function, but Python claimed that the range I wanted to look at was too large.

Try xrange instead. This requires O(1) memory because it doesn't actually create the list of numbers, it just creates an object that behaves like a list of numbers. In other words, what you are trying to do is already available.

>>> 5 in range(10**9)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
MemoryError
>>> 5 in xrange(10**9)
True

Notes

  • In Python 3 this isn't even an issue - xrange has been removed and range now does what xrange did in Python 2.x.)
  • xrange does allows you to create very large ranges, but not arbitrarily large unfortunately. There is still a limit.

Upvotes: 4

Related Questions