Archive:Think Python/Classes and objects: Difference between revisions
wikademia>Whiteknight m Think Python: Automatically uploading HTML source of this book from http://www.greenteapress.com/thinkpython/html/. Will convert to wikitext in a separate step |
wikademia>Whiteknight m Partial (mostly) conversion from HTML to Wikitext |
||
| Line 1: | Line 1: | ||
{{Think Python/Page}} | |||
== Chapter 15  Classes and objects == | |||
=== 15.1  User-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 | to define a new type. As an example, we will create a type | ||
called | called <TT>Point</TT> that represents a point in two-dimensional | ||
space. | space. | ||
In mathematical notation, points are often written in | |||
parentheses with a comma separating the coordinates. For example, | parentheses with a comma separating the coordinates. For example, | ||
(0, 0) represents the origin, and (<I>x</I>, <I>y</I>) represents the | |||
point | point <I>x</I> units to the right and <I>y</I> units up from the origin. | ||
variables, | |||
or tuple. | There are several ways we might represent points in Python: | ||
objects. | |||
*We could store the coordinates separately in two | |||
variables, <TT>x</TT> and <TT>y</TT>. | |||
*We could store the coordinates as elements in a list | |||
or tuple. | |||
*We could create a new type to represent points as | |||
objects. | |||
Creating a new type | |||
is (a little) more complicated than the other options, but | is (a little) more complicated than the other options, but | ||
it has advantages that will be apparent soon. | it has advantages that will be apparent soon. | ||
A class definition looks like this: | |||
A user-defined type is also called a '''class'''. | |||
A class definition looks like this: | |||
<PRE CLASS="verbatim">class Point(object): | |||
"""represents a point in 2-D space""" | """represents a point in 2-D space""" | ||
</PRE> | |||
which is a kind of | This header indicates that the new class is a <TT>Point</TT>, | ||
type. | which is a kind of <TT>object</TT>, which is a built-in | ||
type. | |||
The body is a docstring that explains what the class is for. | |||
You can define variables and functions inside a class definition, | You can define variables and functions inside a class definition, | ||
but we will get back to that later. | but we will get back to that later. | ||
Defining a class named <TT>Point</TT> creates a class object. | |||
<PRE CLASS="verbatim">>>> print Point | |||
<class '__main__.Point'> | <class '__main__.Point'> | ||
</PRE> | |||
name” is | Because <TT>Point</TT> is defined at the top level, its “full | ||
name” is <CODE>__main__.Point</CODE>. | |||
Point, you call | |||
The class object is like a factory for creating objects. To create a | |||
Point, you call <TT>Point</TT> as if it were a function. | |||
<PRE CLASS="verbatim">>>> blank = Point() | |||
>>> print blank | >>> print blank | ||
<__main__.Point instance at 0xb7e9d3ac> | <__main__.Point instance at 0xb7e9d3ac> | ||
</PRE> | |||
assign to | The return value is a reference to a Point object, which we | ||
assign to <TT>blank</TT>. | |||
Creating a new object is called | Creating a new object is called | ||
'''instantiation''', and the object is an '''instance''' of | |||
the class. | the class. | ||
When you print an instance, Python tells you what class it | |||
belongs to and where it is stored in memory (the prefix | belongs to and where it is stored in memory (the prefix | ||
<TT>0x</TT> means that the following number is in hexadecimal). | |||
=== 15.2  Attributes === | |||
You can assign values to an instance using dot notation: | |||
<PRE CLASS="verbatim">>>> blank.x = 3.0 | |||
>>> blank.y = 4.0 | >>> blank.y = 4.0 | ||
</PRE> | |||
module, such as | This syntax is similar to the syntax for selecting a variable from a | ||
module, such as <TT>math.pi</TT> or <TT>string.whitespace</TT>. In this case, | |||
though, we are assigning values to named elements of an object. | though, we are assigning values to named elements of an object. | ||
These elements are called | These elements are called '''attributes'''. | ||
syllable, as opposed to “a-TRIB-ute,” which is a verb. | |||
As a noun, “AT-trib-ute” is pronounced with emphasis on the first | |||
syllable, as opposed to “a-TRIB-ute,” which is a verb. | |||
The following diagram shows the result of these assignments. | |||
A state diagram that shows an object and its attributes is | A state diagram that shows an object and its attributes is | ||
called an | called an '''object diagram''': | ||
<DIV CLASS="center"><IMG SRC="book022.png"></DIV> | |||
The variable <TT>blank</TT> refers to a Point object, which | |||
contains two attributes. Each attribute refers to a | contains two attributes. Each attribute refers to a | ||
floating-point number. | floating-point number. | ||
You can read the value of an attribute using the same syntax: | |||
<PRE CLASS="verbatim">>>> print blank.y | |||
4.0 | 4.0 | ||
>>> x = blank.x | >>> x = blank.x | ||
>>> print x | >>> print x | ||
3.0 | 3.0 | ||
</PRE> | |||
refers to and get the value of | The expression <TT>blank.x</TT> means, “Go to the object <TT>blank</TT> | ||
value to a variable named | refers to and get the value of <TT>x</TT>.” In this case, we assign that | ||
the variable | value to a variable named <TT>x</TT>. There is no conflict between | ||
the variable <TT>x</TT> and the attribute <TT>x</TT>. | |||
You can use dot notation as part of any expression. For example: | |||
<PRE CLASS="verbatim">>>> print '(%g, %g)' % (blank.x, blank.y) | |||
(3.0, 4.0) | (3.0, 4.0) | ||
>>> distance = math.sqrt(blank.x**2 + blank.y**2) | >>> distance = math.sqrt(blank.x**2 + blank.y**2) | ||
>>> print distance | >>> print distance | ||
5.0 | 5.0 | ||
</PRE> | |||
For example: | You can pass an instance as an argument in the usual way. | ||
For example: | |||
<PRE CLASS="verbatim">def print_point(p): | |||
print '(%g, %g)' % (p.x, p.y) | print '(%g, %g)' % (p.x, p.y) | ||
</PRE> | |||
mathematical notation. To invoke it, you can pass | <CODE>print_point</CODE> takes a point as an argument and displays it in | ||
an argument: | mathematical notation. To invoke it, you can pass <TT>blank</TT> as | ||
an argument: | |||
<PRE CLASS="verbatim">>>> print_point(blank) | |||
(3.0, 4.0) | (3.0, 4.0) | ||
</PRE> | |||
the function modifies | Inside the function, <TT>p</TT> is an alias for <TT>blank</TT>, so if | ||
Write a function called | the function modifies <TT>p</TT>, <TT>blank</TT> changes. | ||
<DIV CLASS="theorem">'''Exercise 1'''  '' | |||
Write a function called ''''<TT>distance</TT>'''' that it takes two Points | |||
as arguments and returns the distance between them. | as arguments and returns the distance between them. | ||
''</DIV>=== 15.3  Rectangles === | |||
Sometimes it is obvious what the attributes of an object should be, | |||
but other times you have to make decisions. For example, imagine you | but other times you have to make decisions. For example, imagine you | ||
are designing a class to represent rectangles. What attributes would | are designing a class to represent rectangles. What attributes would | ||
you use to specify the location and size of a rectangle? You can | you use to specify the location and size of a rectangle? You can | ||
ignore angle; to keep things simple, assume that the rectangle is | ignore angle; to keep things simple, assume that the rectangle is | ||
either vertical or horizontal. | either vertical or horizontal. | ||
(or the center), the width, and the height. | |||
the other, so we’ll implement the first one, just as an example. | There are at least two possibilities: | ||
*You could specify one corner of the rectangle | |||
(or the center), the width, and the height. | |||
*You could specify two opposing corners. | |||
At this point it is hard to say whether either is better than | |||
the other, so we’ll implement the first one, just as an example. | |||
Here is the class definition: | |||
<PRE CLASS="verbatim">class Rectangle(object): | |||
"""represent a rectangle. | """represent a rectangle. | ||
attributes: width, height, corner. | attributes: width, height, corner. | ||
""" | """ | ||
</PRE> | |||
The docstring lists the attributes: <TT>width</TT> and | |||
specifies the lower-left corner. | <TT>height</TT> are numbers; <TT>corner</TT> is a Point object that | ||
object and assign values to the attributes: | specifies the lower-left corner. | ||
To represent a rectangle, you have to instantiate a Rectangle | |||
object and assign values to the attributes: | |||
<PRE CLASS="verbatim">box = Rectangle() | |||
box.width = 100.0 | box.width = 100.0 | ||
box.height = 200.0 | box.height = 200.0 | ||
| Line 113: | Line 194: | ||
box.corner.x = 0.0 | box.corner.x = 0.0 | ||
box.corner.y = 0.0 | box.corner.y = 0.0 | ||
</PRE> | |||
“Go to the object | The expression <TT>box.corner.x</TT> means, | ||
“Go to the object <TT>box</TT> refers to and select the attribute named | |||
<TT>corner</TT>; then go to that object and select the attribute named | |||
<TT>x</TT>.” | |||
The figure shows the state of this object: | |||
takes a | |||
that contains the coordinates of the center of the | |||
<DIV CLASS="center"><IMG SRC="book023.png"></DIV> | |||
An object that is an attribute of another object is '''embedded'''. | |||
=== 15.4  Instances as return values === | |||
Functions can return instances. For example, <CODE>find_center</CODE> | |||
takes a <TT>Rectangle</TT> as an argument and returns a <TT>Point</TT> | |||
that contains the coordinates of the center of the <TT>Rectangle</TT>: | |||
<PRE CLASS="verbatim">def find_center(box): | |||
p = Point() | p = Point() | ||
p.x = box.corner.x + box.width/2.0 | p.x = box.corner.x + box.width/2.0 | ||
p.y = box.corner.y + box.height/2.0 | p.y = box.corner.y + box.height/2.0 | ||
return p | return p | ||
</PRE> | |||
the resulting Point to | Here is an example that passes <TT>box</TT> as an argument and assigns | ||
the resulting Point to <TT>center</TT>: | |||
<PRE CLASS="verbatim">>>> center = find_center(box) | |||
>>> print_point(center) | >>> print_point(center) | ||
(50.0, 100.0) | (50.0, 100.0) | ||
</PRE>=== 15.5  Objects are mutable === | |||
You can change the state of an object by making an assignment to one of | |||
its attributes. For example, to change the size of a rectangle | its attributes. For example, to change the size of a rectangle | ||
without changing its position, you can modify the values of | without changing its position, you can modify the values of <TT>width</TT> and <TT>height</TT>: | ||
<PRE CLASS="verbatim">box.width = box.width + 50 | |||
box.height = box.width + 100 | box.height = box.width + 100 | ||
</PRE> | |||
You can also write functions that modify objects. For example, | |||
<CODE>grow_rectangle</CODE> takes a Rectangle object and two numbers, | |||
width and height of the rectangle: | <TT>dwidth</TT> and <TT>dheight</TT>, and adds the numbers to the | ||
width and height of the rectangle: | |||
<PRE CLASS="verbatim">def grow_rectangle(rect, dwidth, dheight) : | |||
rect.width += dwidth | rect.width += dwidth | ||
rect.height += dheight | rect.height += dheight | ||
</PRE> | |||
Here is an example that demonstrates the effect: | |||
<PRE CLASS="verbatim">>>> print box.width | |||
100.0 | 100.0 | ||
>>> print box.height | >>> print box.height | ||
| Line 152: | Line 259: | ||
>>> print box.height | >>> print box.height | ||
300.0 | 300.0 | ||
</PRE> | |||
alias for | Inside the function, <TT>rect</TT> is an | ||
alias for <TT>box</TT>, so if the function modifies <TT>rect</TT>, | |||
Write a function named | <TT>box</TT> changes. | ||
a Rectangle and two numbers named | <DIV CLASS="theorem">'''Exercise 2'''  '' | ||
should change the location of the rectangle by adding | Write a function named ''<CODE>''move_rectangle''</CODE>'' that takes | ||
to the | a Rectangle and two numbers named ''''<TT>dx</TT>'''' and ''''<TT>dy</TT>''''. It | ||
to the | should change the location of the rectangle by adding ''''<TT>dx</TT>'''' | ||
to the ''''<TT>x</TT>'''' coordinate of ''''<TT>corner</TT>'''' and adding ''''<TT>dy</TT>'''' | |||
to the ''''<TT>y</TT>'''' coordinate of ''''<TT>corner</TT>''''. | |||
''</DIV>=== 15.6  Copying === | |||
Aliasing can make a program difficult to read because changes | |||
in one place might have unexpected effects in another place. | in one place might have unexpected effects in another place. | ||
It is hard to keep track of all the variables that might refer | It is hard to keep track of all the variables that might refer | ||
to a given object. | to a given object. | ||
The | |||
can duplicate any object: | |||
Copying an object is often an alternative to aliasing. | |||
The <TT>copy</TT> module contains a function called <TT>copy</TT> that | |||
can duplicate any object: | |||
<PRE CLASS="verbatim">>>> p1 = Point() | |||
>>> p1.x = 3.0 | >>> p1.x = 3.0 | ||
>>> p1.y = 4.0 | >>> p1.y = 4.0 | ||
| Line 174: | Line 290: | ||
>>> import copy | >>> import copy | ||
>>> p2 = copy.copy(p1) | >>> p2 = copy.copy(p1) | ||
</PRE> | |||
not the same Point. | <TT>p1</TT> and <TT>p2</TT> contain the same data, but they are | ||
not the same Point. | |||
<PRE CLASS="verbatim">>>> print_point(p1) | |||
(3.0, 4.0) | (3.0, 4.0) | ||
>>> print_point(p2) | >>> print_point(p2) | ||
| Line 183: | Line 301: | ||
>>> p1 == p2 | >>> p1 == p2 | ||
False | False | ||
</PRE> | |||
The <TT>is</TT> operator indicates that <TT>p1</TT> and <TT>p2</TT> are not the | |||
same object, which is what we expected. But you might have expected | same object, which is what we expected. But you might have expected | ||
<TT>==</TT> to yield <TT>True</TT> because these points contain the same | |||
data. In that case, you will be disappointed to learn that for | data. In that case, you will be disappointed to learn that for | ||
instances, the default behavior of the | instances, the default behavior of the <TT>==</TT> operator is the same | ||
as the | as the <TT>is</TT> operator; it checks object identity, not object | ||
equivalence. This behavior can be changed—we’ll see how later. | equivalence. This behavior can be changed—we’ll see how later. | ||
that it copies the Rectangle object but not the embedded Point. | |||
If you use <TT>copy.copy</TT> to duplicate a Rectangle, you will find | |||
that it copies the Rectangle object but not the embedded Point. | |||
<PRE CLASS="verbatim">>>> box2 = copy.copy(box) | |||
>>> box2 is box | >>> box2 is box | ||
False | False | ||
>>> box2.corner is box.corner | >>> box2.corner is box.corner | ||
True | True | ||
</PRE> | |||
Here is what the object diagram looks like: | |||
< | |||
<BR> | |||
<DIV CLASS="center"><IMG SRC="book024.png"></DIV> | |||
<BR> | <BR> | ||
object and any references it contains, but not the embedded objects. | |||
This operation is called a '''shallow copy''' because it copies the | |||
invoking | object and any references it contains, but not the embedded objects. | ||
affect the other, but invoking | |||
affect both! This behavior is confusing and error-prone. | |||
the objects it refers to, and the objects | |||
For most applications, this is not what you want. In this example, | |||
invoking <CODE>grow_rectangle</CODE> on one of the Rectangles would not | |||
affect the other, but invoking <CODE>move_rectangle</CODE> on either would | |||
affect both! This behavior is confusing and error-prone. | |||
Fortunately, the <TT>copy</TT> module contains a method named <TT>deepcopy</TT> that copies not only the object but also | |||
the objects it refers to, and the objects ''they'' refer to, | |||
and so on. | and so on. | ||
You will not be surprised to learn that this operation is | You will not be surprised to learn that this operation is | ||
called a | called a '''deep copy'''. | ||
<PRE CLASS="verbatim">>>> box3 = copy.deepcopy(box) | |||
>>> box3 is box | >>> box3 is box | ||
False | False | ||
>>> box3.corner is box.corner | >>> box3.corner is box.corner | ||
False | False | ||
</PRE> | |||
Write a version of | <TT>box3</TT> and <TT>box</TT> are completely separate objects. | ||
<DIV CLASS="theorem">'''Exercise 3'''  '' | |||
Write a version of ''<CODE>''move_rectangle''</CODE>'' that creates and | |||
returns a new Rectangle instead of modifying the old one. | returns a new Rectangle instead of modifying the old one. | ||
''</DIV>=== 15.7  Debugging === | |||
When you start working with objects, you are likely to encounter | |||
some new exceptions. If you try to access an attribute | some new exceptions. If you try to access an attribute | ||
that doesn’t exist, you get an | that doesn’t exist, you get an <TT>AttributeError</TT>: | ||
<PRE CLASS="verbatim">>>> p = Point() | |||
>>> print p.z | >>> print p.z | ||
AttributeError: Point instance has no attribute 'z' | AttributeError: Point instance has no attribute 'z' | ||
</PRE> | |||
If you are not sure what type an object is, you can ask: | |||
<PRE CLASS="verbatim">>>> type(p) | |||
<type '__main__.Point'> | <type '__main__.Point'> | ||
</PRE> | |||
you can use the built-in function | If you are not sure whether an object has a particular attribute, | ||
you can use the built-in function <TT>hasattr</TT>: | |||
<PRE CLASS="verbatim">>>> hasattr(p, 'x') | |||
True | True | ||
>>> hasattr(p, 'z') | >>> hasattr(p, 'z') | ||
False | False | ||
</PRE> | |||
The first argument can be any object; the second argument is a ''string'' that contains the name of the attribute. | |||
=== 15.8  Glossary === | |||
<DL CLASS="description"><DT CLASS="dt-description">'''class:'''</DT><DD CLASS="dd-description"> A user-defined type. A class definition creates a new | |||
class object. | class object. | ||
</DD><DT CLASS="dt-description">'''class object:'''</DT><DD CLASS="dd-description"> An object that contains information about a | |||
user-defined type. The class object can be used to create instances | user-defined type. The class object can be used to create instances | ||
of the type. | of the type. | ||
</DD><DT CLASS="dt-description">'''instance:'''</DT><DD CLASS="dd-description"> An object that belongs to a class. | |||
</DD><DT CLASS="dt-description">'''attribute:'''</DT><DD CLASS="dd-description"> One of the named values associated with an object. | |||
</DD><DT CLASS="dt-description">'''embedded (object):'''</DT><DD CLASS="dd-description"> An object that is stored as an attribute | |||
of another object. | of another object. | ||
</DD><DT CLASS="dt-description">'''shallow copy:'''</DT><DD CLASS="dd-description"> To copy the contents of an object, including | |||
any references to embedded objects; | any references to embedded objects; | ||
implemented by the | implemented by the <TT>copy</TT> function in the <TT>copy</TT> module. | ||
</DD><DT CLASS="dt-description">'''deep copy:'''</DT><DD CLASS="dd-description"> To copy the contents of an object as well as any | |||
embedded objects, and any objects embedded in them, and so on; | embedded objects, and any objects embedded in them, and so on; | ||
implemented by the | implemented by the <TT>deepcopy</TT> function in the <TT>copy</TT> module. | ||
</DD><DT CLASS="dt-description">'''object diagram:'''</DT><DD CLASS="dd-description"> A diagram that shows objects, their | |||
attributes, and the values of the attributes. | attributes, and the values of the attributes. | ||
</DD></DL>=== 15.9  Exercises === | |||
<DIV CLASS="theorem">'''Exercise 4'''  '' | |||
'' | |||
'' | |||
'''' | |||
'' | |||
''<TT>World.py</TT>'''', which is part of Swampy (see Chapter ''''4''''), | |||
contains a class definition for a user-defined type called | contains a class definition for a user-defined type called | ||
''''<TT>World</TT>''''. If you run this code:'' | |||
<PRE CLASS="verbatim">''from World import * | |||
world = World() | world = World() | ||
wait_for_user() | wait_for_user() | ||
''</PRE> | |||
''A window should appear with a title bar and an empty square. | |||
In this exercise we will use this window to draw Points, | In this exercise we will use this window to draw Points, | ||
Rectangles and other shapes. | Rectangles and other shapes. | ||
Add the following lines before | Add the following lines before | ||
''<CODE>''wait_for_user''</CODE>'' and run the program again'' | |||
'' | |||
'' | |||
<PRE CLASS="verbatim">''canvas = world.ca(width=500, height=500, background='white') | |||
bbox = [[-150,-100], [150, 100]] | bbox = [[-150,-100], [150, 100]] | ||
canvas.rectangle(bbox, outline='black', width=2, fill='green4') | canvas.rectangle(bbox, outline='black', width=2, fill='green4') | ||
''</PRE> | |||
''You should see a green rectangle with a black outline. | |||
The first line creates a Canvas, which appears in the window | The first line creates a Canvas, which appears in the window | ||
as a white square. The Canvas object provides methods like | as a white square. The Canvas object provides methods like | ||
''''<TT>rectangle</TT>'''' for drawing various shapes.'' | |||
''<TT>bbox</TT>'''' is a list of lists that represents the “bounding box” | |||
of the rectangle. The first pair of coordinates is the lower-left | of the rectangle. The first pair of coordinates is the lower-left | ||
corner of the rectangle; the second pair is the upper-right corner. | corner of the rectangle; the second pair is the upper-right corner.'' | ||
circle; the second parameter is the radius. | ''You can draw a circle like this:'' | ||
<PRE CLASS="verbatim">''canvas.circle([-25,0], 70, outline=None, fill='red') | |||
''</PRE> | |||
''The first parameter is the coordinate pair for the center of the | |||
circle; the second parameter is the radius.'' | |||
''If you add this line to the program, | |||
the result should resemble the national flag of Bangladesh | the result should resemble the national flag of Bangladesh | ||
(see | (see ''''<TT>wikipedia.org/wiki/Gallery_of_sovereign-state_flags</TT>'''').'' | ||
*''Write a function called ''<CODE>''draw_rectangle''</CODE>'' that takes a | |||
Canvas and a Rectangle as arguments and draws a | Canvas and a Rectangle as arguments and draws a | ||
representation of the Rectangle on the Canvas. | representation of the Rectangle on the Canvas.'' | ||
modify | |||
the fill color. | *''Add an attribute named ''''<TT>color</TT>'''' to your Rectangle objects and | ||
modify ''<CODE>''draw_rectangle''</CODE>'' so that it uses the color attribute as | |||
the fill color.'' | |||
*''Write a function called ''<CODE>''draw_point''</CODE>'' that takes a | |||
Canvas and a Point as arguments and draws a | Canvas and a Point as arguments and draws a | ||
representation of the Point on the Canvas. | representation of the Point on the Canvas.'' | ||
*''Define a new class called Circle with appropriate attributes and | |||
instantiate a few Circle objects. Write a function called | instantiate a few Circle objects. Write a function called | ||
''<CODE>''draw_circle''</CODE>'' that draws circles on the canvas.'' | |||
Hint: you can draw a polygon like this: | |||
*''Write a program that draws the national flag of of the Czech Republic. | |||
Hint: you can draw a polygon like this:''<PRE CLASS="verbatim">''points = [[-150,-100], [150, 100], [150, -100]] | |||
canvas.polygon(points, fill='blue') | canvas.polygon(points, fill='blue') | ||
''</PRE> | |||
you can download it from | '' | ||
'' | |||
''I have written a small program that lists the available colors; | |||
you can download it from ''''<TT>thinkpython.com/code/color_list.py</TT>''''.'' | |||
</DIV><HR> | |||
<IMG SRC="previous_motif.gif" ALT="Previous"> | |||
<IMG SRC="contents_motif.gif" ALT="Up"> | |||
<IMG SRC="next_motif.gif" ALT="Next"> | |||
Revision as of 23:09, 15 September 2008
Chapter 15 Classes and objects
15.1 User-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:
- We could store the coordinates separately in two
variables, x and y.
- We could store the coordinates as elements in a list
or tuple.
- We could create a new type to represent points as
objects.
Creating a new type is (a little) more complicated than the other options, but it has advantages that will be apparent soon.
A user-defined type is also called a class. A class definition looks like this:
class Point(object):
"""represents a point in 2-D space"""
This header indicates that the new class is a Point, which is a kind of object, which is a built-in type.
The body is a docstring that explains what the class is for.
You can define variables and functions inside a class definition,
but we will get back to that later.
Defining a class named Point creates a class object.
>>> print 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() >>> print blank <__main__.Point instance 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).
15.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.
As a noun, “AT-trib-ute” is pronounced with emphasis on the first syllable, as opposed to “a-TRIB-ute,” which is a verb.
The following diagram shows the result of these assignments. A state diagram that shows an object and its attributes is called an object diagram:
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:
>>> print blank.y 4.0 >>> x = blank.x >>> print x 3.0
The expression blank.x means, “Go to the object blank refers to and get the value of x.” In this case, 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:
>>> print '(%g, %g)' % (blank.x, blank.y) (3.0, 4.0) >>> distance = math.sqrt(blank.x**2 + blank.y**2) >>> print distance 5.0
You can pass an instance as an argument in the usual way. 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:
>>> print_point(blank) (3.0, 4.0)
Inside the function, p is an alias for blank, so if the function modifies p, blank changes.
Write a function called 'distance' that it takes two Points as arguments and returns the distance between them.
=== 15.3 Rectangles ===
Sometimes it is obvious what the attributes of an object should be, but other times you have to make decisions. For example, imagine you are designing a class to represent rectangles. What attributes would you use to specify the location and size of a rectangle? You can ignore angle; to keep things simple, assume that the rectangle is either vertical or horizontal.
There are at least two possibilities:
- You could specify one corner of the rectangle
(or the center), the width, and the height.
- You could specify two opposing corners.
At this point it is hard to say whether either is better than the other, so we’ll implement the first one, just as an example.
Here is the class definition:
class Rectangle(object):
"""represent 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.”
The figure shows the state of this object:
An object that is an attribute of another object is embedded.
15.4 Instances as return values
Functions can return instances. For example, find_center
takes a Rectangle as an argument and returns a Point
that contains the coordinates of the center of the Rectangle:
def find_center(box):
p = Point()
p.x = box.corner.x + box.width/2.0
p.y = box.corner.y + box.height/2.0
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.0, 100.0)
=== 15.5 Objects are mutable ===
You can change the state of an object by making an assignment to one of
its attributes. For example, to change the size of a rectangle
without changing its position, you can modify the values of width and height:
box.width = box.width + 50 box.height = box.width + 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:
def grow_rectangle(rect, dwidth, dheight) :
rect.width += dwidth
rect.height += dheight
Here is an example that demonstrates the effect:
>>> print box.width 100.0 >>> print box.height 200.0 >>> grow_rectangle(box, 50, 100) >>> print box.width 150.0 >>> print box.height 300.0
Inside the function, rect is an alias for box, so if the function modifies rect, box changes.
Write a function named move_rectangle that takes
a Rectangle and two numbers named 'dx' and 'dy'. It
should change the location of the rectangle by adding 'dx'
to the 'x' coordinate of 'corner' and adding 'dy'
to the 'y' coordinate of 'corner'.
=== 15.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.0, 4.0) >>> print_point(p2) (3.0, 4.0) >>> 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. This behavior can be changed—we’ll see how later.
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
Here is 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 contains 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.
Write a version of move_rectangle that creates and
returns a new Rectangle instead of modifying the old one.
=== 15.7 Debugging ===
When you start working with objects, you are likely to encounter
some new exceptions. If you try to access an attribute
that doesn’t exist, you get an AttributeError:
>>> p = Point() >>> print p.z AttributeError: Point instance has no attribute 'z'
If you are not sure what type an object is, you can ask:
>>> type(p) <type '__main__.Point'>
If you are not sure whether an object has a particular attribute, you can use the built-in function hasattr:
>>> hasattr(p, 'x') True >>> hasattr(p, 'z') False
The first argument can be any object; the second argument is a string that contains the name of the attribute.
15.8 Glossary
- class:
- A user-defined type. A class definition creates a new class object.
- class object:
- An object that contains information about a user-defined type. The class object can be used to create instances of the type.
- instance:
- An object that belongs to a class.
- attribute:
- One of the named values associated with an object.
- embedded (object):
- An object that is stored as an attribute of another object.
- shallow copy:
- To copy the contents of an object, including any references to embedded objects; implemented by the copy function in the copy module.
- deep copy:
- To copy the contents of an object as well as any embedded objects, and any objects embedded in them, and so on; implemented by the deepcopy function in the copy module.
- object diagram:
- A diagram that shows objects, their attributes, and the values of the attributes.
=== 15.9 Exercises ===
'
World.py'', which is part of Swampy (see Chapter '4'), contains a class definition for a user-defined type called 'World'. If you run this code:
''from World import * world = World() wait_for_user() ''
A window should appear with a title bar and an empty square.
In this exercise we will use this window to draw Points,
Rectangles and other shapes.
Add the following lines before
wait_for_user and run the program again
''canvas = world.ca(width=500, height=500, background='white') bbox = [[-150,-100], [150, 100]] canvas.rectangle(bbox, outline='black', width=2, fill='green4') ''
You should see a green rectangle with a black outline. The first line creates a Canvas, which appears in the window as a white square. The Canvas object provides methods like 'rectangle' for drawing various shapes.
bbox'' is a list of lists that represents the “bounding box” of the rectangle. The first pair of coordinates is the lower-left corner of the rectangle; the second pair is the upper-right corner.
You can draw a circle like this:
''canvas.circle([-25,0], 70, outline=None, fill='red') ''
The first parameter is the coordinate pair for the center of the circle; the second parameter is the radius.
If you add this line to the program, the result should resemble the national flag of Bangladesh (see 'wikipedia.org/wiki/Gallery_of_sovereign-state_flags').
- Write a function called
draw_rectanglethat takes a
Canvas and a Rectangle as arguments and draws a representation of the Rectangle on the Canvas.
- Add an attribute named 'color' to your Rectangle objects and
modify draw_rectangle so that it uses the color attribute as
the fill color.
- Write a function called
draw_pointthat takes a
Canvas and a Point as arguments and draws a representation of the Point on the Canvas.
- Define a new class called Circle with appropriate attributes and
instantiate a few Circle objects. Write a function called
draw_circle that draws circles on the canvas.
- Write a program that draws the national flag of of the Czech Republic.
''points = [[-150,-100], [150, 100], [150, -100]] canvas.polygon(points, fill='blue') ''
I have written a small program that lists the available colors; you can download it from 'thinkpython.com/code/color_list.py'.
<IMG SRC="previous_motif.gif" ALT="Previous"> <IMG SRC="contents_motif.gif" ALT="Up"> <IMG SRC="next_motif.gif" ALT="Next">