Reputation: 217
I have the following point class:
import math
class Point:
"""Two-Dimensional Point(x, y)"""
def __init__(self, x=0, y=0):
# Initialize the Point instance
self.x = x
self.y = y
# def __iter__(self):
# return iter(int(self.x))
# return iter(int(self.y))
return self
@property
def magnitude(self):
# """Return the magnitude of vector from (0,0) to self."""
return math.sqrt(self.x ** 2 + self.y ** 2)
def distance(self, self2):
return math.sqrt((self2.x - self.x) ** 2 + (self2.y - self.y) ** 2)
def __str__(self):
return 'Point at ({}, {})'.format(self.x, self.y)
def __repr__(self):
return "Point(x={},y={})".format(self.x, self.y)
I want to make 'points' iterable so that the following is possible:
point = Point(2, 3)
x, y = point
print(x)
2
print(y)
3
If you see my commented code, that is what I attempted but it says TypeError: iter() returned non-iterator of type 'Point'. Any ideas on how to correctly do this?
Upvotes: 2
Views: 1278
Reputation: 366083
tl;dr:
def __iter__(self):
yield self.x
yield self.y
To make something iterable, you need your __iter__
method to return an iterator.
You can't just return self
, unless self
is already an iterator (which means it has a __next__
method). But you don't want points
to be an iterator, you want it to be iterable over and over.
You can't return iter(int(self.x))
then return iter(int(self.y))
, for two reasons. First, after you return
, your function is done; any code that happens after that never gets run. Second, you can't call iter
on an int
. (Also, there's no reason to call int
on an int
.)
You could fix that last set of problems by creating an iterable out of each int
, like a single-element list, iterating that iterable, and then delegating to it with yield from
instead of return
. Using yield
or yield from
in a function makes it a generator function, and the generators created by generated functions are iterators:
yield from iter([self.x])
yield from iter([self.y])
… but this is kind of silly. Once we know we want a generator, we can just yield
the values we want to be iterated. Hence the code at the top.
Alternatively, you could explicitly create a single-element iterables for both elements and chain them together and return that:
def __iter__(self):
return itertools.chain(iter([self.x]), iter([self.y]))
But that's also silly. Chaining two iterators over single-element lists is the same as just iterating a two-element list:
def __iter__(self):
return iter([self.x, self.y])
Finally, rather than relying on a generator, list iterator, chain
, or other iterator type that comes with Python, you can always write one explicitly:
class PointIterator:
def __init__(self, point):
self.values = [point.x, point.y]
def __next__(self):
try:
return self.values.pop(0)
except IndexError:
raise StopIteration
def __iter__(self):
return self
class Point:
# ... other code
def __iter__(self):
return PointIterator(self)
Upvotes: 7
Reputation: 71471
You can use yield
:
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __iter__(self):
yield self.x
yield self.y
a, b = Point(4, 5)
print([a, b])
Output:
[4, 5]
Upvotes: 2
Reputation: 12025
Your __iter__
method should yield x and y values
def __iter__(self):
yield self.x
yield self.y
With this, you get the following output
>>> point = Point(2, 3)
>>> x, y = point
>>> print (x)
2
>>> print (y)
3
>>>
Upvotes: 1
Reputation: 29099
You should implement a special __iter__(self)
method, which should return an iterator. Add something like
def __iter__(self):
return iter([self.x, self.y])
Upvotes: 1