17b The init Method and the __str__ Method

This lesson introduces the two special methods Python calls for you: __init__, which assigns an object's attributes at the moment it is created, and __str__, which supplies a printable representation.

Subject: Python · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Lesson 17b The init Method and the __str__ Method

Title

Python · Chapter 17 — Classes and methods

§17.5-17.6, pp. 164-165

2. By the end of this lesson you can

Objectives

Five things, each one you can check yourself at an interpreter prompt.

Think Python, 2nd edition — Allen B. Downey §17.5-17.6, pp. 164-165 — the pages these objectives are drawn from

3. Before we start: four lines to make one object

Warm-up

Every Time so far has been built the same way.

Discussion prompt

Creating a Time has meant Time() followed by three assignments. List two things that can go wrong with that, and say what they have in common.

Hint: What if you forget one?

Answer:

You can forget an attribute, leaving an object that looks fine until something reads the missing one — and you can misspell a name, which creates a new attribute rather than raising.

What they have in common is that the construction is spread across four statements in the caller's code, so nothing checks it and every call site can get it wrong differently.

This lesson moves that construction inside the class, into a method Python runs at the moment an object is created. One place to get right, and every instance gets it.

4. The one idea behind this lesson: two methods Python calls for you

Concept

The init method is a special method that gets invoked when an object is instantiated, and __str__ is a special method that is supposed to return a string representation of an object.

# you write these:
    def __init__(self, hour=0, minute=0, second=0):
        ...
    def __str__(self):
        ...

# and Python calls them:
time = Time(9, 45)      # calls __init__
print(time)             # calls __str__
What you writeWhat happensNote
Time(9, 45)instantiation__init__ runs
print(time)printing__str__ runs
neither callnames the methodwhich is the point

You never write time.__init__() or time.__str__(). Both are invoked on your behalf, by ordinary syntax that says nothing about them — which is what makes them special methods rather than merely conventional ones.

Figure (svg): Two columns contrasting ordinary methods you call with special methods Python calls

The double underscores mark a method the language knows about.

Think Python, 2nd edition — Allen B. Downey §17.5-17.6, pp. 164-165

5. The init method

Section

Section 1

6. Invoked when an object is instantiated

Concept

The init method — short for initialisation — is a special method that gets invoked when an object is instantiated. Its full name is __init__: two underscore characters, followed by init, and then two more underscores.

# inside class Time:
    def __init__(self, hour=0, minute=0, second=0):
        self.hour = hour
        self.minute = minute
        self.second = second
PartWhat it isNote
selfthe new objectas in any method
hour, minute, secondoptional parameterswith defaults of 0
self.hour = hourstores the parameter as an attributesame name, different place

It is common for the parameters of __init__ to have the same names as the attributes. The statement self.hour = hour stores the value of the parameter hour as an attribute of self.

Think Python, 2nd edition — Allen B. Downey §17.5-17.6, pp. 164-165

7. Picture it: where each name lives

Picture it

Two things called hour, in two different places.

Figure (svg): A Time object being initialised, showing parameters on the left and attributes on the right

self.hour = hour: the attribute on the left, the parameter on the right.

There is no conflict between the two, for the same reason lesson 15a gave: an attribute belongs to an object and is reached through it.

8. Worked example: what each number of arguments gives

Worked example

The parameters are optional, so four calls all work.

>>> time = Time()
>>> time.print_time()
00:00:00
>>> time = Time(9)
09:00:00
>>> time = Time(9, 45)
09:45:00
# and three arguments override all three defaults
CallWhat is suppliedResult
no argumentsall three defaults00:00:00
one argumentoverrides hour09:00:00
two argumentsoverride hour and minute09:45:00
threeoverride all three

Call with nothing.

Why: The parameters are optional, so if you call Time with no arguments you get the default values.

Add arguments from the left.

Why: If you provide one argument, it overrides hour; two override hour and minute.

Note the ordering.

Why: Arguments fill the parameters in order, so you cannot supply a minute without an hour — which is lesson 13b's positional matching.

Figure (svg): The state of the program after each line of Worked example what each number of arguments gives, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

Four usable calls from one definition, because every parameter has a default. This is optional parameters from chapter 13, in the place they are most valuable.

Verify: Check that the defaults produce a valid Time.

Why: Time() gives 00:00:00, which satisfies chapter 16's invariant — every field in range. Choosing defaults that produce a well-formed object matters, because a default of, say, 60 would let Time() build something the rest of the program considers invalid.

9. Predict: what does Time(9) produce?

Prediction

One argument, three parameters.

    def __init__(self, hour=0, minute=0, second=0):
        self.hour = hour
        self.minute = minute
        self.second = second

time = Time(9)
time.print_time()
ParameterWhere its value comes fromValue
hoursupplied9
minutenot suppliedthe default, 0
secondnot suppliedthe default, 0

Predict first

What does this print?

  • 09:00:00
  • 00:00:09
  • 09:09:09
  • A TypeError about missing arguments

Correct: 09:00:00 — one argument overrides hour, and the other two parameters take their defaults.

Why: Arguments fill parameters from the left, so a single argument goes to hour. Option B would require supplying second alone, which positional matching cannot do — you would need a keyword argument, Time(second=9). Nothing raises, because every parameter has a default.

10. Worked example: what this replaces

Worked example

Four statements in the caller become one call.

# before
time = Time()
time.hour = 9
time.minute = 45
time.second = 0

# after
time = Time(9, 45)
VersionWhat the caller writesNote
beforefour statementsin the caller
afterone callthe class does the rest
what changedwhere the construction livesand how many places can get it wrong

Count the statements.

Why: Four in the caller, of which three are assignments that every call site has to repeat.

Move them into the class.

Why: __init__ does the assigning, so a caller supplies values rather than performing the construction.

Note what that fixes.

Why: A forgotten attribute or a misspelled name can now only happen in one place, and it is the place whose job it is.

Figure (svg): Two columns comparing constructing an object by hand with constructing it through __init__

Chapter 15's four gaps, and this closes the first two.

One call in place of four statements, and the attribute list is finally somewhere that runs — chapter 15's docstring promise, kept by code.

Verify: Check that every instance now has every attribute.

Why: Any Time built by calling the class has all three, because __init__ assigns all three unconditionally. That is the property the previous style could not guarantee, and it is what the book means by initialising all of an object's attributes in the init method.

11. Trap: calling __init__ yourself

Trap

The trap

A student writes t = Time(); t.__init__(9, 45), reasoning that the method has to be invoked somehow.

Call the method you defined

Why: Every other method has to be called explicitly.

It is invoked when an object is instantiated, so Time(9, 45) has already run it — calling it again re-initialises an object that was fine, and reads as though the language needed help.

The fix

Pass the arguments to the class.

t = Time(9, 45)

Why: Which calls __init__ with those arguments and self bound to the new object.

And never name the method

Why: The whole point is that ordinary syntax invokes it.

This is the defining property of a special method: Python calls it for you, in response to syntax that does not mention it. Writing the call yourself is legal and defeats the mechanism.

12. Complete it: store the parameter as an attribute

Faded example

Two names, and only one of them belongs to the object.

Fill in the blanks

def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second

Why: The statement self.hour = hour stores the value of the parameter hour as an attribute of self. It is common for the parameters of __init__ to have the same names as the attributes, and there is no conflict between them because an attribute belongs to an object and is reached through it.

13. Sort: which call gives which time?

Sorting

Arguments fill parameters from the left.

Sort into buckets

For each call to Time(hour=0, minute=0, second=0), what does it produce?

midnight — 00:00:00
Time(); Time(0, 0, 0)
an hour only, minutes and seconds zero
Time(9); Time(11)
an hour and at least a minute
Time(9, 45); Time(9, 45, 30)
mid
One supplies nothing and takes all three defaults; the other supplies three zeros explicitly. Both give midnight, by different routes.
hr
Each supplies exactly one argument, which fills hour, leaving minute and second at their defaults of zero.
more
Each supplies at least two arguments, so minute is given explicitly as well as hour.

14. Explain it yourself: why give the parameters defaults?

Explain it to yourself

__init__ would work without them.

Discussion prompt

The book's __init__ gives every parameter a default of zero. What does that buy, and what would be lost without it?

Hint: How many ways can the class then be called?

Answer:

It makes every parameter optional, so one definition supports four call shapes — no arguments through to three — without any extra code.

Without defaults, every caller would have to supply all three, so Time(9) would raise and constructing midnight would mean Time(0, 0, 0).

And the defaults were chosen to produce a valid object: Time() gives 00:00:00, which satisfies chapter 16's invariant. A default that produced a malformed Time would be worse than requiring the argument.

15. The __str__ method

Section

Section 2

16. A string representation, invoked by print

Concept

__str__ is a special method, like __init__, that is supposed to return a string representation of an object.

# inside class Time:
    def __str__(self):
        return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)

>>> time = Time(9, 45)
>>> print(time)
09:45:00
PartWhat it doesNote
__str__returns a stringit does not print
print(time)invokes __str__and prints what it returns
the result09:45:00instead of an address

When you print an object, Python invokes the str method. Note the difference from print_time: __str__ returns a string and prints nothing, which is what makes it usable anywhere a string is wanted.

Think Python, 2nd edition — Allen B. Downey §17.5-17.6, pp. 165-165

17. Picture it: what print does with an object

Picture it

The method supplies the text and print does the printing.

Figure (svg): A pipeline showing print invoking __str__ and displaying the returned string

You never name the method; printing the object is what invokes it.

Which is why the default was so unhelpful: without a __str__, Python falls back on showing the class and the memory address.

18. Worked example: __str__ against print_time

Worked example

Almost the same body, and a different contract.

    def print_time(self):
        print('%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second))

    def __str__(self):
        return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)
MethodWhat it doesNote
print_timeprintsimpure: an effect
__str__returns a stringpure
the differenceprint against returnone word

Compare the bodies.

Why: The format expression is identical; one passes it to print and the other returns it.

Note which is pure.

Why: __str__ modifies nothing and has no effect other than returning a value, which makes it pure by chapter 16's definition. print_time is not.

Note what returning buys.

Why: A string can be printed, stored, concatenated, put in a list, or written to a file. Printing can only be printed.

Figure (svg): Two columns contrasting a method that prints with one that returns a string

The same format expression, and the return is what makes it reusable.

One word's difference and a much more useful method. The string can go anywhere; the printing can only go to the screen.

Verify: Use the result somewhere other than print.

Why: str(time) returns the string directly, so it can be concatenated or written to a file — none of which print_time allows. That is the practical reason __str__ is the one worth having, and print(time) still covers the common case.

19. Predict: what does printing the object give?

Prediction

The class defines __str__.

    def __str__(self):
        return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)

time = Time(9, 45)
print(time)
StepWhat happensResult
print(time)invokes __str__on your behalf
__str__returns '09:45:00'a string
printdisplays it

Predict first

What does this print?

  • 09:45:00
  • <__main__.Time object at 0x...>
  • None
  • 09:45:00 followed by None

Correct: 09:45:00 — when you print an object, Python invokes the str method and displays what it returns.

Why: Option B is what happens without a __str__ method, which is chapter 15's unhelpful default. Option D is what you would get if __str__ printed instead of returning — the text, then None, because the method returned nothing for print to display.

20. Worked example: why these two come first

Worked example

The book's own working habit.

# When I write a new class, I almost always
# start by writing __init__, which makes it
# easier to instantiate objects, and __str__,
# which is useful for debugging.
MethodWhat it buysNote
__init__easier to instantiateone call instead of four statements
__str__useful for debuggingyou can see what an object holds
togetheryou can make one and look at itbefore anything else works

Write __init__ first.

Why: It makes it easier to instantiate objects, so every test and every experiment becomes one line.

Write __str__ second.

Why: It is useful for debugging, because printing an object shows what it holds rather than where it lives.

Note what you then have.

Why: The ability to create an instance and inspect it — which is what every subsequent piece of work needs.

Figure (svg): The state of the program after each line of Worked example why these two come first, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

Two methods that make the class usable before any of its real behaviour exists. That is why they are the ones to write first.

Verify: Consider working without them.

Why: Every test needs four statements to build an object and a helper function to see it, and neither is available until you write it. So the alternative is not slightly less convenient but nothing can be checked yet, which is why the habit is worth adopting rather than admiring.

21. Trap: printing inside __str__

Trap

The trap

A student writes __str__ with a print statement in it, since printing is what it seems to be for.

Make the method do the printing

Why: The name suggests display, and print_time worked that way.

It is supposed to RETURN a string representation. Printing inside it means print(time) displays the text and then prints None — because the method returned nothing.

The fix

Return the string.

return '%.2d:%.2d:%.2d' % (...)

Why: One word different from print_time, and it is the important word.

Let print do the printing

Why: Which is what it was going to do anyway.

The symptom is distinctive: the right text, followed by None on the next line. That combination always means a function printed instead of returning, and something else printed its return value.

22. Discriminate: does this invoke __str__?

Discrimination

Some syntax calls it and some does not.

Sort into buckets

For each expression, is __str__ invoked?

invokes __str__
print(time); str(time); '%s' % time
does not
time.hour; time == other; time.time_to_int()
yes
Each needs a string representation of the object — printing it, converting it, or formatting it with %s, which accepts any value by converting it to a string.
no
Each does something else entirely: reads an attribute, compares identities, or calls an ordinary method. Nothing about them needs text.

23. Complete it: a string representation

Faded example

One word different from print_time.

Fill in the blanks

def __str__(self):
return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)

Why: __str__ is supposed to return a string representation of an object, and print does the displaying. Writing print here instead would show the text and then print None, because the method would return nothing for print to display — which is the distinctive symptom of this mistake.

24. Explain it: why does my object print as None as well?

Explain it

The right text, and something extra.

Discussion prompt

A classmate's print(time) shows 09:45:00 and then None on the next line. Diagnose it.

Hint: What did __str__ return?

Answer:

Their __str__ prints instead of returning. The text appears because the method printed it, and then None appears because print displayed the method's return value — which was nothing.

__str__ is supposed to return a string representation, and printing is print's job. Changing print to return fixes it.

The combination is a reliable tell: correct output followed by None means a function printed where it should have returned, and something else printed the result. It is the mirror image of lesson 6a's void-function trap.

25. What makes a method special

Section

Section 3

26. Python calls it, in response to syntax that never names it

Concept

Both of these methods share a pattern: you define them, and something else invokes them. You never write the call.

time = Time(9, 45)     # calls __init__
print(time)            # calls __str__

# what you never write:
# time.__init__(9, 45)
# print(time.__str__())
SyntaxWhich method it invokesNote
instantiationinvokes __init__the class call
printinginvokes __str__the print call
the namesnever appearin ordinary code

The double underscores mark a method the language itself knows about. Defining one changes what ordinary syntax does with your objects, which is the whole mechanism behind the operator overloading of the next lesson.

Think Python, 2nd edition — Allen B. Downey §17.5-17.6, pp. 164-165

27. Picture it: syntax on one side, methods on the other

Picture it

Each piece of ordinary syntax has a method behind it.

Figure (svg): Pieces of ordinary syntax paired with the special methods they invoke

For every operator in Python there is a corresponding special method.

Which explains something from earlier: len works on a string and a list and a dictionary because each of those types defines the method len looks for.

28. Worked example: why the built-ins have always worked

Worked example

This mechanism was there all along.

len('abc')      # 3
len([1, 2, 3])  # 3
len({'a': 1})   # 1

# three different types, one function -
# because each type defines the method
# that len invokes
CallWhat it countsNote
len on a stringcounts charactersthe string's own method
len on a listcounts elementsthe list's
len on a dictionarycounts itemsthe dictionary's

Notice one function and three behaviours.

Why: len gives a sensible answer for every sequence type, which cannot be one implementation.

Explain it with special methods.

Why: Each type defines the method len invokes, so len asks the object rather than deciding for itself.

Connect it to your own classes.

Why: Defining that method on a class of your own would make len work on your objects too, by the same mechanism.

Figure (svg): A flowchart showing a built-in function delegating to the object's special method

The built-in asks the object. What the object answers is up to its class.

The built-ins delegate to the object. That is why they have always worked across types, and why they can be made to work on yours.

Verify: Check what happens on a type that lacks the method.

Why: len(5) raises TypeError: object of type 'int' has no len() — which is the mechanism failing visibly. The error message names the type rather than the function, because the function did its job and the object could not answer.

29. Predict: what invokes __init__?

Prediction

Nothing in the call names it.

time = Time(9, 45)
PartWhat happensNote
the class callcreates an objectand initialises it
__init__invoked automaticallywith self bound to the new object
the argumentspassed through9 and 45

Predict first

What invokes the __init__ method here?

  • Instantiation — calling the class
  • The assignment to time
  • Nothing; you must call it yourself
  • The first attribute access

Correct: Instantiation — the init method gets invoked when an object is instantiated.

Why: Calling the class creates the object and runs __init__ with the arguments you supplied, which is why you never write time.__init__(9, 45). That automatic invocation in response to ordinary syntax is exactly what makes a method special rather than merely conventional.

30. Worked example: the naming convention

Worked example

Two underscores on each side, and nothing else.

__init__      # correct
_init_        # not special - just a method with a name
__init        # not special
__str__       # correct
__string__    # not a method Python looks for
NameSpecial?Note
two underscores each sidethe conventionand the exact name matters
a near missan ordinary methodnever invoked automatically
the symptomyour method is ignoredsilently

Get the underscores right.

Why: Two underscore characters, followed by init, and then two more underscores — four in total, and a single one on either side is not the same name.

Get the name right.

Why: Python looks for specific names; __string__ is not one of them, however reasonable it looks.

Note the failure mode.

Why: A misspelled special method is a perfectly legal ordinary method that nothing ever calls, so nothing raises and the behaviour simply does not appear.

Figure (svg): The state of the program after each line of Worked example the naming convention, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

Exact names, and near misses fail silently. That is worth knowing before you spend time wondering why print still shows an address.

Verify: Check by calling the method explicitly.

Why: If print(t) shows an address but t.__str__() returns the right string, the method exists and Python is not finding it — which almost always means the name is wrong. That two-line check separates my method is broken from my method is not being called.

31. Trap: a misspelled special method

Trap

The trap

A class defines _init_ with single underscores and its objects come out with no attributes.

Approximate the name

Why: The double underscores are easy to miscount, especially in a proportional font.

It becomes an ordinary method that nothing calls. Instantiation succeeds, every attribute is missing, and the first read raises AttributeError somewhere unrelated.

The fix

Type the name exactly.

Two underscores, the name, two underscores

Why: __init__ and __str__, with four underscores each.

And test the effect, not the method

Why: Create an object and print it; if the special method is being found, that works.

The failure is silent because a method with the wrong name is still a valid method. Nothing in the language can tell that you meant it to be special, which is why the check has to be that the behaviour appears.

32. Two truths and a lie: special methods

Two truths and a lie

Two are true. Keep the lie.

Eliminate the wrong options

Rule out the two true statements.

  • A. __init__ gets invoked when an object is instantiated
  • B. __str__ is supposed to return a string representation of an object
  • C. You have to call __str__ yourself for printing to use it

Survives elimination: C

Why: C describes the opposite of what makes a method special. When you print an object, Python invokes the str method — you never write print(time.__str__()). Being invoked by ordinary syntax that does not name it is the defining property, and it is the mechanism the next lesson's operator overloading uses.

33. Complete it: the special method name

Faded example

Two underscores on each side.

Fill in the blanks

def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second

Why: Two underscore characters, followed by init, and then two more underscores. A near miss like _init_ is a perfectly legal ordinary method that nothing ever calls, so instantiation succeeds with no attributes assigned and the failure appears later as an AttributeError.

34. Where a name triggers behaviour

Real world

Conventions that a system acts on.

Discussion prompt

Think of a place where naming something a particular way makes a system treat it differently. What happens when the name is slightly wrong?

Hint: A file extension, a required filename.

Answer:

A file extension that decides which program opens it; a configuration file the system only reads under one exact name; a folder whose name makes it appear in a menu.

A near miss usually fails silently, because the thing is still a valid file — it is simply not the one the system was looking for, so nothing happens and nothing complains.

Which is exactly the misspelled special method: a legal method that nothing invokes. The general lesson is that when behaviour is triggered by a name, the failure mode is absence rather than error — so the check has to be that the behaviour appeared.

35. Initialising every attribute

Section

Section 4

36. It is a good idea to initialise all of them

Concept

It is legal to add attributes to objects at any point in the execution of a program, but if you have objects with the same type that don't have the same attributes, it is easy to make mistakes. It is considered a good idea to initialise all of an object's attributes in the init method.

# every Time has all three, always
    def __init__(self, hour=0, minute=0, second=0):
        self.hour = hour
        self.minute = minute
        self.second = second
AspectWhat is trueNote
all three assignedunconditionallyevery instance
the guaranteesame type, same attributesno variation
what that preventsa missing attributein some instances only

The problem is not that adding attributes later is illegal — it is that objects of one type with different attributes are hard to reason about, since any function taking one has to cope with several shapes.

Think Python, 2nd edition — Allen B. Downey §17.5-17.6, pp. 168-168

37. Picture it: two objects of one type with different shapes

Picture it

Legal, and hard to work with.

Figure (svg): Two columns contrasting instances with a uniform set of attributes and instances that differ

Objects of the same type that do not have the same attributes make mistakes easy.

This is lesson 12c's shape error, arriving inside a single class — where you would least expect the objects to differ.

38. Worked example: the tools for checking

Worked example

vars and getattr, for when you have to look.

>>> p = Point(3, 4)
>>> vars(p)
{'y': 4, 'x': 3}

def print_attributes(obj):
    for attr in vars(obj):
        print(attr, getattr(obj, attr))
ExpressionWhat it givesNote
vars(obj)a dictionary of names to valuesthe object's attributes
for attr in vars(obj)the names, as stringslooping over the keys
getattr(obj, attr)the value for that namethe name as a string

Look at the whole object.

Why: vars takes an object and returns a dictionary that maps from attribute names — as strings — to their values.

Loop over it.

Why: The keys are the attribute names, so iterating gives them one at a time.

Fetch each value.

Why: getattr takes an object and an attribute name as a string and returns the attribute's value — which is how you read a name you have as text.

Figure (svg): The dictionary vars returns for a Point object

An object's attributes, presented as a dictionary you can loop over.

A function worth keeping handy for debugging, which prints every attribute and its value. It is the general version of the vars check from lesson 15b.

Verify: Use it to catch the typo from chapter 15.

Why: An object with x and Y shows both in vars, which makes a capitalisation slip visible immediately. That is why the tool is worth having: an AttributeError tells you what is missing, and vars tells you what is there instead.

39. Predict: what does vars return?

Prediction

An object with two attributes.

p = Point(3, 4)
print(vars(p))
PartWhat it isNote
varsthe object's attributesas a dictionary
the keysnames, as strings'x' and 'y'
the valuesthe attribute values3 and 4

Predict first

What does this print?

  • {'y': 4, 'x': 3}
  • ['x', 'y']
  • (3, 4)
  • <__main__.Point object at 0x...>

Correct: {'y': 4, 'x': 3} — vars returns a dictionary mapping attribute names, as strings, to their values.

Why: The order is unpredictable, as for any dictionary, which is why the book's own output shows y before x. Option B is what looping over that dictionary would give you one at a time — the names alone — and getattr is how you turn each name back into a value.

40. Worked example: what varying shapes cost

Worked example

Every function has to cope with each shape.

# if some Times have a 'label' attribute and some do not:
def describe(t):
    if hasattr(t, 'label'):
        return t.label + ' ' + str(t)
    return str(t)

# every function touching label needs this check
ApproachWhat it costsNote
the checkhasattr in every functionrepeated
the alternativeinitialise label in __init__even to None
thenone shape, no checks

Note the cost of variation.

Why: Any function that might touch the optional attribute has to check for it, and the check has to be repeated everywhere.

Note the alternative.

Why: Initialising it in __init__ — even to None — gives every instance the same shape, and the check becomes a comparison against None if it is needed at all.

Note what the book recommends.

Why: It is considered a good idea to initialise all of an object's attributes in the init method.

Figure (svg): The state of the program after each line of Worked example what varying shapes cost, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

One check per function, or one assignment in __init__. The second is one line and removes the question everywhere.

Verify: Ask when hasattr is still needed.

Why: When the objects genuinely come from elsewhere and their shape is not under your control — which lesson 15b's try approach also covers. For a class you wrote, initialising in __init__ makes the question disappear, so hasattr is a tool for foreign objects rather than your own.

41. Trap: adding attributes as you need them

Trap

The trap

A program attaches an extra attribute to some Time objects when a particular calculation needs one.

Add what you need where you need it

Why: It is legal to add attributes at any point in the execution of a program.

Now objects with the same type do not have the same attributes, so every later function has to guess which kind it received — and the ones that guess wrong fail with an AttributeError far from the cause.

The fix

Initialise every attribute in __init__.

Including ones that are usually empty

Why: self.label = None gives every instance the attribute.

So every function can rely on the shape

Why: And a check, if needed, becomes a comparison rather than a hasattr.

The legality is not the question. The book's phrasing is precise: it is legal, and if you have objects with the same type that don't have the same attributes, it is easy to make mistakes.

42. Complete it: print every attribute

Faded example

The name arrives as a string.

Fill in the blanks

def print_attributes(obj):
for attr in vars(obj):
print(attr, getattr(obj, attr))

Why: Looping over vars gives the attribute names as strings, and getattr takes an object and an attribute name as a string and returns the attribute's value. Writing obj.attr instead would look for an attribute literally named attr, which almost certainly does not exist.

43. Discriminate: which tool answers this?

Discrimination

Three ways to interrogate an object.

Sort into buckets

For each question, which tool answers it?

vars
which attributes does this object have?; list every name and value
hasattr
does it have one called width?; is this attribute present before I read it?
getattr
what is the value of the attribute named by this string?; read an attribute whose name I computed
v
Both ask for the whole set, which vars returns as a dictionary from names to values.
h
Both ask a yes-or-no question about one name, which is exactly what hasattr answers without raising.
g
Both need the value for a name held as a string, which is what getattr is for — dot notation cannot take a computed name.

44. Think it through: why does uniform shape matter?

Socratic

Adding attributes later is legal.

Discussion prompt

Python lets you add an attribute to one instance and not another. Why is that worth avoiding even though it works?

Hint: What does a function receiving one have to assume?

Answer:

Because a function taking a Time can no longer rely on what a Time has. It either checks every attribute it touches, or it assumes and fails on the instances that differ.

And the failure lands in the function that reads the missing attribute, which may be far from whichever code path produced the odd object — the cause-and-symptom distance again.

So the recommendation is about reasoning rather than legality: initialising all of an object's attributes in the init method means every instance of a type has one shape, and every function can be written once.

45. Interface and implementation

Section

Section 5

46. Keep them separate

Concept

One of the goals of object-oriented design is to make software more maintainable, which means that you can keep the program working when other parts of the system change, and modify the program to meet new requirements.

# the interface: what the class provides
#   time_to_int, is_after, add_time, __str__

# the implementation: how it stores things
#   hour, minute, second
#   or: a single integer, seconds since midnight
PartWhat it isNote
the interfacethe methods a class provideswhat callers use
the implementationhow the attributes are representeda private decision
the principlethe first should not depend on the second

A design principle that helps achieve that goal is to keep interfaces separate from implementations. For objects, that means the methods a class provides should not depend on how the attributes are represented.

Think Python, 2nd edition — Allen B. Downey §17.5-17.6, pp. 169-169

47. Picture it: two implementations behind one interface

Picture it

The same methods, storing the time two ways.

Figure (svg): Two columns showing two possible implementations behind the same set of methods

The same interface — time_to_int, is_after, add_time — over two different representations.

If the interface was designed carefully, changing between these two is invisible to everything using the class.

48. Worked example: the alternative representation

Worked example

The book's own example of a different implementation.

# instead of three attributes:
#   self.seconds = 35100

    def is_after(self, other):
        return self.seconds > other.seconds     # easier

    def __str__(self):
        minutes, second = divmod(self.seconds, 60)
        hour, minute = divmod(minutes, 60)
        return '%.2d:%.2d:%.2d' % (hour, minute, second)   # harder
MethodEffect of the changeNote
is_afterone comparisoneasier than before
__str__two divmods firstharder than before
the interfaceunchangedcallers see no difference

Consider the alternative.

Why: We could replace these attributes with a single integer representing the number of seconds since midnight.

Note the trade.

Why: This implementation would make some methods, like is_after, easier to write, but it makes other methods harder.

Note what does not change.

Why: The methods the class provides — their names, their arguments, what they return — which is the interface.

Figure (svg): The state of the program after each line of Worked example the alternative representation, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

A genuine trade between the two representations, with no clear winner. What matters is that choosing differently would change no calling code.

Verify: Check what a caller would notice.

Why: Nothing: t.is_after(other) and print(t) behave identically under both. The only way to tell them apart is to read the attributes directly — which is exactly what callers should not be doing, and why the principle is worth following.

49. Sort: interface or implementation?

Sorting

One is what callers use; the other is how it works.

Sort into buckets

For each, which side of the line does it belong to?

the interface
the method is_after; the method __str__; the method time_to_int
the implementation
the attribute hour; storing seconds since midnight; using three attributes rather than one
int
Each is a method the class provides — what callers use, and what should stay the same when the representation changes.
imp
Each is a decision about how the data are stored, which callers should not depend on and which you should be free to change.

50. Worked example: why the separation is worth having

Worked example

The cost of getting it wrong is paid later.

# code that uses the interface: survives a change
if t1.is_after(t2):
    ...

# code that reaches into the implementation: does not
if t1.hour > t2.hour or (t1.hour == t2.hour and ...):
    ...
StyleWhat happens on a changeNote
using methodsunaffected by the representationsafe
reading attributesbreaks if they changecoupled
the disciplinecallers use the interface

Note the situation.

Why: After you deploy a new class, you might discover a better implementation.

Note the risk.

Why: If other parts of the program are using your class, it might be time-consuming and error-prone to change the interface.

Note the payoff.

Why: If you designed the interface carefully, you can change the implementation without changing the interface, which means other parts of the program don't have to change.

Figure (svg): A flowchart showing which calling code survives a change of implementation

The separation is what decides how much work a change of representation costs.

Code that goes through the methods survives a change of representation; code that reads the attributes does not. The discipline costs a little now and saves a great deal later.

Verify: Ask what makes an interface careful.

Why: Methods that express what a caller wants rather than how the data are stored. is_after is careful; a method named compare_hours would not be, because its name commits to a representation and would become a lie the moment that changed.

51. Trap: reaching into another object's attributes

Trap

The trap

Code outside the class compares two Times by reading t1.hour and t2.hour directly.

Use the data that are there

Why: The attributes are accessible, so using them looks natural.

Every such use depends on the representation, so changing to seconds-since-midnight breaks all of them — and they are scattered, so some will be missed.

The fix

Go through the interface.

t1.is_after(t2)

Why: Which says what you want rather than how it is stored.

And add a method if the interface lacks one

Why: That is the right response to needing something the class does not offer.

The methods a class provides should not depend on how the attributes are represented — and the other half of that is that callers should not either. Both halves are needed for the separation to be worth anything.

52. Predict: which code survives the change?

Prediction

The class switches to storing seconds since midnight.

# caller A
if t1.is_after(t2): ...

# caller B
if t1.hour > t2.hour: ...
CallerWhat it depends onNote
caller Auses a methodthe interface
caller Breads an attributethe implementation
after the changeA works, B breaks

Predict first

Which caller keeps working?

  • A, because it uses a method rather than an attribute
  • B, because attributes are more stable than methods
  • Both, since the data are the same either way
  • Neither — changing the implementation always breaks callers

Correct: A, because it uses a method rather than an attribute.

Why: If you designed the interface carefully, you can change the implementation without changing the interface, which means other parts of the program don't have to change. Caller B depends on the attribute hour existing, which the new representation does not provide — and such uses are scattered, so some will be missed.

53. Complete it: compare through the interface

Faded example

Say what you want, not how it is stored.

Fill in the blanks

if t1.is_after(t2):
print('later')

Why: The method expresses the question and hides the representation, so it keeps working if the class switches from three attributes to a single integer. Comparing t1.hour with t2.hour would depend on the implementation and would also be wrong on its own, since equal hours need the minutes compared too.

54. Where interface and implementation come apart

Real world

Something whose insides changed without your noticing.

Discussion prompt

Think of something you use whose internals were replaced without any change to how you use it. What made that possible?

Hint: A service, a device, a part.

Answer:

A payment system that changed its provider, a device whose components were replaced, a service whose storage moved — in each case what you interacted with stayed the same.

What made it possible is that the interaction was defined in terms of what you wanted rather than how it worked: a plug, a form, a button, none of which mention the internals.

Which is the principle exactly: the methods a class provides should not depend on how the attributes are represented. Where that discipline is missing, changing the insides means changing everyone who touches it.

55. Compare: __init__ and __str__

Comparison

Fill the blanks. Both are called for you, by different syntax.

Comparison matrix

Question__init____str__
What invokes it?instantiation — calling the classprinting, or str()
What does it return?nothing — it assigns attributesa string
What does it buy?easier instantiationuseful debugging output
When to write it?first, when starting a new classsecond, immediately after

The book's own habit: almost always start by writing __init__ and __str__, before any of the class's real behaviour.

56. The procedure: starting a new class

Pattern

Six steps, and the first two are the book's own opening move.

  1. Write the class statement with a docstring saying what it represents.
  2. Write __init__, assigning every attribute — including ones that are usually empty.
  3. Give the parameters defaults where a sensible default exists, so the class has several usable call shapes.
  4. Write __str__, returning a string representation rather than printing one.
  5. Create an instance and print it, which is now a two-line test of everything so far.
  6. Then add the real methods, keeping them in terms of what a caller wants rather than how the attributes are stored.

Step 5 is why the first two come first: until both exist, you cannot cheaply make an object or see what it holds, so nothing else can be checked.

Python documentation — Classes Classes

57. Check yourself 1 of 3: default arguments

Check

Every parameter has a default.

    def __init__(self, hour=0, minute=0, second=0):
        self.hour = hour
        self.minute = minute
        self.second = second

print(Time(9, 45))
ParameterSourceValue
hoursupplied9
minutesupplied45
secondthe default0

Check your understanding

What does this print, given a __str__ that formats hour:minute:second?

  • A. 09:45:00 (correct)
  • B. 09:45
  • C. 00:09:45
  • D. A TypeError about a missing argument

Answer: A

Why: Two arguments override hour and minute, and second takes its default of zero. The parameters are optional, so nothing raises — and printing the object invokes __str__, which formats all three fields with leading zeros.

Why B tempts people
The __str__ method formats all three fields, so the seconds appear whether or not they were supplied.
Why C tempts people
Arguments fill parameters from the left, so the first goes to hour rather than to second.
Why D tempts people
Every parameter has a default, so any number of arguments from zero to three is accepted.

58. Check yourself 2 of 3: what __str__ should do

Check

One word decides it.

Check your understanding

A __str__ method that prints instead of returning produces what output for print(time)?

  • A. The time, then None on the next line (correct)
  • B. The time only
  • C. None only
  • D. A TypeError

Answer: A

Why: The method prints the text itself, and then print displays the method's return value — which is None, since it returned nothing. That combination is the reliable tell for this mistake: correct output followed by None.

Why B tempts people
This is what a correct __str__ gives. The extra None comes from print displaying the missing return value.
Why C tempts people
The text does appear, printed by the method itself before print adds the None.
Why D tempts people
Nothing illegal happens — printing None is perfectly legal, which is why the mistake is quiet.

59. Check yourself 3 of 3: interface and implementation

Check

The class switches to a single integer.

Check your understanding

What does keeping the interface separate from the implementation mean for a class?

  • A. The methods a class provides should not depend on how the attributes are represented (correct)
  • B. Attributes should be given short names
  • C. Every method should be a special method
  • D. A class should have exactly one attribute

Answer: A

Why: That separation is what lets you change the implementation without changing the interface, so other parts of the program don't have to change — which matters because you might discover a better implementation after the class is already in use.

Why B tempts people
Naming is unrelated to the principle, which is about what callers depend on.
Why C tempts people
Special methods are one part of an interface, and most interfaces are ordinary methods.
Why D tempts people
The number of attributes is exactly the implementation decision the principle says to keep private.

60. Where this shows up outside this course

Real world

Setting something up completely at the moment it is created.

Discussion prompt

Think of a process that fills in everything at the start against one that adds details later. What goes wrong with the second?

Hint: A form completed in stages.

Answer:

Records created with some fields blank and filled in later; an account set up partially and completed on a second visit; equipment assembled in stages.

What goes wrong is that anything encountering the thing in between has to cope with an incomplete version — so every process downstream needs a check, and the ones that forget fail on exactly the incomplete cases.

Which is the argument for initialising all of an object's attributes in the init method. It is not that adding them later is forbidden; it is that objects of one type with different shapes make every function harder to write correctly.

61. Confidence wager: commit before you check

Commit first

Answer, then rate your confidence.

Predict first

What invokes the __str__ method?

  • Printing the object, or converting it with str — you never call it yourself
  • Calling obj.__str__() explicitly, which you must do
  • Instantiation, like __init__
  • Nothing; it is only documentation

Correct: Printing the object, or converting it with str — you never call it yourself.

Why: When you print an object, Python invokes the str method, and displays the string it returns. That automatic invocation in response to ordinary syntax is exactly what makes it a special method — the same property that makes __init__ run when an object is instantiated, without anyone writing time.__init__(9, 45). Two practical consequences follow. First, __str__ must RETURN the string rather than print it; a version that prints gives you the right text followed by None, because print then displays the method's missing return value. Second, the name must be exact — two underscores, str, two underscores — since a near miss like __string__ is a perfectly legal ordinary method that nothing ever invokes, so the behaviour simply never appears and nothing raises. And this same mechanism is what the next lesson builds on: for every operator in Python there is a corresponding special method, so defining __add__ makes the plus sign work on your objects.

62. Explain it to someone else

Explain it

Two methods, and neither is ever called by name.

Discussion prompt

A classmate asks why __init__ and __str__ have those strange names and where they get called from. Explain both.

Hint: What syntax triggers each?

Answer:

The double underscores mark a method the language itself knows about. Python looks for those exact names, so __init__ runs when an object is instantiated and __str__ when one is printed.

Which means you never write the calls: Time(9, 45) invokes __init__ with the arguments you gave, and print(time) invokes __str__ and displays what it returns.

The practical warning is that a near miss fails silently. _init_ or __string__ is a legal ordinary method that nothing calls, so your objects come out with no attributes or still print as an address — and nothing raises to tell you why.

63. Exit ticket

Exit ticket

One honest answer. It decides what the next lesson opens with.

Predict first

Which of these is still least solid for you?

  • __init__, and what each number of arguments produces
  • __str__, and the difference between returning and printing
  • What makes a method special, and why the exact name matters
  • Initialising every attribute, and keeping the interface separate

Correct: Whichever you picked is the right answer — this one is for you, not for a mark.

Why: The init method is mechanically simple, and the part worth practising is the default arguments — four call shapes from one definition. The return-versus-print distinction has a distinctive symptom, correct output followed by None, which is worth recognising on sight. The special-method mechanism is the one that generalises: understanding that ordinary syntax invokes a named method is what makes the next lesson's operator overloading obvious rather than magical. And the interface principle is the chapter's design advice, which costs nothing now and saves a great deal when a representation changes.

64. Synthesis: draw the map of this lesson

Connect it up

One page, from memory.

Draw it

Write __init__ and __str__ for the Time class from memory, and beside each write the ordinary syntax that invokes it. Underneath, list the four calls from Time() to Time(9, 45, 30) with what each produces. Then draw a line down the page with the interface on one side and the implementation on the other, and put the Time class's methods and attributes on the correct sides.

65. What you can do now

Recap

Two pages, and the two methods to write first in any new class.

If you remember one thingIt is this
From __init__Construction moves into the class, so there is one place to get it right.
From __str__Return the string. Printing it gives you the text and then None.
From special methodsOrdinary syntax invokes them, and a misspelled name fails silently.
From initialisationEvery instance of a type should have the same attributes.
From the design principleCallers use methods, so the representation stays free to change.

The next lesson uses the same mechanism to make operators work on your objects: __add__ for the plus sign, type-based dispatch for adding either a Time or a number, __radd__ for when the object is on the right, and polymorphism — where a function you already wrote turns out to work on a type you never planned for.

Think Python, 2nd edition — Allen B. Downey §17.5-17.6, pp. 164-165 — everything on these slides traces back here

Sources

  1. Think Python, 2nd edition — Allen B. Downey — Allen B. Downey, Think Python: How to Think Like a Computer Scientist, 2nd edition (Green Tea Press, 2015), §17.5-17.6, pp. 164-165
  2. Python documentation — Classes
  3. Python documentation — Built-in Types

Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108