How to make a class object iterable - python

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?

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)

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]

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])

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
>>>

Related

Can a class instance return tuple without using attribute?

I've got a small problem which in itself isn't a big deal but is bugging me none the less. Consider the code below.
import pygame
class vector:
def __init__(s, x, y):
s.x = int(x)
s.y = int(y)
s.t = (s.x, s.y)
def __add__(s, n):
return vector(s.x + n[0], s.y + n[1])
size = vector(10, 10)
pygame.Surface((size + (5, 5)).t)
When passing vector as a parameter to pygame.Surface I have to use the 't' attribute to access the tuple stored in 'size'.
Is there a way to have it simply like this?
pygame.Surface(size + (5, 5))
Is there a magic method that returns a value if no attribute or method is referenced?
In pygame you can define a vector like this, v = pygame.math.vector2(10, 10) and then pass that vector to a surfaces blit function like this, someSurface.blit(sprite, v) and there's no need to reference a method or attribute to get the value so it does seem possible, though I guess it's perfectly plausible that the blit function can handle a vector2 as an argument?
Thank you for you time.
Implement __len__ and __getitem__:
class vector:
def __init__(s, x, y):
s.x = int(x)
s.y = int(y)
s.t = (s.x, s.y)
def __add__(s, n):
return vector(s.x + n[0], s.y + n[1])
def __len__(s):
return 2
def __getitem__(s, i):
return [s.x, s.y][i]

Is there a magic method for sets in Python?

I have a custom object like this:
class MyObject:
def __init__(self, x, y):
self.x = x
self.y = y
I want it to work with sets according to the rule: if objects have the same x they are equal.
s = set()
s.add(MyObject(1, 2))
print(MyObject(1, 3) in s) # It is False. I want it to be True, because `x = 1` for both.
Is there a magic method I could implement in MyObject to reach my purpose?
__eq__(self, other)
__hash__(self)
See https://hynek.me/articles/hashes-and-equality/ for more

Python Set Intersection on Object Attributes

I have 2 sets, each containing objects of the following class:
class pointInfo:
x = 0
y = 0
steps = 0
I want to find the intersection of the two sets, but only on the x and y values.
something like:
def findIntersections(pointInfoSet1, pointInfoSetwire2):
return [pointInfo for pointInfo in pointInfoSet1 if pointInfo.x, pointInfo.y in pointInfoSet2]
I know if you have 2 sets in python you can just do set1.intersection(set2), but that wont work in this case because I just want to find where a certain subset of the object attributes are the same, not identical objects. Thanks in advance!
Here's a solution that makes Point objects that are Hashable on their x and y attributes:
class Point:
def __init__(self, x=0, y=0, step=0):
self.x = x
self.y = y
self.step = step
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y))
def __repr__(self):
return "Point(x={0.x}, y={0.y}, step={0.step})".format(self)
We can then put them in sets and get intersections naturally:
{Point(1, 1, 1), Point(2, 2)} & {Point(1, 1, 2)}
# {Point(x=1, y=1, step=2)}
Assuming no wire crosses itself, a better solution might be to iterate through both wires, maintaining a dict of points to steps, and outputting the sum of the steps at each intersection:
from itertools import chain
def stepsToIntersections(wire1, wire2):
seen = {}
for point in chain(wire1, wire2):
if point in seen:
yield point.step + seen[point]
else:
seen[point] = point.step
closest = min(stepsToIntersections(wire1, wire2))

Creating a list of references to object attributes

The problem in the code is that Vector.numerical_vector is a list of copies of floats, but I need it to be list of references, so when Vector.vector[n].value is changed, Vector.numerical_vector[n] is changed to the same value as well.
Thanks for help in advance!
class Var:
def __init__(self, val):
self.value = float(val)
def __float__(self):
return self.value
class Vector:
def __init__(self, vector):
self.vector = vector
self.numerical_vector = [float(x) for x in vector]
vars = [Var(i) for i in range(5)]
vector = Vector(vars)
There's no way to do that in python. What you could do is make numerical_vector an object that, when accessed, returns the float value of the corresponding vector item. Eg
class VectorWrapper:
def __init__(self, vector):
self.vector = vector
def __getitem__(self, i):
return float(self.vector[i])
and set self.numerical_vector = VectorWrapper(self.vector)
If you have to return a float representation when self.numerical_vector[i] is referenced,
You might want to employ a property for that.
class Vector:
def __init__(self, vector):
self.vector = vector
#property
def numerical_vector(self):
return [x.value for x in self.vector]
Now vector.numerical_vector will always be synchronized. A drawback of this method is that it is recomputed each time you access it, some it may not be adapted to your specific application.
If performance is an issue, you could add __getitem__ and __setitem__ methods to Vector as follows:
class Vector:
def __init__(self, vector):
self.vector = vector
self.numerical_vector = [float(x) for x in vector]
def __getitem__(self, i):
return self.vector[i]
def __setitem__(self, i, val):
self.vector[i] = val
self.numerical_vector[i] = float(val)
In that case, if you set vector_instance[i], then vector_instance.numerical_vector[i] will be also modified. But if you want to modify vector_instance.vector[i] directly, then synchronicity will be broken.
Depending on the use case, you can use either of the two approaches. Both have limitations but I don't think much more can be done.

Sorting Objects based on properties

I am trying to sort the Points based on the X and Y properties of the point object.A small example below to explain my process:
class Point:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __repr__(self):
return '[{},{},{}]'.format(self.x, self.y, self.z)
#point instances
p1,p2,p3 = Point(7,85,5), Point(56,16,20), Point(24,3,30)
point_list = [p1,p2,p3]
def get_X(point):
return point.x
def get_Y(point):
return point.y
sorted_points = sorted(point_list, key = get_X)
# print(sorted_points) // [[7,85,5], [24,3,30], [56,16,20]]
sorted_points = sorted(sorted(point_list, key = get_X), key = get_Y)
# print(sorted_points) // [[24,3,30], [56,16,20], [7,85,5]]
But I need an output like this sorting X first keep them in same order and then sort Y
[[7,3,5], [24,16,30], [56,85,20]]
I think I am trying to exchange the properties of each instances by achieving the above, But I don't know how to do that.
Tuples will naturally sort in the way you want. You can simplify things by adding a __lt__() function to your class. Sorted will use this function to compare. Then you can depend on the natural sorting order of tuples easily with something like this:
class Point:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __repr__(self):
return '[{},{},{}]'.format(self.x, self.y, self.z)
def __iter__(self):
return iter((self.x, self.y, self.z))
def __lt__(self, other):
return (self.x, self.y, self.z) < (other.x, other.y, other.z)
#point instances
point_list = [Point(7,85,5), Point(56,16,20), Point(24,3,30), Point(7, 20, 0), Point(56,16,15)]
sorted(point_list)
# --> [[7,20,0], [7,85,5], [24,3,30], [56,16,15], [56,16,20]]
EDIT: Create new points
To create new points by combining the sorted elements of each point individually you can unzip your points, sort them and then zip them again. The nicest way to do this is to add an __iter__() function to your class to make it iterable so it can support zip. I've done this in the code above. This will allow you to do this:
point_list = [Point(7,85,5), Point(56,16,20), Point(24,3,30), Point(7, 20, 0), Point(56,16,15)]
newTuples = list(zip(*[sorted(l) for l in zip(*point_list)]))
sortedPoints = [Point(*p) for p in newTuples ]
#sortedPoint => [[7,3,0], [7,16,5], [24,16,15], [56,20,20], [56,85,30]]
This also sorts the z values, but it's easy enough to change that if you need it for some reason.
By placing the key elements in tuples in the order you want them sorted (primary value first, secondary value second), the ordering method of tuples will automatically perform in the way you are hoping to achieve.
All you need to change is that your value for key should be set to key=get_XY, where get_XY returns a tuple of x and y coordinates:
def get_XY(point):
return point.x, point.y

Categories