Object-oriented programming is a way of programming that groups together related data and functions into objects, and makes it easier to design more complex programs. In traditional (procedural) programming, you can typically think of a program as an set of instructions that is read from top to bottom, with the exception of functions, which allow you to avoid repetitive code. In object-oriented programming, one instead thinks of a program mainly as setting up a number of 'types' of objects, and doing operations on them.
Note that you have already been using objects! Every Python object is an object:
s = 'hello' s.upper()
l = [4,2,1] l.sort() l
In these cases,
sort are both methods, and
l are instances of the
list class respectively (we will discuss these terms below).
Classes are most easily explained by example, so let's dive right in and look at a class, which is used to define an object:
class Person(object): def __init__(self, name): self.name = name def say_hello(self): print("Hello, my name is " + self.name)
and let's try and use it:
tom = Person('Tom') tom.say_hello()
There is a lot of new syntax here which you won't have likely seen before, but also some syntax which will look more familiar. First, let's have a look at the class definition. The class is defined using the following syntax:
class Person(object): ...
This looks similar to the definition for a function, except that it doesn't directly contain code, but it then contains a series of functions:
class Person(object): def __init__(self, ...): ... def say_hello(self, ...): ...
So in effect, a class is a collection of functions. What is special about these functions? If you look at the full class definition above, you will see that the first argument to all the functions is
self, which is the object itself. Why is this useful?
At this point, we need to clarify some more terminology: an instance of a class is a particular object represented by the class - that is, while
Person is the class (i.e. the general definition of the idea of a 'person'), an instance is a particular person, e.g. Tom:
tom = Person('Tom')
tom is an instance of the
Person class. Now let's look at the first defined method, called
def __init__(self, name): self.name = name
The term method is basically used for a function that is attached to a class. The
__init__ method is a method that is automatically called when creating an instance of the class (for those familiar with the terminology, it is equivalent to a constructor). What is basically happening here is that
self is the actual instance that is being created, and the method then takes the name of the person and assigns it to the
name attribute of the instance:
tom = Person('Tom') print(tom.name)
As you can see, the
tom instance has an attribute
name that has been set to the name that was passed. Now let's look at the second method:
def say_hello(self): print "Hello, my name is " + self.name
This looks more like a normal function, but takes the same
self argument. It then prints out a string containing the value of the
name attribute of this instance. Note that while all methods should take the
self argument, this argument doesn't need to be passed because when calling a method, this is automatically done:
is equivalent to:
In the last example, we passed the instance explicitly to the function, but of course the first notation is much cleaner and simpler. Now let's look at the following example:
alice = Person("Alice") bob = Person("Bob") alice.say_hello() bob.say_hello()
as you can see, when calling
say_hello, the result will depend to the actual object that the method is attached to.
Since they are essentially functions, methods can of course take arguments, which can be any Python object(s). For example, we can make a new
say_hello_to method that will take another
Person instance and say hello to them:
class Person(object): def __init__(self, name): self.name = name def say_hello(self): print("Hello, my name is " + self.name) def say_hello_to(self, other): print("Hello " + other.name + ", my name is " + self.name)
alice = Person("Alice") bob = Person("Bob") alice.say_hello_to(bob)
As we will see below, you can actually pass any object to
say_hello_to as long as it has a
One of the powerful features of object-oriented programming is inheritance, which means that it is possible to define classes based on other classes. For example, we can define:
from scipy.integrate import simps class Scientist(Person): def integrate(self, x, y): return simps(y, x=x)
This looks similar to before, but this time when defining the class, we've used
Person instead of
object. This means that by default,
Scientist will behave like a
Person instance, but it then has some additional methods defined:
alice = Scientist("Alice") alice.integrate([1,2,3], [4,5,6])
say_hello_to method takes any object that has a
name attribute, so it can say hello to another person:
bob = Person("Bob") alice.say_hello_to(bob)
eve = Scientist("Eve") alice.say_hello_to(eve)
As mentioned above, attributes are variables attached to the object. It is worth mentioning that attributes are not static, so they can be changed from outside the class:
p = Person('Tom') p.say_hello()
p.name = 'Alice' p.say_hello()
and it is also possible to create new attributes from the outside:
p.age = 97 p.name p.age
So far, the programs you have needed to write have not been very complex, but as you write more and more complex analysis, classes may come in useful in some situations. For example, if you are doing particle physics, you might want to use a class to represent a
Particle, which then has common operations and calculations you might want to do with a particle. If you are doing Astronomy, you could use a
Galaxy class that would be used to represent these objects. You can even use objects without defining actual methods, but just as a convenience to contain several variables - if you need to always handle cartesian point in your code, you could define:
class Point(object): def __init__(self, x, y, z): self.x = x self.y = y self.z = z
pt = Point(1, 2, 3) pt.x
After which if you want to pass a point to a function, you can pass it in a single variable instead of three:
def find_separation(p1, p2): return np.sqrt((p1.x - p2.x)**2 + (p1.y - p2.y)**2 + (p1.z - p2.z)**2)
as opposed to:
def find_separation(x1, y1, z1, x2, y2, z2): return np.sqrt((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
This might not look like a big difference, but now imagine that you also wanted to pass 3-d velocities, then you would need to call the function with 12 arguments!
This is not to say that you should always use objects, but if you start to think of your program instead of what objects and basic operations are being represented, you may be able to write it more simply than if you were using only functions and procedural code. You may also be able to re-use classes for different projects if they are general enough!
Particle class that can be used to represent a particle with a mass, a 3-d position, and a 3-d velocity.
Write a method that can be used to compute the kinetic energy of the particle
Write a method that takes another particle as an argument and finds the distance between the two particles
Write a method that given a time interval
dt will update the position of the particle to the new position based on the current position and velocity.
ChargedParticle class that inherits from the
Particle class, but also has an attribute for the charge of the particle.
Write examples of using these classes to test that the methods are working correctly.
# your solution here