Class that implements custom max function - python

I have two Point objects and the code looks like this:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
a = Point(1, 3)
b = Point(4, 2)
max(a, b) # Make this output Point(4, 3)
My question is: "How can I implement a custom max function for the Point class that will return Point(max(self.x, other.x), max(self.y, other.y))?" The max function seems to just look at the __lt__ and return the highest.

max() can't do this, it can only return one of the elements given as input, not produce new instances.
You need to implement your own function:
def max_xy_point(*points):
if not points:
raise ValueError("Need at least 2 points to compare")
if len(points) == 1:
points = points[0]
return Point(
max(p.x for p in points),
max(p.y for p in points)
)
Like the built-in max() function, this can take either a single sequence (max([p1, p2, p3, ...]) or separate arguments (max(p1, p2, p3, ...)).

max(a, b) can return only a or b - it can't create point with new values.
You may add own method to class and use
c = a.max(b)
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def max(self, other):
return Point(max(self.x, other.x), max(self.y, other.y))
a = Point(1, 3)
b = Point(4, 2)
c = a.max(b)
print(c.x, c.y)

You can go about it like this, to get desired output:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def max(self, other):
if not isinstance(other, Point):
return NotImplemented
return Point(max(self.x, other.x), max(self.y, other.y))
def __repr__(self):
return f'Point{self.x, self.y}'
a = Point(1, 3)
b = Point(4, 2)
a.max(b)
# Point(4, 3)

Related

How to make Python class constructor return an already existing object (pointing to the same object)?

I'm writing a class called Position, something like Python's Small Integer Constant Pool:
class Position:
"""Takes 2 args: x, and y. If coordinate are equal, make them pointing to the same object"""
def __init__(self, x, y):
self.x = x
self.y = y
def __new__(self, *args, **kwargs):
import gc
for obj in gc.get_objects():
if isinstance(obj, eval(self.__qualname__)): # Won't work cause arg 2 is a string, not a type name
if obj.x == self.x and obj.y == self.y:
return obj
return super(Position, self).__new__(self, *args, **kwargs) # Have no idea what to write
p1 = Position(1, 2)
p2 = Position(2, 3)
p3 = Position(1, 2)
print(p1 is p2, p2 is p3, p1 is p3) # Should print False, False, True
Rewriting hash and eq won't work when using is to compare.
I want my class to be something like:
a = 1
b = 1
print(a is b) # True
You can't rely on the garbage collector list of all existing objects for this, and much less on a linear search of all objects.
Simply keep your objects in a registry where you can find then back.
You can use WeakValues dictionary so that if there is no reference left to anyone of your objects it is discarded. (Just use a plain dictionary if you want each one to remain created, even if it is no longer in use):
from weakref import WeakValueDictionary
class Position:
"""Takes 2 args: x, and y. If coordinate are equal, make them pointing to the same object"""
_registry = WeakValueDictionary()
def __new__(cls, x, y):
if (x,y) in cls._registry:
return cls._registry[x,y]
instance = super().__new__(cls) # don't pass extra *args and **kwargs to obj.__new__
cls._registry[x,y] = instance
return instance
def __init__(self, x, y):
if hasattr(self, "x"): # avoid running init twice if the attribute is already set
return
self.x = x
self.y = y
p1 = Position(1, 2)
p2 = Position(2, 3)
p3 = Position(1, 2)
in this scenario p1 == p3 evaluates to True

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)

Setting the property of a property

I am trying to figure out a way of having the setter of a property of a property trigger some action on the top level class.
As a dummy example, lets say my top level class is a Segment. All is good if I store the coordinates of its endpoints directly as properties of this object x0, y0, x1 and y1, and have the setters of each one trigger the selected action.
But if I want to group them into two Point members as properties p0 and p1, each with properties x and y, whenever one of these coordinates is modified, there is no obvious way of telling the Segment to do something. This is what I would like to be able to do:
>>> segment = Segment(Point(0, 0), Point(3, 3))
>>> segment.p0
Point(0, 0)
>>> segment.p0.x
0
>>> segment.p1.y = 4
Length of segment changed to 5.0! # This can only be printed by segment, not p1!
The problem is that the line segment.p1.y = 4 first calls the getter of p1 on the segment instance, and then the setter of y on the return of the previous call, at which point there is no simple way of letting the segment instance know that a change has been made.
The best I can think of right now is something along the lines of the following:
class Point(object):
def __init__(self, x, y, parent=None, name=None):
self.parent, self.name = parent, name
self._x, self._y = x, y
#property
def x(self):
return self._x
#x.setter
def x(self, value):
self._x = value
if parent is not None:
setattr(self.parent, self.name, self)
# Similar code for y goes here...
class Segment(object):
def __init__(self, p0, p1):
self.p0, self.p1 = p0, p1
#property
def p0(self):
return self._p0
#p0.setter
def p0(self, point):
self._p0 = point
self.p0.parent = self
self.p0.name = 'p0'
if not self._silent:
self.do_something() # This would print the length in the above example
# Similar code for p1 goes here...
While this does what I want it to, I don't quite like having to manually add that link back to the parent, nor how I would either have to make lots of redundant copies of the Point objects, or risk interesting bugs if doing something like:
p0, p1, p2 = Point(0, 0), Point(1, 1), Point(2, 2)
seg0 = Segment(p0, p1)
seg1 = Segment(p0, p2)
# The following line changes the value on both seg0 and seg1, but triggers
# the do_something call on seg1 only!
seg0.p0.x = 6
Is there some ready-made recipe for this? Anyone can come up with a better way of doing it?
Perhaps you are looking for the Observer design pattern:
import math
class Point(object):
def __init__(self, x, y, name=None):
self.name = name
self._x, self._y = x, y
self.observers = []
def observe(self, observer):
self.observers.append(observer)
def __repr__(self):
return 'Point({}, {})'.format(self.x, self.y)
#property
def x(self):
return self._x
#x.setter
def x(self, value):
self._x = value
for o in self.observers:
o.notify()
#property
def y(self):
return self._y
#y.setter
def y(self, value):
self._y = value
for o in self.observers:
o.notify()
class Segment(object):
def __init__(self, p0, p1):
self._p0, self._p1 = p0, p1
p0.observe(self)
p1.observe(self)
def __repr__(self):
return 'Segment({}, {})'.format(self.p0, self.p1)
def notify(self):
print('Length of {} changed to {}'.format(self, self.length()))
def length(self):
return math.sqrt((self.p0.x - self.p1.x)**2
+ (self.p0.y - self.p1.y)**2)
#property
def p0(self):
return self._p0
#p0.setter
def p0(self, point):
self._p0 = point
#property
def p1(self):
return self._p1
#p1.setter
def p1(self, point):
self._p1 = point
segment = Segment(Point(0, 0), Point(3, 3))
print(segment.p0)
# Point(0, 0)
print(segment.p0.x)
# 0
segment.p1.y = 4
yields
Length of Segment(Point(0, 0), Point(3, 4)) changed to 5.0
and
p0, p1, p2 = Point(0, 0), Point(1, 1), Point(2, 2)
seg0 = Segment(p0, p1)
seg1 = Segment(p0, p2)
seg0.p0.x = 6
yields
Length of Segment(Point(6, 0), Point(1, 1)) changed to 5.09901951359
Length of Segment(Point(6, 0), Point(2, 2)) changed to 4.472135955

How to make two objects have the same id in python?

If I have a class like below:
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
And have 2 objects:
a = Point(1,2)
b = Point(1,2)
How can I modify class Point to make id(a) == id(b)?
class Point(object):
__cache = {}
def __new__(cls, x, y):
if (x, y) in Point.__cache:
return Point.__cache[(x, y)]
else:
o = object.__new__(cls)
o.x = x
o.y = y
Point.__cache[(x, y)] = o
return o
>>> Point(1, 2)
<__main__.Point object at 0xb6f5d24c>
>>> id(Point(1, 2)) == id(Point(1,2))
True
When you need a really simple class like Point, always consider collections.namedtuple
from collections import namedtuple
def Point(x, y, _Point=namedtuple('Point', 'x y'), _cache={}):
return _cache.setdefault((x, y), _Point(x, y))
>>> Point(1, 2)
Point(x=1, y=2)
>>> id(Point(1, 2)) == id(Point(1, 2))
True
I used a function alongside namedtuple because it is simpler IMO but you can easily represent it as a class if needed:
class Point(namedtuple('Point', 'x y')):
__cache = {}
def __new__(cls, x, y):
return Point.__cache.setdefault((x, y),
super(cls, Point).__new__(cls, x, y))
As #PetrViktorin noted in his answer you should consider the use of a weakref.WeakValueDictionary so deleted instances of the class (doesn't work with namedtuple apparently) don't remain in memory since they remain referenced in the dictionary itself.
You need to have a global dictionary of objects, and get them through a factory function (or a custom __new__, see the other answers). Additionally, consider using a WeakValueDictionary so that you don't unnecessarily fill up memory with objects that are no longer needed.
from weakref import WeakValueDictionary
class _Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
# Cache of Point objects the program currently uses
_points = WeakValueDictionary()
def Point(x, y):
"""Create a Point object"""
# Note that this is a function (a "factory function")
# You can also override Point.__new__ instead
try:
return _points[x, y]
except KeyError:
_points[x, y] = point = _Point(x, y)
return point
if __name__ == '__main__':
# A basic demo
print Point(1, 2)
print id(Point(1, 2))
print Point(2, 3) == Point(2, 3)
pt_2_3 = Point(2, 3)
# The Point(1, 2) we created earlier is not needed any more.
# In current CPython, it will have been been garbage collected by now
# (but note that Python makes no guarantees about when objects are deleted)
# If we create a new Point(1, 2), it should get a different id
print id(Point(1, 2))
Note that a namedtuple won't work with WeakValueDictionary.
If you need to compare whether your two objects house the same values, you can implement the eq operator:
>>> class Point(object):
... 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
...
>>> a = Point(1,2)
>>> b = Point(1,2)
>>> a == b
True
>>> b = Point(2,2)
>>> a == b
False

Is it possible to instantiate an object of one class in two different ways?

Here is an example which creates a point as p=Point(x, y). Assume that I have some array ppp=(x, y) where x and y are numbers and I want to make it of class Point but in the way: p=Point(ppp). I can do either one or another way but not both simultaneously. Is it possible to have both ways?
There are two different ways to acquire the result, the first is to analyse arguments that you pass to __init__ and in dependence of their quantity and type - choose a decision what are you using to instantiate class.
class Point(object):
x = 0
y = 0
def __init__(self, x, y=None):
if y is None:
self.x, self.y = x, x
else:
self.x, self.y = x, y
The other decision is to use classmethods as instantiators:
class Point(object):
x = 0
y = 0
#classmethod
def from_coords(cls, x, y):
inst = cls()
inst.x = x
inst.y = y
return inst
#classmethod
def from_string(cls, x):
inst = cls()
inst.x, inst.y = x, x
return inst
p1 = Point.from_string('1.2 4.6')
p2 = Point.from_coords(1.2, 4.6)
If you know that you have a tuple/list while creating the instance, you can do: p = Point(*ppp), where ppp is the tuple.
class Point:
def __init__(self, x, y=None):
if isinstance(x, tuple):
self.x, self.y = x
else:
self.x = x
self.y = y
Yes:
class Point(object):
def __init__(self, x, y=None):
if y is not None:
self.x, self.y = x, y
else:
self.x, self.y = x
def __str__(self):
return "{}, {}".format(self.x, self.y)
print Point(1,2)
# 1, 2
print Point((1,2))
# 1, 2
I would guess that your looking for a way to overload your constructor, as is common in statically typed languages such as C++ and Java.
This is not possible in Python. What you can do is provide different keyword argument combinations, something like:
class Point(object):
def __init__(self, x=None, y=None, r=None, t=None):
if x is not None and y is not None:
self.x = x
self.y = y
elif r is not None and t is not None:
# set cartesian coordinates from polar ones
Which you would then use as:
p1 = Point(x=1, y=2)
p2 = Point(r=1, t=3.14)

Categories