Module 4 - Chapter 1
Module 4 - Chapter 1
CHAPTER 01
CLASSES AND OBJECTS
1. Programmer-defined types
We have used many of Python’s built-in types; now we are going to define a new type. As an example,
we will create a type called Point that represents a point in two-dimensional space.
In mathematical notation, points are often written in parentheses with a comma separating the
coordinates.
For example, (0, 0) represents the origin, and (x, y) represents the point x units to the right and y units up
from the origin.
There are several ways we might represent points in Python:
1. We could store the coordinates separately in two variables, x and y.
2. We could store the coordinates as elements in a list or tuple.
3. We could create a new type to represent points as objects.
Creating a new type is more complicated than the other options, but it has advantages that will be
apparent soon.
A programmer-defined type is also called a class. A class definition looks like this:
class Point:
"""Represents a point in 2-D space."""
The header indicates that the new class is called Point. The body is a docstring that
ex-plains what the class is for. You can define variables and methods inside a class
definition, but we will get back to that later.
Defining a class named Point creates a class object.
>>> Point
<class '__main__.Point'>
Because Point is defined at the top level, its “full name” is __main__.Point.
The class object is like a factory for creating objects. To create a Point, you call Point as if it were a function.
blank = Point()
blank
<__main__.Point object at 0xb7e9d3ac>
The return value is a reference to a Point object, which we assign to blank.
Creating a new object is called instantiation, and the object is an instance of the class.
When you print an instance, Python tells you what class it belongs to and where it is stored in memory (the
prefix 0x means that the following number is in hexadecimal).
2. Attributes
You can assign values to an instance using dot notation:
blank.x = 3.0
blank.y = 4.0
This syntax is similar to the syntax for selecting a variable from a module, such as math.pi or
string.whitespace .
In this case, though, we are assigning values to named elements of an object. These elements are called
attributes.
A state diagram that shows an object and its attributes is called an object diagram; see Figure 15.1.
The variable blank refers to a Point object, which contains two attributes. Each attribute refers to a
floating-point number.
You can read the value of an attribute using the same syntax:
blank.y x = blank.x
4.0 x
3.0
The expression blank.x means, “Go to the object blank refers to and get the value of x.” In the
example, we assign that value to a variable named x. There is no conflict between the variable x
and the attribute x.
You can use dot notation as part of any expression. For example:
def print_point(p):
print('(%g, %g)' % (p.x, p.y))
print_point takes a point as an argument and displays it in mathematical notation. To invoke it, you
can pass blank as an argument:
3. Rectangles
Sometimes it is obvious what the attributes of an object should be, but other times you have to make
decisions.
class Rectangle:
"""Represents a rectangle.
attributes: width, height, corner."""
The docstring lists the attributes: width and height are numbers; corner is a Point object that
specifies the lower-left corner.
To represent a rectangle, you have to instantiate a Rectangle object and assign values to the attributes:
box = Rectangle()
box.width = 100.0
box.height = 200.0
box.corner = Point()
box.corner.x = 0.0
box.corner.y = 0.0
The expression box.corner.x means, “Go to the object box refers to and select the attribute named
corner; then go to that object and select the attribute named x.”
Figure 15.2 shows the state of this object. An object that is an attribute of another object is embedded.
def find_center(rect):
p = Point()
p.x = rect.corner.x + rect.width/2
p.y = rect.corner.y + rect.height/2
return p
Here is an example that passes box as an argument and assigns the resulting Point to center:
center = find_center(box)
print_point(center)
(50, 100)
box.width = box.width + 50
box.height = box.height + 100
You can also write functions that modify objects.
For example, grow_rectangle takes a Rectangle object and two numbers, dwidth and dheight, and adds
the numbers to the width and height of the rectangle:
6. Copying
Aliasing can make a program difficult to read because changes in one place might have unexpected
effects in another place.
It is hard to keep track of all the variables that might refer to a given object.
Copying an object is often an alternative to aliasing. The copy module contains a function called copy
that can duplicate any object:
p1 = Point()
p1.x = 3.0
p1.y = 4.0
import copy
p2 = copy.copy(p1)
p1 and p2 contain the same data, but they are not the same Point.
print_point(p1)
(3, 4)
print_point(p2)
(3, 4)
p1 is p2
False
p1 == p2
False
The is operator indicates that p1 and p2 are not the same object, which is what we expected. But you
might have expected == to yield True because these points contain the same data.
In that case, you will be disappointed to learn that for instances, the default behavior of the == operator is
the same as the is operator; it checks object identity, not object equivalence.
That’s because for programmer-defined types, Python doesn’t know what should be considered
equivalent. At least, not yet.
If you use copy.copy to duplicate a Rectangle, you will find that it copies the Rectangle object but not the
embedded Point.
box2 = copy.copy(box)
box2 is box
False
box2.corner is box.corner
True
Figure 15.3 shows what the object diagram looks like. This operation is called a shallow copy because it
copies the object and any references it contains, but not the embedded objects.
For most applications, this is not what you want.
In this example, invoking grow_rectangle on one of the Rectangles would not affect the other,
but invoking move_rectangle on either would affect both! This behavior is confusing and error -
prone.
Fortunately, the copy module provides a method named deepcopy that copies not only the object
but also the objects it refers to, and the objects they refer to, and so on. You will not be surprised
to learn that this operation is called a deep copy.
box3 = copy.deepcopy(box)
box3 is box
False
box3.corner is box.corner
False
box3 and box are completely separate objects.