Python is anobject-oriented programming language, which means that itprovides features that supportobject-oriented programming (OOP).
Object-oriented programming has its roots in the 1960s, but it wasn’t until themid 1980s that it became the mainprogramming paradigm used in the creationof new software. It was developed as a way to handle the rapidly increasingsize and complexity of software systems, and to make it easier to modify theselarge and complex systems over time.
Up to now, most of the programs we have been writing use aprocedural programming paradigm. Inprocedural programming the focus is on writing functions orprocedures whichoperate on data. In object-oriented programming the focus is on the creation ofobjects which contain both data and functionality together. (We have seen turtleobjects, string objects, and random number generators, to name a few places wherewe’ve already worked with objects.)
Usually, each object definition corresponds to some object or concept in the realworld, and the functions that operate on that object correspond to the waysreal-world objects interact.
We’ve already seen classes likestr,int,float andTurtle.We are now ready to create our own user-defined class: thePoint.
Consider the concept of a mathematical point. In two dimensions, a point is twonumbers (coordinates) that are treated collectively as a single object.Points are often written in parentheses with a commaseparating the coordinates. For example,(0,0) represents the origin, and(x,y) represents the pointx units to the right andy units upfrom the origin.
Some of the typical operations that one associates with points might becalculating the distance of a point from the origin, or from another point,or finding a midpoint of two points, or asking if a point falls within agiven rectangle or circle. We’ll shortly see how we can organize thesetogether with the data.
A natural way to represent a point in Python is with two numeric values. Thequestion, then, is how to group these two values into a compound object. Thequick and dirty solution is to use a tuple, and for some applicationsthat might be a good choice.
An alternative is to define a newclass. This approach involves abit more effort, but it has advantages that will be apparent soon.We’ll want our points to each have anx and ay attribute,so our first class definition looks like this:
1234567 classPoint:""" Point class represents and manipulates x,y coords. """def__init__(self):""" Create a new point at the origin """self.x=0self.y=0
Class definitions can appear anywhere in a program, but they are usually nearthe beginning (after theimport statements). Some programmers and languagesprefer to put every class in a module of its own — we won’t do that here.The syntax rules for a classdefinition are the same as for other compound statements. There is a headerwhich begins with the keyword,class, followed by the name of the class,and ending with a colon. Indentation levels tell us where the class ends.
If the first line after the class header is a string, it becomesthe docstring of the class, and will be recognized by various tools. (Thisis also the way docstrings work in functions.)
Every class should have a method with the special name__init__.Thisinitializer method is automatically called whenever a newinstance ofPoint is created. It gives the programmer the opportunityto set up the attributes required within the new instance by giving themtheir initial state/values. Theself parameter (we could choose anyother name, butself is the convention) is automatically set to referencethe newly created object that needs to be initialized.
So let’s use our newPoint class now:
1234 p=Point()# Instantiate an object of type Pointq=Point()# Make a second pointprint(p.x,p.y,q.x,q.y)# Each point object has its own x and y
This program prints:
0000
because during the initialization of the objects, we created twoattributes calledx andy for each, and gave them both the value 0.
This should look familiar — we’ve used classes before to createmore than one object:
1234 fromturtleimportTurtletess=Turtle()# Instantiate objects of type Turtlealex=Turtle()
The variablesp andq are assigned references to two newPoint objects.A function likeTurtle orPoint that creates a new object instanceis called aconstructor, and every class automatically provides aconstructor function which is named the same as the class.
It may be helpful to think of a class as afactory for making objects.The class itself isn’t an instance of a point, but it contains the machineryto make point instances. Every time we call the constructor, we’re askingthe factory to make us a new object. As the object comes off theproduction line, its initialization method is executed toget the object properly set up with its factory default settings.
The combined process of “make me a new object” and “get its settings initializedto the factory default settings” is calledinstantiation.
Like real world objects, object instances have both attributes and methods.
We can modify the attributes in an instance using dot notation:
>>>p.x=3>>>p.y=4
Both modules and instances createtheir own namespaces, and the syntax for accessing names contained in each,calledattributes, is the same. In this case the attribute we are selectingis a data item from an instance.
The following state diagram shows the result of these assignments:
The variablep refers to aPoint object, which contains two attributes.Each attribute refers to a number.
We can access the value of an attribute using the same syntax:
>>>print(p.y)4>>>x=p.x>>>print(x)3
The expressionp.x means, “Go to the objectp refers to and get thevalue ofx”. In this case, we assign that value to a variable namedx.There is no conflict between the variablex (in the global namespace here)and the attributex (in the namespace belonging to the instance). Thepurpose of dot notation is to fully qualify which variable we are referring tounambiguously.
We can use dot notation as part of any expression, so the following statementsare legal:
12 print("(x={0}, y={1})".format(p.x,p.y))distance_squared_from_origin=p.x*p.x+p.y*p.y
The first line outputs(x=3,y=4). The second line calculates the value 25.
To create a point at position (7, 6) currently needs three lines of code:
123 p=Point()p.x=7p.y=6
We can make our class constructor more general by placing extra parameters intothe__init__ method, as shown in this example:
123456789 classPoint:""" Point class represents and manipulates x,y coords. """def__init__(self,x=0,y=0):""" Create a new point at x, y """self.x=xself.y=y# Other statements outside the class continue below here.
Thex andy parameters here are both optional. If the caller does notsupply arguments, they’ll get the default values of 0. Here is our improved classin action:
>>>p=Point(4,2)>>>q=Point(6,3)>>>r=Point()# r represents the origin (0, 0)>>>print(p.x,q.y,r.x)4 3 0
Technically speaking ...
If we are really fussy, we would argue that the__init__ method’s docstringis inaccurate.__init__ doesn’tcreate the object (i.e. set aside memory for it), —it just initializes the object to its factory-default settings after its creation.
But tools like PyScripter understand that instantiation — creation and initialization —happen together, and they choose to display theinitializer’s docstring as the tooltipto guide the programmer that calls the class constructor.
So we’re writing the docstring so that it makes the most sense when it pops up tohelp the programmer who is using ourPoint class:

The key advantage of using a class likePoint rather than a simpletuple(6,7) now becomes apparent. We can add methods tothePoint class that are sensible operations for points, butwhich may not be appropriate for other tuples like(25,12) which mightrepresent, say, a day and a month, e.g. Christmas day. So being ableto calculate the distance from the origin is sensible forpoints, but not for (day, month) data. For (day, month) data,we’d like different operations, perhaps to find what day of the weekit will fall on in 2020.
Creating a class likePoint brings an exceptionalamount of “organizational power” to our programs, and to our thinking.We can group together the sensible operations, and the kinds of datathey apply to, and each instance of the class can have its own state.
Amethod behaves like a function but it is invoked on a specificinstance, e.g.tess.right(90). Like a dataattribute, methods are accessed using dot notation.
Let’s add another method,distance_from_origin, to see better how methodswork:
1 2 3 4 5 6 7 8 91011 classPoint:""" Create a new Point, at coordinates x, y """def__init__(self,x=0,y=0):""" Create a new point at x, y """self.x=xself.y=ydefdistance_from_origin(self):""" Compute my distance from the origin """return((self.x**2)+(self.y**2))**0.5
Let’s create a few point instances, look at their attributes, and call our newmethod on them: (We must run our program first, to make ourPoint class available to the interpreter.)
>>>p=Point(3,4)>>>p.x3>>>p.y4>>>p.distance_from_origin()5.0>>>q=Point(5,12)>>>q.x5>>>q.y12>>>q.distance_from_origin()13.0>>>r=Point()>>>r.x0>>>r.y0>>>r.distance_from_origin()0.0
When defining a method, the first parameter refers to the instance beingmanipulated. As already noted, it is customary to name this parameterself.
Notice that the caller ofdistance_from_origin does not explicitlysupply an argument to match theself parameter — this is done forus, behind our back.
We can pass an object as an argument in the usual way. We’ve already seenthis in some of the turtle examples, where we passed the turtle tosome function likedraw_bar in the chapter titledConditionals,so that the function could control and use whatever turtle instance we passed to it.
Be aware that our variable only holds a reference to an object, so passingtessinto a function creates an alias: both the caller and the called functionnow have a reference, but there is only one turtle!
Here is a simple function involving our newPoint objects:
12 defprint_point(pt):print("({0}, {1})".format(pt.x,pt.y))
print_point takes a point as an argument and formats the output in whicheverway we choose. If we callprint_point(p) with pointp as defined previously,the output is(3,4).
Most object-oriented programmers probably would not do what we’ve just done inprint_point.When we’re working with classes and objects, a preferred alternativeis to add a new method to the class. And we don’t like chatterbox methods that callprint. A better approach is to have a method so that every instancecan produce a string representation of itself. Let’s initiallycall itto_string:
12345 classPoint:# ...defto_string(self):return"({0}, {1})".format(self.x,self.y)
Now we can say:
>>>p=Point(3,4)>>>print(p.to_string())(3, 4)
But don’t we already have astr type converter that canturn our object into a string? Yes! And doesn’tprintautomatically use this when printing things? Yes again!But these automatic mechanisms do not yet do exactly what we want:
>>>str(p)'<__main__.Point object at 0x01F9AA10>'>>>print(p)'<__main__.Point object at 0x01F9AA10>'
Python has a clever trick up its sleeve to fix this. If we call our newmethod__str__ instead ofto_string, the Python interpreterwill use our code whenever it needs to convert aPoint to a string.Let’s re-do this again, now:
12345 classPoint:# ...def__str__(self):# All we have done is renamed the methodreturn"({0}, {1})".format(self.x,self.y)
and now things are looking great!
>>>str(p)# Python now uses the __str__ method that we wrote.(3, 4)>>>print(p)(3, 4)
Functions and methods can return instances. For example, given twoPoint objects,find their midpoint. First we’ll write this as a regular function:
12345 defmidpoint(p1,p2):""" Return the midpoint of points p1 and p2 """mx=(p1.x+p2.x)/2my=(p1.y+p2.y)/2returnPoint(mx,my)
The function creates and returns a newPoint object:
>>>p=Point(3,4)>>>q=Point(5,12)>>>r=midpoint(p,q)>>>r(4.0, 8.0)
Now let us do this as a method instead. Suppose we have a point object,and wish to find the midpoint halfway between it and some other target point:
12345678 classPoint:# ...defhalfway(self,target):""" Return the halfway point between myself and the target """mx=(self.x+target.x)/2my=(self.y+target.y)/2returnPoint(mx,my)
This method is identical to the function, aside from some renaming.It’s usage might be like this:
>>>p=Point(3,4)>>>q=Point(5,12)>>>r=p.halfway(q)>>>r(4.0, 8.0)
While this example assigns each point to a variable, this need not be done.Just as function calls are composable, method calls and object instantiationare also composable, leading to this alternative that uses no variables:
>>>print(Point(3,4).halfway(Point(5,12)))(4.0, 8.0)
The original syntax for a function call,print_time(current_time), suggests that thefunction is the active agent. It says something like,“Hey, print_time!Here’s an object for you to print.”
In object-oriented programming, the objects are considered the active agents. Aninvocation likecurrent_time.print_time() says“Hey current_time!Please print yourself!”
In our early introduction to turtles, we usedan object-oriented style, so that we saidtess.forward(100), whichasks the turtle to move itself forward by the given number of steps.
This change in perspective might be more polite, but it may not initiallybe obvious that it is useful. But sometimes shifting responsibility fromthe functions onto the objects makes it possible to write more versatilefunctions, and makes it easier to maintain and reuse code.
The most important advantage of the object-oriented style is that itfits our mental chunking and real-life experience more accurately.In real life ourcook method is part of our microwave oven — we don’thave acook function sitting in the corner of the kitchen, into whichwe pass the microwave! Similarly, we use the cellphone’s own methodsto send an sms, or to change its state to silent. The functionalityof real-world objects tends to be tightly bound up inside the objectsthemselves. OOP allows us to accurately mirror this when weorganize our programs.
Objects are most useful when we also need to keep some state that is updated fromtime to time. Consider a turtle object. Its state consists of things likeits position, its heading, its color, and its shape. A method likeleft(90) updatesthe turtle’s heading,forward changes its position, and so on.
For a bank account object, a main component of the state would bethe current balance, and perhaps a log of all transactions. The methods wouldallow us to query the current balance, deposit new funds, or make a payment.Making a payment would include an amount, and a description, so that this couldbe added to the transaction log. We’d also want a method to show the transactionlog.
Rewrite thedistance function from the chapter titledFruitful functions so that it takes twoPoints as parameters instead of four numbers.
Add a methodreflect_x toPoint which returns a newPoint, one which is thereflection of the point about the x-axis. For example,Point(3,5).reflect_x() is (3, -5)
Add a methodslope_from_origin which returns the slope of the line joining the originto the point. For example,
>>>Point(4,10).slope_from_origin()2.5
What cases will cause this method to fail?
The equation of a straight line is “y = ax + b”, (or perhaps “y = mx + c”).The coefficients a and b completely describe the line. Write a method in thePoint class so that if a point instance is given another point, it will compute the equationof the straight line joining the two points. It must return the two coefficients as a tupleof two values. For example,
>>>print(Point(4,11).get_line_to(Point(6,15)))>>>(2,3)
This tells us that the equation of the line joining the two points is “y = 2x + 3”.When will this method fail?
Given four points that fall on the circumference of a circle, find the midpoint of the circle.When will this function fail?
Hint: Youmustknow how to solve the geometry problembefore you think of going anywhere near programming.You cannot program a solution to a problem if you don’t understand what you want the computer to do!
Create a new class, SMS_store. The class will instantiate SMS_store objects,similar to an inbox or outbox on a cellphone:
my_inbox=SMS_store()
This store can hold multiple SMS messages (i.e. its internal state will just be a list of messages). Each messagewill be represented as a tuple:
(has_been_viewed,from_number,time_arrived,text_of_SMS)
The inbox object should provide these methods:
my_inbox.add_new_arrival(from_number,time_arrived,text_of_SMS)# Makes new SMS tuple, inserts it after other messages# in the store. When creating this message, its# has_been_viewed status is set False.my_inbox.message_count()# Returns the number of sms messages in my_inboxmy_inbox.get_unread_indexes()# Returns list of indexes of all not-yet-viewed SMS messagesmy_inbox.get_message(i)# Return (from_number, time_arrived, text_of_sms) for message[i]# Also change its state to "has been viewed".# If there is no message at position i, return Nonemy_inbox.delete(i)# Delete the message at index imy_inbox.clear()# Delete all messages from inbox
Write the class, create a message store object, write tests for these methods, and implement the methods.