What is the correct syntax for defining a class called game in Python?

Python is an object-oriented programming language, which means that it provides features that support object-oriented programming ( OOP).

Object-oriented programming has its roots in the 1960s, but it wasn’t until the mid 1980s that it became the main programming paradigm used in the creation of new software. It was developed as a way to handle the rapidly increasing size and complexity of software systems, and to make it easier to modify these large and complex systems over time.

Up to now we have been writing programs using a procedural programming paradigm. In procedural programming the focus is on writing functions or procedures which operate on data. In object-oriented programming the focus is on the creation of objects which contain both data and functionality together.

7.2. User-defined compound types¶

We will now introduce a new Python keyword, class, which in essence defines a new data type. We have been using several of Python’s built-in types throughout this book, we are now ready to create our own user-defined type: the

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5.

Consider the concept of a mathematical point. In two dimensions, a point is two numbers (coordinates) that are treated collectively as a single object. In mathematical notation, points are often written in parentheses with a comma separating the coordinates. For example,

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
6 represents the origin, and
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
7 represents the point
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
8 units to the right and
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
9 units up from the origin.

A natural way to represent a point in Python is with two numeric values. The question, then, is how to group these two values into a compound object. The quick and dirty solution is to use a list or tuple, and for some applications that might be the best choice.

An alternative is to define a new user-defined compound type, called a class. This approach involves a bit more effort, but it has advantages that will be apparent soon.

A class definition looks like this:

class Point:
    pass

Class definitions can appear anywhere in a program, but they are usually near the beginning (after the

>>> p2 = Point()
>>> p2.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'x'
>>>
0 statements). The syntax rules for a class definition are the same as for other compound statements. There is a header which begins with the keyword,
>>> p2 = Point()
>>> p2.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'x'
>>>
1, followed by the name of the class, and ending with a colon.

This definition creates a new class called

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5. The pass statement has no effect; it is only necessary because a compound statement must have something in its body. A docstring could serve the same purpose:

class Point:
    "Point class for storing mathematical points."

By creating the

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 class, we created a new type, also called
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5. The members of this type are called instances of the type or objects. Creating a new instance is called instantiation, and is accomplished by calling the class. Classes, like functions, are callable, and we instantiate a
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 object by calling the
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 class:

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>

The variable

>>> p2 = Point()
>>> p2.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'x'
>>>
7 is assigned a reference to a new
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 object.

It may be helpful to think of a class as a factory for making objects, so our

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 class is a factory for making points. The class itself isn’t an instance of a point, but it contains the machinary to make point instances.

7.3. Attributes¶

Like real world objects, object instances have both form and function. The form consists of data elements contained within the instance.

We can add new data elements to an instance using dot notation:

>>> p.x = 3
>>> p.y = 4

This syntax is similar to the syntax for selecting a variable from a module, such as

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
0 or
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
1. Both modules and instances create their own namespaces, and the syntax for accessing names contained in each, called attributes, is the same. In this case the attribute we are selecting is a data item from an instance.

The following state diagram shows the result of these assignments:

What is the correct syntax for defining a class called game in Python?

The variable

>>> p2 = Point()
>>> p2.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'x'
>>>
7 refers to a Point object, which contains two attributes. Each attribute refers to a number.

We can read the value of an attribute using the same syntax:

>>> print(p.y)
4
>>> x = p.x
>>> print(x)
3

The expression

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
3 means, “Go to the object
>>> p2 = Point()
>>> p2.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'x'
>>>
7 refers to and get the value of
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
8”. In this case, we assign that value to a variable named
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
8. There is no conflict between the variable
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
8 and the attribute
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
8. The purpose of dot notation is to identify which variable you are referring to unambiguously.

You can use dot notation as part of any expression, so the following statements are legal:

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y

The first line outputs

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
9; the second line calculates the value 25.

7.4. The initialization method and class Point: def __init__(self, x, y): self.x = x self.y = y def distance_from_origin(self): return ((self.x ** 2) + (self.y ** 2)) ** 0.5 0¶

Since our

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 class is intended to represent two dimensional mathematical points, all point instances ought to have
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
8 and
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
9 attributes, but that is not yet so with our
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 objects.

>>> p2 = Point()
>>> p2.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'x'
>>>

To solve this problem we add an initialization method to our class.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

A method behaves like a function but it is part of an object. Like a data attribute it is accessed using dot notation.

The initialization method is a special method that is invoked automatically when an object is created by calling the class. The name of this method is

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
5 (two underscore characters, followed by
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
6, and then two more underscores). This name must be used to make a method an initialization method in Python.

There is no conflict between the attribute

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
7 and the parameter
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
8. Dot notation specifies which variable we are referring to.

Let’s add another method,

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
9, to see better how methods work:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5

Let’s create a few point instances, look at their attributes, and call our new method on them:

>>> p = Point(3, 4)
>>> p.x
3
>>> p.y
4
>>> p.distance_from_origin()
5.0
>>> q = Point(5, 12)
>>> q.x
5
>>> q.y
12
>>> q.distance_from_origin()
13.0
>>> r = Point(0, 0)
>>> r.x
0
>>> r.y
0
>>> r.distance_from_origin()
0.0

When defining a method, the first parameter refers to the instance being created. It is customary to name this parameter self. In the example session above, the

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
0 parameter refers to the instances
>>> p2 = Point()
>>> p2.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'x'
>>>
7,
>>> p = Point(3, 4)
>>> p.x
3
>>> p.y
4
>>> p.distance_from_origin()
5.0
>>> q = Point(5, 12)
>>> q.x
5
>>> q.y
12
>>> q.distance_from_origin()
13.0
>>> r = Point(0, 0)
>>> r.x
0
>>> r.y
0
>>> r.distance_from_origin()
0.0
2, and
>>> p = Point(3, 4)
>>> p.x
3
>>> p.y
4
>>> p.distance_from_origin()
5.0
>>> q = Point(5, 12)
>>> q.x
5
>>> q.y
12
>>> q.distance_from_origin()
13.0
>>> r = Point(0, 0)
>>> r.x
0
>>> r.y
0
>>> r.distance_from_origin()
0.0
3 respectively.

7.5. Instances as parameters¶

You can pass an instance as a parameter to a function in the usual way. For example:

class Point:
    "Point class for storing mathematical points."
0

>>> p = Point(3, 4)
>>> p.x
3
>>> p.y
4
>>> p.distance_from_origin()
5.0
>>> q = Point(5, 12)
>>> q.x
5
>>> q.y
12
>>> q.distance_from_origin()
13.0
>>> r = Point(0, 0)
>>> r.x
0
>>> r.y
0
>>> r.distance_from_origin()
0.0
4 takes a point as an argument and displays it in the standard format. If you call
>>> p = Point(3, 4)
>>> p.x
3
>>> p.y
4
>>> p.distance_from_origin()
5.0
>>> q = Point(5, 12)
>>> q.x
5
>>> q.y
12
>>> q.distance_from_origin()
13.0
>>> r = Point(0, 0)
>>> r.x
0
>>> r.y
0
>>> r.distance_from_origin()
0.0
5 with point
>>> p2 = Point()
>>> p2.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'x'
>>>
7 as defined previously, the output is
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
9.

To convert

>>> p = Point(3, 4)
>>> p.x
3
>>> p.y
4
>>> p.distance_from_origin()
5.0
>>> q = Point(5, 12)
>>> q.x
5
>>> q.y
12
>>> q.distance_from_origin()
13.0
>>> r = Point(0, 0)
>>> r.x
0
>>> r.y
0
>>> r.distance_from_origin()
0.0
4 to a method, do the following:

  1. Indent the function definition so that it is inside the class definition.

  2. Rename the parameter to

    class Point:
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def distance_from_origin(self):
            return ((self.x ** 2) + (self.y ** 2)) ** 0.5
    
    0.

class Point:
    "Point class for storing mathematical points."
1

We can now invoke the method using dot notation.

class Point:
    "Point class for storing mathematical points."
2

The object on which the method is invoked is assigned to the first parameter, so in this case

>>> p2 = Point()
>>> p2.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'x'
>>>
7 is assigned to the parameter
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
0. By convention, the first parameter of a method is called
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
0. The reason for this is a little convoluted, but it is based on a useful metaphor.

The syntax for a function call,

>>> p = Point(3, 4)
>>> p.x
3
>>> p.y
4
>>> p.distance_from_origin()
5.0
>>> q = Point(5, 12)
>>> q.x
5
>>> q.y
12
>>> q.distance_from_origin()
13.0
>>> r = Point(0, 0)
>>> r.x
0
>>> r.y
0
>>> r.distance_from_origin()
0.0
5, suggests that the function is the active agent. It says something like, Hey
>>> p = Point(3, 4)
>>> p.x
3
>>> p.y
4
>>> p.distance_from_origin()
5.0
>>> q = Point(5, 12)
>>> q.x
5
>>> q.y
12
>>> q.distance_from_origin()
13.0
>>> r = Point(0, 0)
>>> r.x
0
>>> r.y
0
>>> r.distance_from_origin()
0.0
4! Here’s an object for you to print.

In object-oriented programming, the objects are the active agents. An invocation like

class Point:
    "Point class for storing mathematical points."
05 says Hey
>>> p2 = Point()
>>> p2.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'x'
>>>
7! Please print yourself!

This change in perspective might be more polite, but it is not obvious that it is useful. In the examples we have seen so far, it may not be. But sometimes shifting responsibility from the functions onto the objects makes it possible to write more versatile functions, and makes it easier to maintain and reuse code.

7.6. Object-oriented features¶

It is not easy to define object-oriented programming, but we have already seen some of its characteristics:

  1. Programs are made up of class definitions which contain attributes that can be data (instance variables) or behaviors (methods).

  2. Each object definition corresponds to some object or concept in the real world, and the functions that operate on that object correspond to the ways real-world objects interact.

  3. Most of the computation is expressed in terms of operations on objects.

For example, the

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 class corresponds to the mathematical concept of a point.

7.7. Time¶

As another example of a user-defined type, we’ll define a class called

class Point:
    "Point class for storing mathematical points."
08 that records the time of day. Since times will need hours, minutes, and second attributes, we’ll start with an initialization method similar to the one we created for Points.

The class definition looks like this:

class Point:
    "Point class for storing mathematical points."
3

When we call the

class Point:
    "Point class for storing mathematical points."
08 class, the arguments we provide are passed along to
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
6:

class Point:
    "Point class for storing mathematical points."
4

Here is a

class Point:
    "Point class for storing mathematical points."
11 method for our
class Point:
    "Point class for storing mathematical points."
08 objects that uses string formating to display minutes and seconds with two digits.

To save space, we will leave out the initialization method, but you should include it:

class Point:
    "Point class for storing mathematical points."
5

which we can now invoke on time instances in the usual way:

class Point:
    "Point class for storing mathematical points."
6

7.8. Optional arguments¶

We have seen built-in functions that take a variable number of arguments. For example,

class Point:
    "Point class for storing mathematical points."
13 can take two, three, or four arguments.

It is possible to write user-defined functions with optional argument lists. For example, we can upgrade our own version of

class Point:
    "Point class for storing mathematical points."
14 to do the same thing as
class Point:
    "Point class for storing mathematical points."
13.

This is the original version:

class Point:
    "Point class for storing mathematical points."
7

This is the new and improved version:

class Point:
    "Point class for storing mathematical points."
8

The third parameter,

class Point:
    "Point class for storing mathematical points."
16, is optional because a default value,
class Point:
    "Point class for storing mathematical points."
17, is provided. If we invoke
class Point:
    "Point class for storing mathematical points."
14 with only two arguments, we use the default value and start from the beginning of the string:

class Point:
    "Point class for storing mathematical points."
9

If we provide a third parameter, it overrides the default:

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
0

We can rewrite our initialization method for the

class Point:
    "Point class for storing mathematical points."
08 class so that
class Point:
    "Point class for storing mathematical points."
20,
class Point:
    "Point class for storing mathematical points."
21, and
class Point:
    "Point class for storing mathematical points."
22 are each optional arguments.

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
1

When we instantiate a

class Point:
    "Point class for storing mathematical points."
08 object, we can pass in values for the three parameters, as we did with

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
2

Because the parameters are now optional, however, we can omit them:

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
3

Or provide only the first parameter:

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
4

Or the first two parameters:

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
5

Finally, we can provide a subset of the parameters by naming them explicitly:

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
6

7.9. Another method¶

Let’s add a method

class Point:
    "Point class for storing mathematical points."
24, which increments a time instance by a given number of seconds. To save space, we will continue to leave out previously defined methods, but you should always keep them in your version:

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
7

Now we can invoke

class Point:
    "Point class for storing mathematical points."
24 on a time instance.

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
8

Again, the object on which the method is invoked gets assigned to the first parameter,

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
0. The second parameter,
class Point:
    "Point class for storing mathematical points."
22 gets the value
class Point:
    "Point class for storing mathematical points."
28.

7.10. An example with two class Point: "Point class for storing mathematical points." 08s¶

Let’s add a boolen method,

class Point:
    "Point class for storing mathematical points."
30, that takes two time instances and returns
class Point:
    "Point class for storing mathematical points."
31 when the first one is chronologically after the second.

We can only convert one of the parameters to

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
0; the other we will call
class Point:
    "Point class for storing mathematical points."
33, and it will have to be a parameter of the method.

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
9

We invoke this method on one object and pass the other as an argument:

>>> p.x = 3
>>> p.y = 4
0

You can almost read the invocation like English: If time1 is after time2, then…

7.10.1. Pure functions and modifiers (again)¶

In the next few sections, we’ll write two versions of a function called

class Point:
    "Point class for storing mathematical points."
34, which calculates the sum of two
class Point:
    "Point class for storing mathematical points."
08s. They will demonstrate two kinds of functions: pure functions and modifiers, which we first encountered in the Functions chapter.

The following is a rough version of

class Point:
    "Point class for storing mathematical points."
34:

>>> p.x = 3
>>> p.y = 4
1

The function creates a new

class Point:
    "Point class for storing mathematical points."
08 object, initializes its attributes, and returns a reference to the new object. This is called a pure function because it does not modify any of the objects passed to it as parameters and it has no side effects, such as displaying a value or getting user input.

Here is an example of how to use this function. We’ll create two

class Point:
    "Point class for storing mathematical points."
08 objects:
class Point:
    "Point class for storing mathematical points."
39, which contains the current time; and
class Point:
    "Point class for storing mathematical points."
40, which contains the amount of time it takes for a breadmaker to make bread. Then we’ll use
class Point:
    "Point class for storing mathematical points."
34 to figure out when the bread will be done. If you haven’t finished writing
class Point:
    "Point class for storing mathematical points."
11 yet, take a look ahead to Section before you try this:

>>> p.x = 3
>>> p.y = 4
2

The output of this program is

class Point:
    "Point class for storing mathematical points."
43, which is correct. On the other hand, there are cases where the result is not correct. Can you think of one?

The problem is that this function does not deal with cases where the number of seconds or minutes adds up to more than sixty. When that happens, we have to carry the extra seconds into the minutes column or the extra minutes into the hours column.

Here’s a second corrected version of the function:

>>> p.x = 3
>>> p.y = 4
3

Although this function is correct, it is starting to get big. Later we will suggest an alternative approach that yields shorter code.

7.10.2. Modifiers¶

There are times when it is useful for a function to modify one or more of the objects it gets as parameters. Usually, the caller keeps a reference to the objects it passes, so any changes the function makes are visible to the caller. Functions that work this way are called modifiers.

class Point:
    "Point class for storing mathematical points."
24, which adds a given number of seconds to a
class Point:
    "Point class for storing mathematical points."
08 object, would be written most naturally as a modifier. A rough draft of the function looks like this:

>>> p.x = 3
>>> p.y = 4
4

The first line performs the basic operation; the remainder deals with the special cases we saw before.

Is this function correct? What happens if the parameter

class Point:
    "Point class for storing mathematical points."
22 is much greater than sixty? In that case, it is not enough to carry once; we have to keep doing it until
class Point:
    "Point class for storing mathematical points."
22 is less than sixty. One solution is to replace the
class Point:
    "Point class for storing mathematical points."
48 statements with
class Point:
    "Point class for storing mathematical points."
49 statements:

>>> p.x = 3
>>> p.y = 4
5

This function is now correct, but it is not the most efficient solution.

7.11. Prototype development versus planning¶

So far in this chapter, we’ve used an approach to program development that we’ll call prototype development. We wrote a rough draft (or prototype) that performed the basic calculation and then tested it on a few cases, correcting flaws as we found them.

Although this approach can be effective, it can lead to code that is unnecessarily complicated – since it deals with many special cases – and unreliable – since it is hard to know if we’ve found all the errors.

An alternative is planned development, in which high-level insight into the problem can make the programming much easier. In this case, the insight is that a

class Point:
    "Point class for storing mathematical points."
08 object is really a three-digit number in base 60! The
class Point:
    "Point class for storing mathematical points."
51 component is the ones column, the
class Point:
    "Point class for storing mathematical points."
52 component is the sixties column, and the
class Point:
    "Point class for storing mathematical points."
53 component is the thirty-six hundreds column.

When we wrote

class Point:
    "Point class for storing mathematical points."
34 and
class Point:
    "Point class for storing mathematical points."
24, we were effectively doing addition in base 60, which is why we had to carry from one column to the next.

This observation suggests another approach to the whole problem – we can convert a

class Point:
    "Point class for storing mathematical points."
08 object into a single number and take advantage of the fact that the computer knows how to do arithmetic with numbers. The following function converts a
class Point:
    "Point class for storing mathematical points."
08 object into an integer:

>>> p.x = 3
>>> p.y = 4
6

Now, all we need is a way to convert from an integer to a

class Point:
    "Point class for storing mathematical points."
08 object:

>>> p.x = 3
>>> p.y = 4
7

You might have to think a bit to convince yourself that this technique to convert from one base to another is correct. Assuming you are convinced, you can use these functions to rewrite

class Point:
    "Point class for storing mathematical points."
34:

>>> p.x = 3
>>> p.y = 4
8

This version is much shorter than the original, and it is much easier to demonstrate that it is correct (assuming, as usual, that the functions it calls are correct).

7.12. Generalization¶

In some ways, converting from base 60 to base 10 and back is harder than just dealing with times. Base conversion is more abstract; our intuition for dealing with times is better.

But if we have the insight to treat times as base 60 numbers and make the investment of writing the conversion functions (

class Point:
    "Point class for storing mathematical points."
60 and
class Point:
    "Point class for storing mathematical points."
61), we get a program that is shorter, easier to read and debug, and more reliable.

It is also easier to add features later. For example, imagine subtracting two

class Point:
    "Point class for storing mathematical points."
08s to find the duration between them. The naive approach would be to implement subtraction with borrowing. Using the conversion functions would be easier and more likely to be correct.

Ironically, sometimes making a problem harder (or more general) makes it easier (because there are fewer special cases and fewer opportunities for error).

7.13. Algorithms¶

When you write a general solution for a class of problems, as opposed to a specific solution to a single problem, you have written an algorithm. We mentioned this word before but did not define it carefully. It is not easy to define, so we will try a couple of approaches.

First, consider something that is not an algorithm. When you learned to multiply single-digit numbers, you probably memorized the multiplication table. In effect, you memorized 100 specific solutions. That kind of knowledge is not algorithmic.

But if you were lazy, you probably cheated by learning a few tricks. For example, to find the product of

class Point:
    "Point class for storing mathematical points."
63 and 9, you can write
class Point:
    "Point class for storing mathematical points."
64 as the first digit and
class Point:
    "Point class for storing mathematical points."
65 as the second digit. This trick is a general solution for multiplying any single-digit number by 9. That’s an algorithm!

Similarly, the techniques you learned for addition with carrying, subtraction with borrowing, and long division are all algorithms. One of the characteristics of algorithms is that they do not require any intelligence to carry out. They are mechanical processes in which each step follows from the last according to a simple set of rules.

In my opinion, it is embarrassing that humans spend so much time in school learning to execute algorithms that, quite literally, require no intelligence.

On the other hand, the process of designing algorithms is interesting, intellectually challenging, and a central part of what we call programming.

Some of the things that people do naturally, without difficulty or conscious thought, are the hardest to express algorithmically. Understanding natural language is a good example. We all do it, but so far no one has been able to explain how we do it, at least not in the form of an algorithm.

7.14. Points revisited¶

Let’s rewrite the

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 class in a more object- oriented style:

>>> p.x = 3
>>> p.y = 4
9

The next method,

class Point:
    "Point class for storing mathematical points."
67, returns a string representation of a
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 object. If a class provides a method named
class Point:
    "Point class for storing mathematical points."
67, it overrides the default behavior of the Python built-in
class Point:
    "Point class for storing mathematical points."
70 function.

>>> print(p.y)
4
>>> x = p.x
>>> print(x)
3
0

Printing a

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 object implicitly invokes
class Point:
    "Point class for storing mathematical points."
67 on the object, so defining
class Point:
    "Point class for storing mathematical points."
67 also changes the behavior of
class Point:
    "Point class for storing mathematical points."
74:

>>> print(p.y)
4
>>> x = p.x
>>> print(x)
3
1

When we write a new class, we almost always start by writing

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
5, which makes it easier to instantiate objects, and
class Point:
    "Point class for storing mathematical points."
67, which is almost always useful for debugging.

7.15. Operator overloading¶

Some languages make it possible to change the definition of the built-in operators when they are applied to user-defined types. This feature is called operator overloading. It is especially useful when defining new mathematical types.

For example, to override the addition operator

class Point:
    "Point class for storing mathematical points."
77, we provide a method named
class Point:
    "Point class for storing mathematical points."
78:

>>> print(p.y)
4
>>> x = p.x
>>> print(x)
3
2

As usual, the first parameter is the object on which the method is invoked. The second parameter is conveniently named

class Point:
    "Point class for storing mathematical points."
33 to distinguish it from
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_from_origin(self):
        return ((self.x ** 2) + (self.y ** 2)) ** 0.5
0. To add two
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5s, we create and return a new
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 that contains the sum of the
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
8 coordinates and the sum of the
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
9 coordinates.

Now, when we apply the

class Point:
    "Point class for storing mathematical points."
77 operator to
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 objects, Python invokes
class Point:
    "Point class for storing mathematical points."
78:

>>> print(p.y)
4
>>> x = p.x
>>> print(x)
3
3

The expression

class Point:
    "Point class for storing mathematical points."
88 is equivalent to
class Point:
    "Point class for storing mathematical points."
89, but obviously more elegant. As an exercise, add a method
class Point:
    "Point class for storing mathematical points."
90 that overloads the subtraction operator, and try it out. There are several ways to override the behavior of the multiplication operator: by defining a method named
class Point:
    "Point class for storing mathematical points."
91, or
class Point:
    "Point class for storing mathematical points."
92, or both.

If the left operand of

class Point:
    "Point class for storing mathematical points."
93 is a
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5, Python invokes
class Point:
    "Point class for storing mathematical points."
91, which assumes that the other operand is also a
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5. It computes the dot product of the two points, defined according to the rules of linear algebra:

>>> print(p.y)
4
>>> x = p.x
>>> print(x)
3
4

If the left operand of

class Point:
    "Point class for storing mathematical points."
93 is a primitive type and the right operand is a
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5, Python invokes
class Point:
    "Point class for storing mathematical points."
92, which performs scalar multiplication:

>>> print(p.y)
4
>>> x = p.x
>>> print(x)
3
5

The result is a new

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 whose coordinates are a multiple of the original coordinates. If
class Point:
    "Point class for storing mathematical points."
33 is a type that cannot be multiplied by a floating-point number, then
class Point:
    "Point class for storing mathematical points."
92 will yield an error.

This example demonstrates both kinds of multiplication:

>>> print(p.y)
4
>>> x = p.x
>>> print(x)
3
6

What happens if we try to evaluate

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
03? Since the first parameter is a
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5, Python invokes
class Point:
    "Point class for storing mathematical points."
91 with
>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
06 as the second argument. Inside
class Point:
    "Point class for storing mathematical points."
91, the program tries to access the
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
8 coordinate of
class Point:
    "Point class for storing mathematical points."
33, which fails because an integer has no attributes:

>>> print(p.y)
4
>>> x = p.x
>>> print(x)
3
7

Unfortunately, the error message is a bit opaque. This example demonstrates some of the difficulties of object-oriented programming. Sometimes it is hard enough just to figure out what code is running.

For a more complete example of operator overloading, see Appendix (reference overloading).

7.16. Polymorphism¶

Most of the methods we have written only work for a specific type. When you create a new object, you write methods that operate on that type.

But there are certain operations that you will want to apply to many types, such as the arithmetic operations in the previous sections. If many types support the same set of operations, you can write functions that work on any of those types.

For example, the

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
10 operation (which is common in linear algebra) takes three parameters; it multiplies the first two and then adds the third. We can write it in Python like this:

>>> print(p.y)
4
>>> x = p.x
>>> print(x)
3
8

This method will work for any values of

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
8 and
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
9 that can be multiplied and for any value of
>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
13 that can be added to the product.

We can invoke it with numeric values:

>>> print(p.y)
4
>>> x = p.x
>>> print(x)
3
9

Or with

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5s:

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
0

In the first case, the

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 is multiplied by a scalar and then added to another
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5. In the second case, the dot product yields a numeric value, so the third parameter also has to be a numeric value.

A function like this that can take parameters with different types is called polymorphic.

As another example, consider the method

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
17, which prints a list twice, forward and backward:

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
1

Because the

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
18 method is a modifier, we make a copy of the list before reversing it. That way, this method doesn’t modify the list it gets as a parameter.

Here’s an example that applies

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
17 to a list:

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
2

Of course, we intended to apply this function to lists, so it is not surprising that it works. What would be surprising is if we could apply it to a

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5.

To determine whether a function can be applied to a new type, we apply the fundamental rule of polymorphism: If all of the operations inside the function can be applied to the type, the function can be applied to the type. The operations in the method include

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
21,
>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
18, and
class Point:
    "Point class for storing mathematical points."
74.

>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
21 works on any object, and we have already written a
class Point:
    "Point class for storing mathematical points."
67 method for
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5s, so all we need is a
>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
18 method in the
print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 class:

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
3

Then we can pass

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5s to
>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
17:

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
4

The best kind of polymorphism is the unintentional kind, where you discover that a function you have already written can be applied to a type for which you never planned.

7.17. Glossary¶

class

A user-defined compound type. A class can also be thought of as a template for the objects that are instances of it.

instantiate

To create an instance of a class.

instance

An object that belongs to a class.

object

A compound data type that is often used to model a thing or concept in the real world.

attribute

One of the named data items that makes up an instance.

pure function

A function that does not modify any of the objects it receives as parameters. Most pure functions are fruitful.

modifier

A function that changes one or more of the objects it receives as parameters. Most modifiers are void.

functional programming style

A style of program design in which the majority of functions are pure.

prototype development

A way of developing programs starting with a prototype and gradually testing and improving it.

planned development

A way of developing programs that involves high-level insight into the problem and more planning than incremental development or prototype development.

object-oriented language

A language that provides features, such as user-defined classes and inheritance, that facilitate object-oriented programming.

object-oriented programming

A style of programming in which data and the operations that manipulate it are organized into classes and methods.

method

A function that is defined inside a class definition and is invoked on instances of that class. :override:: To replace a default. Examples include replacing a default parameter with a particular argument and replacing a default method by providing a new method with the same name.

initialization method

A special method that is invoked automatically when a new object is created and that initializes the object’s attributes.

operator overloading

Extending built-in operators (

class Point:
    "Point class for storing mathematical points."
77,
>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
32,
class Point:
    "Point class for storing mathematical points."
93,
>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
34,
>>> type(Point)
<class 'type'>
>>> p = Point()
>>> type(p)
<class '__main__.Point'>
35, etc.) so that they work with user-defined types.

dot product

An operation defined in linear algebra that multiplies two

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5s and yields a numeric value.

scalar multiplication

An operation defined in linear algebra that multiplies each of the coordinates of a

print('({0}, {1})'.format(p.x, p.y))
distance_squared = p.x * p.x + p.y * p.y
5 by a numeric value.

polymorphic

A function that can operate on more than one type. If all the operations in a function can be applied to a type, then the function can be applied to a type.

What is the correct syntax for defining a class called game if it inherits from a parent class called LogicGame in Python?

What is the correct syntax for defining a class called Game, if it inherits from a parent class called LogicGame? Explanation: The parent class which is inherited is passed as an argument to the child class.

What is the syntax of a Python class?

A class in Python can be defined using the class keyword. As per the syntax above, a class is defined using the class keyword followed by the class name and : operator after the class name, which allows you to continue in the next indented line to define class members.

What is the correct syntax for defining a class?

The basic syntax is: class MyClass { // class methods constructor() { ... } method1() { ... } method2() { ... }

What is the correct syntax for instantiating a new object of the type game Python?

Instantiating a class in Python is simple. To instantiate a class, we simply call the class as if it were a function, passing the arguments that the __init__ method defines. The return value will be the newly created object.