How to remove an object by value from a set - python

I have a set of Point objects, and I would like to be able to remove these objects from my set. However, Python seems to be comparing these by pointer rather than by value, so I can't remove elements easily, since they don't have the same pointer because they are not the exact same object. This is a problem only with objects, not with primitives.
A simplified example of my problem:
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
a = set()
a.add(Point(0, 0))
b = Point(0, 0)
a.remove(b)
When run, this returns
Traceback (most recent call last):
File "example.py", line 9, in <module>
a.remove(b)
KeyError: <__main__.Point object at 0x7f6292376128>
(obviously, the specific pointer changes on each run).
I would prefer to have the element (0, 0) removed from a, leaving a to be the empty set.

If you tell Python how to compare these objects, this can work. Add two methods like:
Code:
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
Test Code:
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
a = set()
a.add(Point(0, 0))
b = Point(0, 0)
a.remove(b)

It does not appear that you have actually added Point b to the set 'a'.
According to the docs:
If the element(argument) passed to the remove() method doesn't exist, keyError exception is thrown.
I made this slight alteration and it didn't error out
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
a = set()
a.add(Point(0, 0))
b = Point(0, 0)
a.add(b)
a.remove(b)

Related

Made a class of Point in python but I'm not quite sure what i did wrong

#
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, x, y):
return self.x + self.y
def __sub__(self, x, y):
return self.x - self.y
p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2
print(p3)
OUTPUT:
I get an an output which is looking for the parameter of y but i think i already pass y to it
Traceback (most recent call last):
File "C:##################################", line 18, in <module>
p3 = p1 + p2
TypeError: __add__() missing 1 required positional argument: 'y'
The expression p3 = p1 + p2 is in reality executed as:
p3 = p1.__add__(p2)
With your current function signature __add__(self, x, y), the Python interpreter will only receive self (p1), x (p2), but will be missing one argument (y), hence the error:
__add__() missing 1 required positional argument: 'y'
What you need instead, is an __add__ implementation (same goes for sub) which takes self and another point instance as arguments, and returns a new point:
class Point(object):
def __add__(self, other: "Point") -> "Point":
# returns a new point created by adding coordinates
# from self and the other point
You need to indent the functions belonging to the class. Currently, Python thinks __add__ function has nothing to do with Point and has two arguments self and y. If it was indented, self would automatically be set to a reference to the instance.
The second issue is, in built-in magic methods, you only pass a reference to the other object explicitly and access the attributes of the calling object via self by the automatic referencing I mentioned.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)

Python Classes using __init and __repr__ methods

Iam trying to create a class that should accept two arguments, x and y, and be represented by a string 'Point(x, y)' with appropriate values for x and y
class Point(object):
def __init__(self, x, y):
self.x = 0
self.y = 0
def __repr__(self):
return "Point(%s,%s)"%(self.x, self.y)
Error:
Point(0,0) is not of type 'string'
Failed validating 'type' in schema['items']:
{'type': 'string'}
On instance[0]:
Point(0,0)
"self.x" is the value of the instance of your class. So, if you set "self.x = 0", it means whenever you create an object for that class, the "x" value of that object will always be 0 instead of what you pass in the parameter.
"x" is the value of what you pass in the parameter.
self.x = x
self.y = y
Code:
class MyClass():
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return "Point(%s,%s)"%(self.x, self.y)
thiss = MyClass(0, 0)
print(thiss.__repr__())
thiss = MyClass(20, 20)
print(thiss.__repr__())
Output:
daudn$ python3 point.py
Point(20,20)
daudn$ python3 point.py
Point(0,0)
When declaring your init function, you initialized self.x and self.y to always be 0. If you look at the code I've posted, whatever number you pass to the class will become the values or Point.

special method for an object to override tuple expansion?

I will provide an example of the problem in question, in case the title was not clear enough.
Let's say that I have a class Point(object) that represent 2d coordinates.
Is it possible to create a "magic" method that will allow the following?
x, y = point
Maybe some hacks with iterators?
you can simply tap into the iterator protocol of the object and accomplish this
class Point(object):
def __init__(self, x,y):
self.x = x
self.y = y
self.points = (x,y)
def __iter__(self):
return iter(self.points)
p = Point(1,5)
x,y = p
print x,y
# 1,5
take a look at http://www.rafekettler.com/magicmethods.html#sequence on more information on how a custom object can be converted into an iterable; or more precisely how one would use an object like an iterable.
Just provide an __iter__ method.
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __iter__(self):
yield self.x
yield self.y
p = Point(1, 2)
x, y = p
assert (1, 2) == (x, y)
Be careful though. This means your class suddenly becomes safe to use in many other places where it might have previously thrown a type error.
eg.
def add_1(x):
return x + 1
l = list(map(add_1, p)) # works, because the point is iterable
Ergo, you may want to provide a method other than __iter__ that provides the iterator.
eg.
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
def coords(self):
yield self.x
yield self.y
p = Point(1, 2)
x, y = p.coords()
assert (1, 2) == (x, y)

Method argument conversion: 'tuple' has no attribute 'attribute'

I have a simple vector class that overloards several arithmetic operators:
class vec2:
x = 0.0
y = 0.0
def __add__(self,other):
self.x = other.x
self.y = other.y
def __mul__(self,scalar):
self.x *= scalar
self.y *= scalar
However, somewhere else I call the method like this:
class foo:
position = vec2()
velocity = vec2()
def update(self,dt):
self.position += self.velocity * dt;
However, once I get to the update function, the interpreter gives an error:
'tuple' object has no attribute 'x'
inside the __add__ function.
Why is "other" in __add__ passed as a tuple, and not a vec2?
The entire code is here.
Return new vectors when using __add__ and __mul__, and handle 'strange' types:
class vec2:
x = 0.0
y = 0.0
def __init__(self, x=0.0, y=0.0):
self.x, self.y = x, y
def __add__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
result = self.__class__(self.x, self.y)
result.x += other.x
result.y += other.y
return result
def __iadd__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
self.x += other.x
self.y += other.y
return self
def __mul__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
result = self.__class__(self.x, self.y)
result.x *= other.x
result.y *= other.y
return result
def __imul__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
self.x *= other.x
self.y *= other.y
return self
To modify the vectors in-place, use __iadd__ and __imul__; these still need to return the new value; this can be self.
Note that this does not handle just passing in a tuple of (x, y) coordinates. If you want to support that usecase, you need to specially handle it:
class foo:
def __init__(self, position=(0.0, 0.0), velocity=(1.0, 1.0)):
self.position = vec2()
self.velocity = vec2(*velocity)
def update(self, dt):
if isinstance(dt, tuple):
dt = vec2(*dt)
self.position += self.velocity * dt;
Note also that you should not really use class attributes for your position and velocity values; I've used instance attributes instead above, and took the opportunity to set both position and velocity to sane values.
Demo:
>>> f = foo()
>>> f.position.x, f.position.y
(0.0, 0.0)
>>> f.update((1, 2))
>>> f.position.x, f.position.y
(1.0, 2.0)

Attribute error: Rich comparison configuration

I have a class Point with xand y attributes. I'd like to get False comparing a Point object with any other type of object. For instance, Point(0, 1) == None fails:
AttributeError: 'NoneType' object has no attribute 'x'
The class:
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __ne__(self, other):
return not self.__eq__(other)
How do I configure __eq__to get False in comparison with any other object type?
I would check to see whether the other object acts like a Point object instead of rejecting all non-Point objects:
def __eq__(self, other):
try:
return self.x == other.x and self.y == other.y
except AttributeError:
return False
That way Point(1, 1) == Vector(1, 1), in case you use coordinate vectors.
def __eq__(self, other):
if not isinstance(other, Point):
return False
try:
return self.x == other.x and self.y == other.y
except AttributeError:
return False
First check the type and return False if its not a Point instance. We do this in case they are comparing some other type that happens to have an x or y attribute but isn't necessarily the same context.
Second catch an attribute error, just in case someone subclasses Point and removes the attribute or changes Point in some way.
Try this:
def __eq__(self, other):
return isinstance(other, Point) and self.x == other.x and self.y == other.y

Categories