from turtle_widget import Turtle41 Turtle: classes and debugging
The Turtle is actually a class I made for you, and you have been using turtle object and its methods for weeks now. This week we try to use it along side classes we make ourselves.
Exercise 41-1
Here is a class with attributes and one method. Read it, predict what the last three lines do, and then run it.
class Polygon:
def __init__(self, sides, size):
self.sides = sides
self.size = size
def draw(self, t):
for _ in range(self.sides):
t.forward(self.size)
t.left(360 / self.sides)
t = Turtle()
t.speed(0)
hexagon = Polygon(6, 60)
hexagon.draw(t)The Polygon knows how many sides it has, how long they are and nothing else. It does not know where it is, what color it is, or when it will be drawn, because all of that belongs to the turtle.
Exercise 41-2
Here is another one. Predict the picture and what gets printed before you run it.
t = Turtle()
t.speed(0)
shapes = [Polygon(3, 80), Polygon(5, 60), Polygon(8, 40)]
for shape in shapes:
shape.draw(t)
t.left(120)
print(shapes[0].sides)
print(shapes[1].sides)There are three Polygon objects. Each one has its own data. Confirm that by assigning a new value to shapes[0].size print shapes[1].size afterwards to see if that changed too or not.
Exercise 41-3
Fill in the grow method. It changes the object rather than drawing anything. The method takes a single factor argument should scale the size attribute. I.e., If factor is 2, the method should make thesize attribute twice as large as it was.
class Polygon:
def __init__(self, sides, size):
self.sides = sides
self.size = size
def grow(self, factor):
...
def draw(self, t):
for _ in range(self.sides):
t.forward(self.size)
t.left(360 / self.sides)
p = Polygon(6, 40)
t = Turtle()
p.draw(t)
print(p.size)
p.grow(2)
p.draw(t)
print(p.size)t = Turtle()
t.speed()
a, b = 0, 1
p = Polygon(4, b)
for i in range(15):
p.draw(t)
t.forward(b)
t.left(90)
a, b = b, a + b
t.forward(a)
p.size = b
print((a + b)/b)Exercise 41-4
Write a second class of your own with the same shape. Call it Star, give it a number of points and a size, give it a draw method that leaves the turtle exactly as it found it. Then write the check that proves the claim about what it leaves behind.
class Star:
def __init__(self, sides, size):
self.sides = sides
self.size = size
def draw(self, t):
...
t = Turtle()
t.speed(0)
before = t.position()
facing = t.heading()
Star(5, 80).draw(t)
print(t.position() == before)
print(t.heading() == facing)If either check prints False, the picture is not the thing to look at. Add a t.left(...) at the end of draw until both print True, and then say in one sentence why the number you needed is what it is.
Exercise 41-5
Nominate one method for the assistant to write, and follow the full order from this week’s AI note without cutting any of it.
First specify, in writing and before you prompt: a method perimeter(self) on Polygon that returns the total distance a turtle would travel drawing it, with the worked example that Polygon(6, 70).perimeter() is 420. Decide, also before you prompt, what it should do for a polygon with zero sides, because that is the awkward case and the assistant will decide it for you silently if you do not.
Then write the two tests, still before prompting.
print(Polygon(6, 70).perimeter() == 420)
print(Polygon(0, 70).perimeter() == 0)Then ask for the body, read every line of what comes back, and run both tests. A passing gentle test tells you the method is not catastrophically broken. Only the nasty test passing earns your belief, and if it fails then the interesting question is whether it was answering the question you actually asked, rather than whether it was wrong.
Two bugs, on purpose
The four steps from this week are read the traceback from the bottom, locate the failing line, form a hypothesis you could be wrong about, and confirm the hypothesis before changing anything. The temptation to skip to the last step and change something hopefully is strongest when the fix looks obvious, which is exactly when it is most likely to be a different bug wearing the same clothes.
Exercise 41-6
This one stops. Run it, then work the four steps in order, out loud or on paper, before you touch the code.
from turtle_widget import Turtle
class Spiral:
def __init__(self, turns, step):
self.turns = turns
self.step = step
def draw(self, t):
for i in range(self.turns):
t.forward(self.step * i)
t.left(self.angle)
t = Turtle()
t.speed(0)
Spiral(20, 6).draw(t)Read the last line of the traceback first and say what it names. Then find the line it points at. Then state your hypothesis as a sentence that could turn out to be false, in the form of a claim about what is or is not true of the object at that moment. Then confirm it, by printing something, before you decide what the fix is. There is more than one defensible fix here, and which one is right depends on a decision about what a spiral is, which the error message cannot make for you.
Exercise 41-7
This one does not stop. It runs without complaint and draws the wrong thing, which is the harder case because Python has nothing to say about it.
from turtle_widget import Turtle
def staircase(t, steps, size):
"Draw a staircase going up and to the right."
for _ in range(steps):
t.forward(size)
t.left(90)
t.forward(size)
t.left(90)
t = Turtle()
t.speed(0)
staircase(t, 5, 40)There is no traceback to read, so the first step of the method is replaced by a different one: say exactly what you expected and what you got, in a way somebody else could check. Then halve the problem. Run it with steps set to one and look at what a single step of the staircase actually draws, because a bug that repeats five times is five copies of a bug that happens once, and it is always cheaper to find the one.
Exercise 41-8
Confirm your hypothesis about the staircase by watching the state rather than the picture, which is the print-based version of what the codelens widget shows you.
from turtle_widget import Turtle
def staircase(t, steps, size):
for i in range(steps):
t.forward(size)
t.left(90)
t.forward(size)
t.left(90)
print(i, t.position(), t.heading())
t = Turtle()
t.speed(0)
staircase(t, 3, 40)Read the printed heading after each step and compare it with the heading you expected. The picture and the numbers were always saying the same thing, but the numbers say it in a form you can be exact about, and being exact is what turns a hunch into a hypothesis. When you have found it, fix it, and delete the print line afterwards, because a debugging print left in finished code is a small lie about what the function is for.
Exercise 41-9
Take the traceback from the spiral exercise, paste it into the assistant along with the class, and ask it what is wrong. Then treat the answer as what it is, which is a hypothesis produced by something that has never run your code.
Do the third and fourth steps of the method yourself. Turn its answer into a sentence that could be false, and confirm it with the machine before you change anything. Then ask yourself the question that matters more than whether it was right: did its answer include the decision about what a spiral is, or did it quietly choose one for you and present the result as the fix?
Exercise 41-10
Finish both bugs properly, by adding the check that would have caught each one. For the spiral, write a check that the object has the attribute the method expects, which you can do by drawing it and confirming the turtle moved at all. For the staircase, write a check on the turtle’s heading after one step, with the value you decided was correct.
from turtle_widget import Turtle
t = Turtle(autoshow=False)
staircase(t, 1, 40)
print(round(t.heading(), 6) == 0.0)
print(round(t.xcor(), 6) == 40.0)
print(round(t.ycor(), 6) == 40.0)The autoshow=False is there because this cell is not drawing anything for you to look at. It is asking questions, and it does not need a canvas to do it. That is a small thing now and it is the entire subject of next week, so it is worth noticing that you have just written a check on a drawing without producing the drawing.