Classes#
Python doesn’t make a distinction between a struct and a
class—we always use the class keyword. For example,
we can create our struct Planet from Creating a struct as:
class Planet:
"""Create a Planet
Parameters
----------
name: str
The name of the planet
a : float
The semi-major axis
e : float
The eccentricity
"""
def __init__(self, name, a=1, e=0):
self.name = name
self.a = a
self.e = e
def __str__(self):
return f"{self.name:10} : {self.a:5.3f}, {self.e:5.3f}"
planets = [Planet("Mercury", a=0.3871, e=0.2056),
Planet("Venus", a=0.7233, e=0.0068),
Planet("Earth", a=1.0000, e=0.0167),
Planet("Mars", a=1.5237, e=0.0934),
Planet("Jupiter", a=5.2029, e=0.0484),
Planet("Saturn", a=9.5370, e=0.0539),
Planet("Uranus", a=19.189, e=0.0473),
Planet("Neptune", a=30.070, e=0.0086)]
for p in planets:
print(p)
Some notes:
In python, the constructor (
__init__) returns an object, so we create aPlanetlike:p = Planet("Earth")
The
__init__function is the constructor in python. Unlike C++, in python, there is only a single constructor.All member functions (methods) of the class take a first argument traditionally called
self. This is the python equivalent of the C++thispointer.selfrefers to the object that we are working on (an instance of the class).When we access data stored in the class, we do it through
self, e.g.,self.name.The
__str__method is python’s version of overloading<<for output. This is the function that is called when weprint()aPlanet. It returns a string that we would likeprintto display.Our
planetslist containsPlanet, this is similar to C++std::vector<Planet> planets.
Operator overloading#
We can overload +, -, *, /, … in python just like in
C++. This is done by creating functions like __add__(self, x)__
for addition, etc. There are many of these special method names
that we can implement (see in particular the emulating numeric types
section).
Mathematical vector example#
Let’s implement a python version of our C++ Example: Mathematical Vectors.
Tip
We can put the class definition in its own .py file, and as long
as that is in the same directory (or installed in a systemwide search path),
we can import it.
This works similar to creating a header file in C++.
Here’s a python module that implements our vector class:
import math
class Vector:
""" a general two-dimensional vector """
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x} î + {self.y} ĵ)"
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
# it doesn't make sense to add anything but two vectors
raise NotImplementedError(f"adding {type(other)} to Vector not defined")
def __sub__(self, other):
if isinstance(other, Vector):
return Vector(self.x - other.x, self.y - other.y)
# it doesn't make sense to add anything but two vectors
raise NotImplementedError(f"subtracting {type(other)} from Vector not defined")
def __mul__(self, other):
if isinstance(other, (int, float)):
# scalar multiplication changes the magnitude
return Vector(other*self.x, other*self.y)
raise NotImplementedError("multiplication not defined")
def __matmul__(self, other):
# a dot product
if isinstance(other, Vector):
return self.x*other.x + self.y*other.y
raise NotImplementedError("matrix multiplication not defined")
def __rmul__(self, other):
return self.__mul__(other)
def __truediv__(self, other):
# we only know how to multiply by a scalar
if isinstance(other, (int, float)):
return Vector(self.x/other, self.y/other)
raise NotImplementedError("we don't know how to divide two Vectors")
def __abs__(self):
return math.sqrt(self.x**2 + self.y**2)
def __neg__(self):
return Vector(-self.x, -self.y)
def cross(self, other):
"""a vector cross product -- we return the magnitude, since it will
be in the z-direction, but we are only 2-d"""
return abs(self.x*other.y - self.y*other.x)
Some comments:
We use
isinstanceto check if the type ofothermakes sense. If not, we raise an exception that halts the execution.In addition to
__str__, we also include__repr__which is the representation of our object. That is used when theVectoris displayed via means other thanprintsee this stackoverflow discussion on __repr__ vs __str__.The
/operator is called__truediv__in python.We have a
__matmul__operator, which is invoked by the@operator. This is intended to be used for dot-products and matrix multiplication.Just like in C++, we treat
vec * aanda * vecdifferently (wherevecis aVector), since we cannot assume the objects commute under multiplication. We use__rmul__to express right-multiplication.
and here’s an example of using it—note the import vector at the top.
import vector
u = vector.Vector(3, 5)
v = vector.Vector(1, 2)
print(f"u = {u}")
print(f"v = {u}")
print()
# addition and subtraction
print(f"{u} + {v} = {u + v}")
print(f"{u} - {v} = {u - v}")
print()
# multiplication and division
print(f"2 * {u} = {2 * u}")
print(f"{u} * 2 = {u * 2}")
print(f"{u} / 3 = {u / 3}")
# abs, dot, and cross
print(f"abs(u) = {abs(u)}")
print(f"u . v = {u @ v}")
print(f"u x v = {u.cross(v)}")
Important
When we do import vector we are importing the module named vector. We
need to use that module name as the namespace when accessing the contents of the
module, so we do vector.Vector() to create an instance of our Vector class.