Python __sub__ usage with class instances - python

class Point:
def __init__(self, x = 0, y = 0):
self.x = x
self.y = y
def __sub__(self, other):
x = self.x + other.x
y = self.y + other.y
return Point(x,y)
p1 = Point(3, 4)
p2 = Point(1, 2)
result = p1-p2
print(result.x, result.y) # prints (4,6)
Can anyone explain how the above code works. Can't get my head around it.
I understand that __sub__ is an operator overloader in python and intercepts the p1-p2 call. But how does it work with two separate instances of class ?

__sub__ is supposed to be the magic method equivalent of - arithmetic operator, so not sure why you're adding them...
That aside, when you do p1 - p2, it is the same as p1.__sub__(p2). The __sub__ function is invoked on p1, the calculation made and the new Point object returned.

Now you edited the question, the answer is simple:
p1 = Point(3, 4)
p2 = Point(1, 2)
result = p1-p2
You have two points as arguments self, other, so, self, obviously in your example would be p1, and other is p2, after doing all the calculation, you return a new Point, so, p1 and p2 are not modified.
Important advice
The most rare thing, is that you call it __sub__, but indeed, you are actually adding! I mean, please... either change the method definition and replace + by -, or change __sub__... just a piece of advice.

Related

Using attributes from one class into another

In python, I want to create a coordinate system with two classes in separate scripts; lines and points where point class is just x,y and z coordinates and line class then have two of these points as attributes. My problem is that I don't understand how to write line class so it understands it is points it is getting and how to retrieve for example x in a point in line class. Point class looks like this:
class point:
def __init__(self, x, y, z=0):
self.x = float(x)
self.y = float(y)
self.z = float(z)
I am not certain how to write the line class but it looks something like this:
import point
import math
class line(point):
def __init__(self, p1, p2):
self.p1 = p1
self.p2 = p2
def length(self):
return math.sqrt((self.p2.x-self.p1.x)+(self.p2.y-self.p1.y))
Where I should then be able to create objects either like
line = line((1,2), (3,4,5))
or like
p1 = point.point(1,2)
p2 = point.point(3,4,5)
line = line(p1,p2)
I also want to be able to create functions like the one in line class and to retrieve coordinates in something like
line.p2.x
or
line.p2[0]
giving me (in this case) the output of 3. I also don't want point to be a subclass since there can be points not belonging to a line. Perhaps there is already a good answer to how to implement line class in such a way but I haven't found or understood it. Most examples is either about subclasses or separate classes which doesn't send or share some attributes. Thanks in advance for the help.
First: classes should be title capitalized, so that the proper names would be Line and Point - that make them easy to separate from plain variables.
As you've mentioned, inheriting from Point isn't the way to go - since a Line isn't a point. But you're almost there with your example:
from point import Point
import math
class Line:
def __init__(self, p1, p2):
self.p1 = p1
self.p2 = p2
def length(self):
return math.sqrt((self.p2.x-self.p1.x)+(self.p2.y-self.p1.y))
Line(Point(x1, y1), Point(x2, y2)) # should work
You can also use Python 3.6+'s typing support to say that you expect Point objects:
class Line:
def __init__(self, p1: Point, p2: Point):
self.p1 = p1
self.p2 = p2
....
A tuple is not a point, and line.__init__ will not automatically know to make the conversion. While you could add logic to line.__init__ to detect the type of argument, I would recommend separate functions for creating lines from tuples and points. Which you choose as the "canonical" argument is up to you. Here's an example using point.
class Line:
def __init__(self, p1: point, p2: point):
self.p1 = p1
self.p2 = p2
#classmethod
def from_tuples(cls, p1, p2):
return cls(point(*p1), point(*p2))
line1 = Line.from_tuples((1,2), (3,4,5))
p1 = point(1,2)
p2 = point(3,4,5)
line = Line(p1,p2)
Based on how you want to use point, I would recommend a namedtuple, and again leave the responsibility to pass the correct type of argument to the caller.
Point = namedtuple('Point', 'x y z')
p1 = Point(1.0, 2.0) # p.x == p[0] == 1

Purpose and functionality of Method vs function

I am trying to understand what is the difference between creating a method inside a class and creating a function. Both gives same output. Pls provide clarity
class Point:
""" Create a new Point, at coordinates x, y """
def __init__(self, x=0, y=0, z=0):
""" Create a new point at x, y """
self.x = x
self.y = y
self.z = ((self.x ** 2) + (self.y ** 2)) ** 0.5
and this function:
def distance(x,y):
z=((x ** 2) + (y ** 2)) ** 0.5
return z
both looks same to me
p = Point(3,4)
p.x, p.y, p.z
output:
(3, 4, 5.0)
and
distance(3,4)
output: 5.0
The difference is the self reference - the class instance has access to its own local variables and its methods. In this small example there isn't any need for a class. The function returns a tuple object much like the Point object you created. There is a rule: If a class has two methods and one of them is __init__, you shouldn't have a class. In your case, you didn't even reach the two method bar. (That rule is somewhat tongue-in-cheek but applies most of the time).
The usefulness of classes is when you have multiple methods that naturally share a set of data and you want to keep them together. Or if you have different types of objects that share common functionality and you want them to look the same externally.
It is mostly the same.
Some differences:
The function is available to be used outside the class.
If you include the calculation in the init constructor, the value is saved as an attribute of the class. It will not be recalculated every time you want to see the value.
With a class you should preferably do something like that :
class Point:
""" Create a new Point, at coordinates x, y """
def __init__(self, x=0, y=0):
""" Create a new point at x, y """
self.x = x
self.y = y
def distance(self):
self.z = ((self.x ** 2) + (self.y ** 2)) ** 0.5
return self.z
p = Point(3, 4)
print(p.distance())
The main advantage is to avoid the need of passing parameters to the method while they are required for a function
Here is the gist of it. A method can depend on the state (instance) variables in a class. A free function outside of a class has no such dependence.
Example:
>>> x = "foo"
foo
>>> x.length()
3
The method length() has no arguments; however you can see it is dependent on the string's state (its character sequence).

How to control type casting for my classes

How can I control type casting for my classes? I know there is __int__() or __str__(), but what about other types? E.g. I have two classes Point and Vector. Is it possible to do something like:
point = Point(1, 2, 3)
# the following should not call Vector._init_(), but use a cast defined in Point
# (something like Point.__Vector__())
vector = Vector(point)
Is this possible? If so, how can I achieve such behavior? If not, for what standard types could I define a cast function to allow e.g. tuple(point) or list(point) - couldn't find this in the python documentation.
You could do it via the use of classmethods.
For example,
from point import Point # where the Point object is defined
class Vector:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
#classmethod
def from_point(cls, point):
if not isinstance(point, Point): # some validation first
raise TypeError("The given object is not of type 'Point'")
return cls(point.x, point.y, point.z)
# other methods
Thus, then if you have a p = Point(1, 0, 0) then you can do v = Vector.from_point(p) and you'll have what you were looking for.
There are quite some improvements that could be applied but depend on the details of Point.

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

Two objects in a method from a class?

I'm having a small issue with this code, I am currently learning about classes and trying to separate the two objects I have created to use both of them in a method from the class.
import math
class Segment:
def __init__(self, xcoord = 0, ycoord = 0):
self.x = xcoord
self.y = ycoord
def get(self):
return (self.x, self.y)
def setx(self, xcoord):
self.x = xcoord
def sety(self, ycoord):
self.y = ycoord
def length(self, xcoord, ycoord):
return math.sqrt(math.pow(xcoord-ycoord,2)+(xcoord-ycoord,2))
p1 = Segment(3,4)
p2 = Segment()
p2.setx(5)
p2.sety(5)
s = Segment(p1,p2)
print(Segment.get(p1))
print(Segment.get(p2))
print(s.length())
I know that I am missing parameters in my length() method, or perhaps I have not? I would like to understand how I am able to have the objects interact with on another after I have defined them.
For further clarity, I am trying to print the distance between the two objects using the parameters I have assigned to them.
Okay, let's forget the code for a second. Firstly, let's talk about naming things. Your Segment class is not a class of segments - it's a class of points. So let's start by renaming your class Point.
class Point:
def __init__(self, xcoord = 0, ycoord = 0):
self.x = xcoord
self.y = ycoord
Better already, no?
Now, imagine you're looking at someone else's code, trying to use that. Their Points have a length() method that you can call. What do you expect that to do? What could that... possibly do? A number of things, all because length is an awful descriptor for something that a Point is doing. It's certainly not a property of the Point - a point is 0-dimensional.
So let's rethink that function. There are two obvious ways to make this API - your Point class could have a distance_to(other_point) method, that would accept one argument - another Point. Optionally, you could have a module-level function segment_length(point1, point2) that would give you the length of the segment defined by the two Point objects.
So, the module-level function:
def segment_length(p1, p2):
return math.sqrt((p2.x-p1.x)**2 + (p2.y-p1.y)**2)
I'll leave the Point method to you, should you wish to attempt it. It looks very similar, just using self in lieu of one of the points.
Lets walk through this:
p1 is an instance of the Segment class with attributes x=3, y=4.
p2 is an instance of the Segment class with attributes x=0, y=0,
When you set p2 to (5, 5) you could do it with p2 = Segment(5, 5),
s will have attributes x=p1 (an instance of Segment, not a coordinate) and y=p2 (another instance of Segment).
Calculating the length.
Your length method should look like this:
def length(self, xcoord, ycoord):
return math.sqrt(math.pow(xcoord - self.x,2)+math.pow(ycoord - self.y,2))
This now uses the x and y coordinates of the class instance (in the example below, p1) and calculates the length between them, and the xcoord and ycoord parameters provided.
And you would call this with:
p2x, p2y = p2.get()
print(p1.length(p2x, p2y))
Firstly, you're missing a second math.pow() in your line:
return math.sqrt(math.pow(xcoord-ycoord,2)+(xcoord-ycoord,2))
Secondly with your call s = Segment(p1, p2) the x and y values of your Segment s are equal to the segments p1 and p2.
At the moment your values should read:
p1.get()
> (3, 4)
p2.get()
> (5, 5)
After the assignment of s you get:
s.get()
> ((3, 4), (5, 5))
This is problematic, because math.pow() has no idea what to do with a Segment object.

Categories