I want to make a simple Point2d class in a Python 3.7 program that implements just a few features. I saw in an SO answer (that I can’t find now) that one way to create a Point class was to override
complex so I wrote this:
import math class Point2d(complex): def distanceTo(self, otherPoint): return math.sqrt((self.real - otherPoint.real)**2 + (self.imag - otherPoint.imag)**2) def x(self): return self.real def y(self): return self.imag
In : p1 = Point2d(3, 3) In : p2 = Point2d(6, 7) In : p1.distanceTo(p2) Out: 5.0
But when I do this,
p3 is instance of
In : p3 = p1 + p2 In : p3.distanceTo(p1) --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-52-37fbadb3015e> in <module> ----> 1 p3.distanceTo(p1) AttributeError: 'complex' object has no attribute 'distanceTo'
Most of my background is in Objective-C and C# so I’m still trying to figure out the pythonic way of doing things like this. Do I need to override all the math operators I want to use on my Point2d class? Or am I going about this completely the wrong way?
The problem, is that your class, when it uses any of the data model functions belonging to complex It returns a complex, so you’ll need to turn this in to your
adding this method should do the trick
def __add__(self, b): return Point2d(super().__add__(b))
But still there should be a better way of doing it. But this is the way to dynamically wrap some Data Model (dunder) methods.
By the way, the distance function you can make it shorter something like this
def distanceTo(self, otherPoint): return abs(self - otherPoint)
In general, prefer composition to inheritance. You can implement all the desired operations in terms of complex numbers.
class Point2D: def __init__(self, x, y): self._p = complex(x,y) @classmethod def _from_complex(self, z): return Point2D(z.real, z.imag) def __add__(self, other): return Point2D._from_complex(self._p + other._p) # etc
Is it a lot of boilerplate? Yes. But it’s not really boilerplate you can avoid.
In this case I suggest to implement your class
Point2d from scratch.
If you’re lazy, take a look to some lib like sympy which includes a
Point class and other geometry stuff https://docs.sympy.org/latest/modules/geometry/index.html
I’m going to mention a way of overriding all the methods without manually writing each of them, but only because we are all consenting adults here. I don’t really recommend it, it is much clearer if you just override each and every operation. That said, you can write a class wrapper which inspects all the methods of the base class and converts the output to a point if it is a complex type.
import math import inspect def convert_to_base(cls): def decorate_func(name, method, base_cls): def method_wrapper(*args, **kwargs): obj = method(*args, **kwargs) return cls.convert(obj, base_cls) if isinstance(obj, base_cls) else obj return method_wrapper if name not in ('__init__', '__new__') else method for base_cls in cls.__bases__: for name, method in inspect.getmembers(base_cls, inspect.isroutine): # Might want to change this filter setattr(cls, name, decorate_func(name, method, base_cls)) return cls @convert_to_base class Point2d(complex): @classmethod def convert(cls, obj, base_cls): # Use base_cls if you need to know which base class to convert. return cls(obj.real, obj.imag) def distanceTo(self, otherPoint): return math.sqrt((self.real - otherPoint.real)**2 + (self.imag - otherPoint.imag)**2) def x(self): return self.real def y(self): return self.imag p1 = Point2d(3, 3) p2 = Point2d(6, 7) p3 = p1 + p2 p4 = p3.distanceTo(p1) print(p4) # 9.219544457292887
What is happening here is that it just checks all the methods of the base class, and if what it returns is of the type of the base class, converts it to the child class, as defined by the special classmethod in the child class.
Maybe you are interested in this. A while ago I started working in a module called
odeanimate (For time reason the development is kind of stalled).
The idea was to create a module do to math in an explicit way, such that you have objects that respect the operators in a meaningful way.
I would encourage you to check this file in particular
odeanimate/vector.py, which in my opinion is a good example on how to override the operators.
But essentially the answer is to implement all the data model methods or dunder methods, which the other answers already explained.