We've seen that values in Python each have a type: an integer, a floating-point number, a Boolean, a string, a list, a tuple, a dictionary, or a file handle. In writing larger programs, though, it's useful to develop our own types, so that we can think in terms of manipulating things specific to our application, like customers or warehouses.
Developing software by developing new types is called
object-oriented programming. Object-oriented programming
involves understanding several new terms. First, a
class a user-defined type, defining the
properties that each value of that type has.
An individual value of a class's type is called an
instance or object (the two terms are synonymous).
Each instance will have some
named values providing information about that instance, each
called a data attribute.
And instances will have methods, which
are basically functions that the instance can perform;
we've seen several examples of methods before, including the
split
method that strings have and the append
method that lists have.
As an example of a class in Python, let's suppose we
want to define a type for representing points, called Point
.
We'll get to
how to define a class later, but for now let's concentrate on
what how we might want to use points.
o = Point() # create point at (0,0)
print(o.x) # access o's x-coordinate: 0
p = Point() # create point that we move to (5, 5)
p.move_to(5, 5)
p.move_up(7) # move point p to (5, 12)
print(p.y) # access p's y-coordinate: 12
d = o.distance_to(p) # compute distance between both points
print(d)
The first line creates an individual Point
object:
“Calling” the class as if it were a function
is what creates a new instance of the class.
As it happens, each Point
instance has two data
attributes, an x-coordinate and a y-coordinate,
named appropriate enough x
and y
.
So if o
refers to an individual instance, I can retrieve
o
's x-coordinate using o.x
.
The Point
class also defines some methods that each
individual point can perform. One method, named move_to
, simply
moves the point to the coordinates named in the parameters.
Another, named translate
, adds the parameters to the
point's x- and y-coordinates.
And finally, the distance_to
method takes another point
as a parameter and returns the distance between those two
points.
The code above won't work unless we actually have a
Point
class. In fact, we have to create it. Following is
an example:
class Point:
def __init__(self):
self.x = 0
self.y = 0
def move_to(self, new_x, new_y):
self.x = new_x
self.y = new_y
def move_up(self, change_in_y):
self.y += change_in_y
def distance_to(self, other_point):
dx = self.x - other_point.x
dy = self.y - other_point.y
return (dx * dx + dy * dy) ** 0.5
We define the class beginning with a class
declaration, followed by the name of the class we are defining.
Inside the class
are a list of several
“functions” that actually correspond to the class's
methods.
One method named __init__
(note the
double-underscore on both sides) is called the
constructor. It has a special function: This method is
executed immediately upon creation of a new object.
When we say “o = Point()
”, Python first
allocates memory for the object and then enters
the constructor, passing the newly created object
as the first parameter. The job of the constructor is to assign
initial values to each of the object's data attributes.
In this case, the constructor wants to initialize the two
attributes of each point, x
and a y
.
Notice how we use the syntax self.x
to refer to the data attribute.
If we had written “x = 0
” instead, we would
simply be creating a local variable that would disappear as soon
as the function is over. But when we write self.x
, we
are depositing the data attribute x
into self
,
which is the newly created object.
Following __init__
, you see three more functions,
each corresponding to one of the methods.
Each function starts with an additional parameter, again named
self
, which refers to the object on which the method has
been invoked. When Python executes “p.move_to(4, 5)
”,
it enters move_to
with p
for the first argument
self
, 4 for the second argument new_x
,
and 5 for the third argument new_y
.
In this case, move_to
should update p
's
x-coordinate and y-coordinate, so you
see the body updating the x
and y
attributes of
self
— that is, p
.
Notice how distance_to
ends up taking another point
as its argument. When we call “o.distance_to(p)
”,
the first parameter self
would be o
, while the
second parameter other_point
would be p
.
Inside, we want to retrieve the x-coordinates of both
points, so you see self.x
to get o
's x-coordinate
and other_point.y
to get p
's x-coordinate.
The difference is saved into dx
, which is simply a
temporary variable to help with the computation (since we didn't
write self.dx
), and so it disappears as soon as the
function returns.