17a Methods: Object-Oriented Features and Printing Objects

This lesson turns functions into methods, covers the two ways to invoke one, explains the self convention and the subject metaphor, and decodes the argument-count error that method syntax produces.

Subject: Python · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Lesson 17a Methods: Object-Oriented Features and Printing Objects

Title

Python · Chapter 17 — Classes and methods

§17.1-17.4, pp. 161-164

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.1-17.4, pp. 161-164 — the pages these objectives are drawn from

3. Before we start: what do these functions have in common?

Warm-up

Look at the Time program from the last chapter.

Discussion prompt

print_time, increment, is_after, time_to_int — list what every one of them has in common, and say what the program does not record about it.

Hint: Look at the first parameter of each.

Answer:

Every one of them takes at least one Time object as an argument. That is not a coincidence — they are all operations on times.

And nothing in the program says so. The class definition and the function definitions sit next to each other with no connection between them beyond the order they happen to be written in.

This chapter makes that connection explicit, by moving the functions inside the class. The transformation is mechanical, and what it buys is that the structure of the program becomes visible.

4. The one idea behind this chapter: make the relationship explicit

Concept

The programs from the last two chapters are not really object-oriented because they don't represent the relationships between programmer-defined types and the functions that operate on them. The next step is to transform those functions into methods that make the relationships explicit.

method — A function that is associated with a particular class, and defined inside a class definition.

These features are not strictly necessary; most of them provide alternative syntax for things we have already done. But in many cases the alternative is more concise and more accurately conveys the structure of the program.

Figure (svg): Two columns contrasting functions beside a class with methods inside it

The same computation. What changes is what the program says about itself.

Think Python, 2nd edition — Allen B. Downey §17.1-17.4, pp. 161-162

5. What object-oriented programming is

Section

Section 1

6. Three defining characteristics

Concept

Python is an object-oriented programming language, which means that it provides features that support object-oriented programming — which has these defining characteristics.

The Time class corresponds to the way people record the time of day, and the functions we defined correspond to the kinds of things people do with times. Similarly, the Point and Rectangle classes correspond to the mathematical concepts of a point and a rectangle.

Think Python, 2nd edition — Allen B. Downey §17.1-17.4, pp. 161-161

7. Picture it: the classes so far, and what they correspond to

Picture it

Each was chosen because it matches something outside the program.

Figure (svg): Three classes from the course paired with what each represents

Objects often represent things in the real world, and methods correspond to how those things interact.

Which is why the classes have felt natural: they were chosen to match something you already had a concept for.

8. Worked example: what the transformation actually changes

Worked example

The book is unusually honest about this.

# before: a function beside the class
def print_time(time):
    print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))

# after: the same body, indented inside the class
class Time:
    def print_time(time):
        print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
AspectWhat changedNote
the bodyunchangedcharacter for character
the indentationone level deeperinside the class
what it computesexactly the same

Move the definition inside.

Why: To make print_time a method, all we have to do is move the function definition inside the class definition. Notice the change in indentation.

Note that nothing else changed.

Why: The body is identical, and the function computes what it always computed.

Note what the book says about it.

Why: This transformation is purely mechanical; you can do it by following a sequence of steps.

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

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

One level of indentation. That is genuinely all the transformation is, which is why the chapter can be about what it means rather than how to do it.

Verify: Ask what would break if the class had no other members.

Why: Nothing — a class body containing only a method is complete, exactly as one containing only a docstring was. And the method is available immediately, which shows the relationship is established by position rather than by anything declared.

9. Predict: what changes when a function becomes a method?

Prediction

The body is untouched.

class Time:
    def print_time(time):
        print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
AspectWhat happenedNote
the bodyidenticalto the function version
the indentationone level deeperinside the class
the computationunchanged

Predict first

What did moving the definition inside the class change?

  • Where it is defined and how it is invoked — not what it computes
  • The computation, which is now faster
  • The function's parameters, which are now automatic
  • Nothing at all, including the invocation

Correct: Where it is defined and how it is invoked — not what it computes.

Why: The transformation is purely mechanical and the body is unchanged. What it buys is that the relationship between the class and the function becomes explicit, and that a second, more concise invocation syntax becomes available. The computation is identical.

10. Worked example: why bother, if it changes nothing

Worked example

The book's case, stated carefully.

# in Time1.py there is no obvious connection
# between the class definition and the function
# definitions that follow.

# With some examination, it is apparent that
# every function takes at least one Time object
# as an argument.
AspectBeforeNote
the connectionexists in factevery function takes a Time
the programdoes not say soyou have to examine it
methodssay itby where they are written

Identify what is missing.

Why: There is no obvious connection between the class definition and the function definitions that follow — the relationship is real and unstated.

Identify how you would find it.

Why: With some examination, it is apparent that every function takes at least one Time object as an argument. That examination is work a reader has to do.

State the benefit precisely.

Why: The alternative is more concise and more accurately conveys the structure of the program.

Figure (svg): Two columns separating what the transformation changes from what it does not

Most of them provide alternative syntax for things we have already done.

The gain is in what the program communicates rather than in what it computes. The book does not overstate it: these features are not strictly necessary.

Verify: Ask where the benefit becomes concrete.

Why: The book says that 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 — and that in the examples so far it may not be obvious. So the honest position is that the payoff arrives later, which §17.9's polymorphism section delivers.

11. Trap: assuming object-oriented means better

Trap

The trap

A student concludes that any program not using classes and methods is written wrongly.

Take the chapter's subject as an instruction

Why: The book does say the previous programs are not really object-oriented.

It also says the features are not strictly necessary and that most of them provide alternative syntax for things we have already done. Not really object-oriented is a description, not a criticism.

The fix

Read the claim as being about expression.

The alternative more accurately conveys the structure

Why: Which is a real benefit and a specific one.

And the book adds: it is not obvious that it is useful

Why: In the examples seen so far.

Being able to convert between the two forms is the stated goal: if you are comfortable converting from one form to another, you will be able to choose the best form for whatever you are doing. Choosing is the skill, not always picking one.

12. Two truths and a lie: object-oriented features

Two truths and a lie

Two are true. Keep the lie.

Eliminate the wrong options

Rule out the two true statements.

  • A. The features are not strictly necessary, and most provide alternative syntax
  • B. Objects often represent things in the real world, and methods how those things interact
  • C. A program that uses methods computes its results more efficiently

Survives elimination: C

Why: C claims a benefit the book never does. The transformation is purely mechanical and the bodies are unchanged, so nothing about the computation differs. The stated benefit is that the alternative is more concise and more accurately conveys the structure of the program.

13. Complete it: make a function into a method

Faded example

One level of indentation.

Fill in the blanks

class Time:
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))

Why: Moving the definition inside the class body is the entire transformation — the function's body is unchanged. What it establishes is the relationship between the class and the operation, which the previous version left for a reader to notice.

14. Explain it yourself: what does *not really object-oriented* mean?

Explain it to yourself

The previous chapters used classes and objects throughout.

Discussion prompt

Chapters 15 and 16 defined classes and created objects. Why does the book say those programs are not really object-oriented?

Hint: Look at the three characteristics.

Answer:

Because of the first characteristic: programs include class AND METHOD definitions. Those chapters had classes and no methods, so the operations sat outside the types they operated on.

The consequence is the second characteristic's absence too — the computation is expressed as functions taking objects rather than as operations on objects, which is a difference in framing rather than in result.

So the phrase is precise rather than dismissive. The programs used objects and did not represent the relationships between the types and the functions operating on them, which is exactly what this chapter adds.

15. Two ways to invoke a method

Section

Section 2

16. Function syntax and method syntax

Concept

Now there are two ways to call print_time. The first — and less common — way is to use function syntax; the second, and more concise, is to use method syntax.

subject — The object a method is invoked on.

>>> Time.print_time(start)
09:45:00
>>> start.print_time()
09:45:00
CallWhat the parts areNote
Time.print_time(start)the class, the method, the objectfunction syntax
start.print_time()the object, the methodmethod syntax
bothidentical outputthe same call

In the first, Time is the name of the class and print_time the name of the method, with start passed as a parameter. In the second, print_time is the name of the method and start is the object the method is invoked on — which is called the subject.

Think Python, 2nd edition — Allen B. Downey §17.1-17.4, pp. 162-163

17. Picture it: the object moves from inside the parentheses to outside

Picture it

The same three pieces, arranged two ways.

Figure (svg): Two columns showing the same method call written in function syntax and method syntax

Exactly the same call. The object has moved from inside the parentheses to in front of the dot.

Which is more than cosmetic: inside the method, the subject is assigned to the first parameter either way.

18. Worked example: where the subject goes

Worked example

The first parameter receives it, in both syntaxes.

class Time:
    def print_time(time):
        print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))

>>> start.print_time()
# start is assigned to time
PartWhat happensNote
start.print_time()no arguments in the parenthesesand one parameter
the subjectstartassigned to time
inside the methodtime refers to startordinary parameter passing

Look at the call.

Why: The parentheses are empty, so it looks like a call with no arguments.

Look at the definition.

Why: The method has one parameter, which would be a mismatch if the parentheses told the whole story.

Reconcile them.

Why: Inside the method, the subject is assigned to the first parameter — so start is assigned to time.

Figure (svg): A call diagram showing the subject of a method call being bound to the first parameter

The object before the dot becomes the first parameter inside.

The subject fills the first parameter. That is the single mechanical fact behind every method call, and it explains the argument counting in the next idea.

Verify: Check against the function syntax.

Why: Time.print_time(start) passes start explicitly as the one argument, which lands in the same parameter. So the two syntaxes differ only in whether the subject is written inside the parentheses or before the dot — which makes the mechanism easier to see in the less common form.

19. Predict: are these the same call?

Prediction

Two syntaxes for one method.

Time.print_time(start)
start.print_time()
CallThe syntaxNote
the firstfunction syntaxstart as an argument
the secondmethod syntaxstart as the subject
bothstart is assigned to the first parameteridentical

Predict first

What is the relationship between these two calls?

  • They are the same call written two ways
  • The first passes the class and the second passes the object
  • The second is faster
  • Only the second one works

Correct: They are the same call written two ways — in both, start is assigned to the first parameter.

Why: In the function syntax Time is the class and start is passed as a parameter; in the method syntax start is the subject and is assigned to the first parameter inside. The book calls the second more concise and more common, and the first is a useful way to see the mechanism, since the subject is written explicitly.

20. Worked example: the self convention

Worked example

The first parameter has a conventional name and a reason for it.

class Time:
    def print_time(self):
        print('%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second))
AspectWhat is trueNote
the parameter nameself, by conventionnot a keyword
what it holdsthe subjectthe object invoked on
why the namean implicit metaphorthe object is the agent

Rename the parameter.

Why: By convention, the first parameter of a method is called self, so it would be more common to write print_time this way.

Note it is a convention.

Why: Not a keyword — the earlier version named it time and worked identically. Following the convention is what makes the code readable to anyone else.

Note the metaphor.

Why: The syntax for a function call suggests the function is the active agent; in object-oriented programming, the objects are the active agents.

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

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

The same method, with the conventional parameter name. The convention exists because of the metaphor, and the metaphor is what the syntax is expressing.

Verify: Read both call forms aloud.

Why: print_time(start) says: Hey print_time! Here's an object for you to print. start.print_time() says: Hey start! Please print yourself. The book's own gloss — and it makes the reason for the naming convention concrete rather than arbitrary.

21. Trap: forgetting the self parameter

Trap

The trap

A method is defined as def print_time(): with no parameters, since the call has empty parentheses.

Match the definition to the call

Why: start.print_time() shows no arguments, so none seem needed.

The subject is passed whether or not you declared a parameter for it, so the call raises TypeError: takes 0 positional arguments but 1 was given — for a call with nothing in the parentheses.

The fix

Always declare the first parameter.

def print_time(self):

Why: Which receives the subject.

And name it self, by convention

Why: So that anyone reading recognises it immediately.

The empty parentheses at the call site are misleading, and this is the mismatch they cause. Reading the method syntax as the subject plus whatever is in the parentheses makes both the definition and the error message predictable.

22. Discriminate: which syntax is this?

Discrimination

Look at what comes before the dot.

Sort into buckets

For each call, which syntax is being used?

method syntax
start.print_time(); end.is_after(start); t.time_to_int()
function syntax
Time.print_time(start); Time.is_after(end, start); Time.time_to_int(t)
meth
In each, an object comes before the dot and is the subject — so it is assigned to the first parameter, and any remaining arguments go in the parentheses.
func
In each, the class name comes before the dot and every argument including the subject is written inside the parentheses. Less common, and it makes the mechanism visible.

23. Complete it: the conventional first parameter

Faded example

It receives the subject.

Fill in the blanks

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

Why: By convention the first parameter of a method is called self, and it receives the subject — the object the method was invoked on. It is a convention rather than a keyword, so any name would work; using self is what makes the code readable to everyone else.

24. Explain it: why is it called self?

Explain it

The name is a convention with a reason behind it.

Discussion prompt

A classmate asks why the first parameter is called self rather than something descriptive like time. Explain the metaphor.

Hint: Read both call forms aloud.

Answer:

The function syntax suggests the function is the active agent: print_time(start) says something like Hey print_time! Here's an object for you to print.

The method syntax reverses that: start.print_time() says Hey start! Please print yourself. The object is the one being addressed, so inside the method it refers to itself — self.

The book is honest that this change in perspective might be more polite and it is not obvious that it is useful, at least in the examples so far. What it eventually buys is versatility, which the polymorphism section delivers.

25. Methods with extra arguments

Section

Section 3

26. The subject, then everything else

Concept

Here's a version of increment rewritten as a method. This version assumes that time_to_int is written as a method — and note that it is a pure function, not a modifier.

# inside class Time:
    def increment(self, seconds):
        seconds += self.time_to_int()
        return int_to_time(seconds)

>>> end = start.increment(1337)
>>> end.print_time()
10:07:17
ParameterWhat it receivesNote
selfthe subjectstart
secondsthe first written argument1337
self.time_to_int()a method on the subjectchained

The subject, start, gets assigned to the first parameter, self; the argument, 1337, gets assigned to the second parameter, seconds. Note also that this version is a pure function — it returns a new Time rather than modifying the subject.

Think Python, 2nd edition — Allen B. Downey §17.1-17.4, pp. 163-164

27. Picture it: where each parameter's value comes from

Picture it

One comes from before the dot and the rest from inside the parentheses.

Figure (svg): A call diagram showing the subject and the written argument mapped to two parameters

The subject fills the first parameter, and the parentheses fill the rest.

That is the whole mechanism, and it is why counting arguments at the call site gives one fewer than the method declares.

28. Worked example: the confusing error message

Worked example

Everyone meets this once, and the explanation is one sentence.

>>> end = start.increment(1337, 460)
TypeError: increment() takes 2 positional arguments but 3 were given
SourceHow manyNote
in the parenthesestwo arguments1337 and 460
the subjectalso an argumentstart
the totalthreeand the method takes two

Read the message and be confused.

Why: The error message is initially confusing, because there are only two arguments in parentheses.

Count the subject.

Why: But the subject is also considered an argument, so all together that's three.

Recount the method.

Why: increment declares self and seconds, which is two — hence the mismatch.

Figure (svg): A panel decoding the argument-count error message for a method call

Three given and two accepted, because the subject counts. Once you know that, every argument-count error on a method reads correctly.

Verify: Check the opposite mistake.

Why: Defining a method with no parameters and calling it with empty parentheses gives takes 0 positional arguments but 1 was given — the same rule, in the other direction. Both messages are only confusing until you know the subject is counted, and then both say exactly what is wrong.

29. Predict: how many arguments were given?

Prediction

Two in the parentheses.

end = start.increment(1337, 460)
# TypeError: increment() takes 2 positional
# arguments but 3 were given
SourceWhat it contributesCount
the subjectstartcounted
the written arguments1337 and 460two more
the totalthree

Predict first

Why does the message say three?

  • The subject is also considered an argument, so all together that is three
  • Python counts the method name as an argument
  • The error message is wrong
  • Because increment calls another method internally

Correct: The subject is also considered an argument, so all together that is three.

Why: The message is initially confusing because there are only two arguments in parentheses — but the object before the dot fills the first parameter, so it counts too. Knowing that makes every argument-count error on a method readable: add one to what you can see.

30. Worked example: positional and keyword arguments

Worked example

The message says positional, and the book explains why.

sketch(parrot, cage, dead=True)

# parrot and cage are POSITIONAL
# dead is a KEYWORD argument
KindHow it is writtenHow it is matched
positionalno parameter name givenmatched by position
keywordname=valuematched by name
the error messagecounts the positional oneshence the wording

Define the term.

Why: A positional argument is an argument that doesn't have a parameter name; that is, it is not a keyword argument.

Read the example.

Why: In sketch(parrot, cage, dead=True), parrot and cage are positional, and dead is a keyword argument.

Connect it to the message.

Why: That is why the error says positional arguments rather than just arguments — it is counting the ones matched by position, which includes the subject.

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

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

Two kinds of argument, and the error message names the kind it is counting. You have already used keyword arguments — print's sep, and sort's reverse.

Verify: Find the keyword arguments you have already used.

Why: print(word, freq, sep='\t') from lesson 13b and t.sort(reverse=True) from the same lesson are both keyword arguments, used before the term was introduced. Recognising them retrospectively is worth a moment, because it shows the feature was familiar before it was named.

31. Trap: passing the subject twice

Trap

The trap

A student writes start.increment(start, 1337), reasoning that the method needs a Time and a number.

Supply every parameter the method declares

Why: It has two, so two arguments look right.

The subject already fills the first parameter, so this passes three things to a method that takes two — and the extra Time lands in seconds, or the count error fires.

The fix

Write only the arguments after the first.

start.increment(1337)

Why: The subject fills self; the parentheses fill the rest.

Or use function syntax if you want them all visible

Why: Time.increment(start, 1337) writes both explicitly.

The rule to hold: a method call passes the subject plus whatever is in the parentheses. Counting that way makes both the correct call and the error message obvious.

32. Sort: positional or keyword?

Sorting

A positional argument has no parameter name.

Sort into buckets

In sketch(parrot, cage, dead=True), and in calls you have seen, which kind is each?

positional
parrot, in sketch(parrot, cage, dead=True); 1337, in start.increment(1337); the subject of a method call
keyword
dead=True, in the same call; sep='\t', in a print call; reverse=True, in t.sort(reverse=True)
pos
Each is matched by its position rather than by a name — including the subject, which is why the error message counts it among the positional arguments.
kw
Each is written as name=value and matched by the parameter's name, so its position does not matter.

33. Complete it: call a method with an argument

Faded example

The subject is not written in the parentheses.

Fill in the blanks

end = start.increment(1337)
end.print_time() # 10:07:17

Why: The subject start fills the first parameter, self, so only the remaining argument goes in the parentheses. Writing start.increment(start, 1337) would pass three things to a two-parameter method and raise the argument-count error.

34. Explain it: my method says it got one too many

Explain it

The commonest first error with methods.

Discussion prompt

A classmate calls a method with what looks like the right number of arguments and gets a count error one higher than they wrote. Explain.

Hint: What is before the dot?

Answer:

The subject counts. The object before the dot is assigned to the first parameter, so a call with two things in the parentheses passes three arguments in total.

Which means the method's definition needs one more parameter than the call appears to supply — conventionally named self, and it comes first.

The rule that makes both readable: a method call passes the subject plus whatever is in the parentheses. Add one to what you can see, and the message says exactly what is wrong.

35. Methods taking another object of the same class

Section

Section 4

36. self and other

Concept

Rewriting is_after is slightly more complicated because it takes two Time objects as parameters. In this case it is conventional to name the first parameter self and the second parameter other.

# inside class Time:
    def is_after(self, other):
        return self.time_to_int() > other.time_to_int()

>>> end.is_after(start)
True
ParameterWhat it receivesNote
selfthe subjectend
otherthe written argumentstart
the comparisonboth converted to integersusing the method form

To use this method, you have to invoke it on one object and pass the other as an argument. One nice thing about this syntax is that it almost reads like English: end is after start?

Think Python, 2nd edition — Allen B. Downey §17.1-17.4, pp. 164-164

37. Picture it: the call reads like a sentence

Picture it

Subject, verb, object.

Figure (svg): A method call broken into its parts alongside the English sentence it resembles

Just as the subject of a sentence is what the sentence is about, the subject of a method invocation is what the method is about.

That readability is the concrete benefit in this example — the function form, is_after(end, start), gives no clue which order the arguments go in.

38. Worked example: the ordering the syntax makes clear

Worked example

Which is first matters, and the two forms differ in how obvious that is.

# function form: which order?
is_after(end, start)

# method form: reads like English
end.is_after(start)
FormHow clear is the orderNote
the function formtwo arguments of the same typethe order is a convention to remember
the method formsubject then objectend is after start
the riskswapping themmuch easier in the first

Look at the function form.

Why: Two Time arguments, and nothing in the call says which is the earlier — you have to remember or check.

Look at the method form.

Why: One nice thing about this syntax is that it almost reads like English: end is after start?

Note what that prevents.

Why: Swapping the arguments produces the opposite answer, silently. The method form makes the swap read wrongly, which is a real defence.

Figure (svg): Two columns comparing the readability of the function form and the method form

The benefit here is not conciseness but legibility of the argument order.

The same comparison, with the method form making the argument order legible. This is the more accurately conveys the structure claim made concrete.

Verify: Swap the arguments in both forms and read them.

Why: is_after(start, end) looks perfectly plausible and is wrong; start.is_after(end) reads as start is after end, which is visibly the wrong question. The error is equally easy to make and much easier to see, which is exactly the kind of benefit the book claims.

39. Predict: which time is the subject?

Prediction

The call reads like a sentence.

# start is 09:45, end is 10:07
print(end.is_after(start))
PartIts roleNote
endthe subjectself
startthe argumentother
the questionis end after start?True

Predict first

What does this print?

  • True
  • False
  • The two times
  • A TypeError about arguments

Correct: True — end is later than start, and the call asks exactly that question.

Why: The subject is what the method is about, so end.is_after(start) asks whether end comes after start. Writing start.is_after(end) would ask the opposite question and print False — which the method syntax makes visible, since it reads as start is after end.

40. Worked example: calling a method on self

Worked example

Inside a method, the subject is an ordinary object.

    def is_after(self, other):
        return self.time_to_int() > other.time_to_int()

# self.time_to_int() is an ordinary method call
# whose subject happens to be self
ExpressionWhat it isNote
self.time_to_int()a method call on the subjectnothing special
other.time_to_int()the same methodon the other object
bothreturn integersthen compared

Note that self is just a name.

Why: It refers to an object, and every operation available on a Time is available through it.

Call a method on it.

Why: self.time_to_int() invokes the method with self as the subject — the same syntax as any other call.

Note the symmetry.

Why: other.time_to_int() does the same thing on the other object, and the two results are compared as ordinary integers.

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

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

Two conversions and a comparison. The base-60 insight from the previous chapter is doing the work, and the method form just relocates where the conversion lives.

Verify: Compare with the version from chapter 16.

Why: That one built tuples and compared them; this one converts both to integers and compares those. Both are correct, and this version reuses time_to_int rather than restating the field order — which is the reuse the conversion functions were an investment in.

41. Trap: naming the second parameter after its type

Trap

The trap

A method is written as def is_after(self, time2):, describing what the argument is.

Name parameters after their contents

Why: Which is good advice for ordinary functions.

It says nothing a reader did not know — both parameters are Times — and it breaks the convention, so a reader has to work out whether time2 plays the self role or the other role.

The fix

Use self and other.

def is_after(self, other):

Why: Which is what the book calls conventional for this case.

And the call site carries the meaning

Why: end.is_after(start) says which is which.

The convention is doing real work here: it tells a reader immediately that the second parameter is another instance of the same class, which a type-based name would not distinguish from any other Time-valued argument.

42. Complete it: the conventional second parameter

Faded example

Another instance of the same class.

Fill in the blanks

def is_after(self, other):
return self.time_to_int() > other.time_to_int()

Why: When a method takes a second object of the same class, it is conventional to name the first parameter self and the second other. The convention tells a reader immediately that the argument is another instance rather than an unrelated value, which a type-based name would not.

43. Compare: function form and method form

Comparison

Fill the blanks. The same operation, two ways.

Comparison matrix

Questionis_after(t1, t2)t1.is_after(t2)
Where is it defined?beside the classinside the class
How many arguments are written?twoone — the subject is before the dot
How obvious is the order?by convention onlyby grammar — it reads like English
What does it compute?the same thingthe same thing

The bottom row is the honest one: the transformation is purely mechanical, and the benefits are all in the other rows.

44. Where the subject-verb-object shape helps

Real world

An interface that reads like a sentence.

Discussion prompt

Think of an instruction you have given a machine or a person where the order of two similar things mattered. What made it easy or hard to get right?

Hint: Copy A to B, or move this before that.

Answer:

Copying one file over another, transferring between two accounts, scheduling one thing before another — in each case both arguments are the same kind of thing, and only the order distinguishes them.

What makes it easy is when the phrasing carries the direction: move this into that is harder to get backwards than a form with two identical boxes.

Which is the argument for end.is_after(start) over is_after(end, start). The mistake is equally easy to make and much easier to see, because the wrong version reads as the wrong question.

45. What does not become a method

Section

Section 5

46. A method needs an object to be invoked on

Concept

The exercise is to rewrite time_to_int as a method — and the book adds a warning: you might be tempted to rewrite int_to_time as a method too, but that doesn't really make sense, because there would be no object to invoke it on.

# a method: the subject is the Time
    def time_to_int(self):
        minutes = self.hour * 60 + self.minute
        return minutes * 60 + self.second

# NOT a method: its argument is an integer,
# and it creates the Time
def int_to_time(seconds):
    ...
FunctionDoes it have a subject?Note
time_to_intoperates on an existing Timea natural method
int_to_timecreates a Time from a numberno subject exists yet
the testis there an object to invoke it on?

That is the test for whether an operation should be a method: is there an object of the class that the operation is about? For a conversion that produces a Time from an integer, the answer is no.

Think Python, 2nd edition — Allen B. Downey §17.1-17.4, pp. 163-163

47. Picture it: which direction has a subject

Picture it

One conversion starts with a Time and one ends with one.

Figure (svg): Two conversions shown in opposite directions with the subject marked on one

The method has something to be invoked on. The function does not.

So the two halves of a conversion pair need not be the same kind of thing — which is a genuine asymmetry rather than an inconsistency.

48. Worked example: time_to_int as a method

Worked example

The subject supplies the fields.

# inside class Time:
    def time_to_int(self):
        minutes = self.hour * 60 + self.minute
        seconds = minutes * 60 + self.second
        return seconds

>>> start.time_to_int()
35100
PartWhat changedNote
the parameterself replaces timethe only change
the bodyself.hour instead of time.hourmechanical
the callstart.time_to_int()no arguments

Rename the parameter.

Why: time becomes self, and every reference in the body follows.

Move it inside the class.

Why: Indentation, as before — the transformation is purely mechanical.

Call it on a Time.

Why: start.time_to_int() with empty parentheses, since the subject supplies the only input.

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

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

35100 seconds for 09:45:00. A method with no arguments beyond its subject, which is the commonest shape for a query.

Verify: Check the number.

Why: Nine hours is 32400 seconds and forty-five minutes is 2700, which sum to 35100. Verifying with a time whose arithmetic you can do in your head is what confirms the conversion rather than merely that it ran.

49. Discriminate: method or plain function?

Discrimination

Ask whether there is an object to invoke it on.

Sort into buckets

For each operation, which form fits?

a method
convert a Time to an integer; print a Time; test whether one Time is after another
a plain function
build a Time from a number of seconds; make a Time representing right now; parse a Time out of a string
meth
Each is about an existing Time, which becomes the subject — the operation has something to be invoked on.
func
Each produces a Time from something else, so no Time exists to invoke it on. Creating an object is not an operation on that object.

50. Worked example: why int_to_time cannot be one

Worked example

The test is whether a subject exists.

# what would the call even look like?
#   3661.int_to_time()   - on an integer?
#   Time.int_to_time(3661) - a class-level call
#
# there is no Time to invoke it on, because
# creating one is what the function does
AttemptWhy it failsNote
invoking on an integerthe wrong classint does not have this method
invoking on a Timewhich Time?the result does not exist yet
the conclusiona plain function

Ask what the subject would be.

Why: The input is an integer, which is not a Time — and the Time is what the function produces.

Note the circularity.

Why: There would be no object to invoke it on, because the object is the output rather than the input.

Leave it as a function.

Why: Which is the book's own conclusion, and it is not a failure of the design.

Figure (svg): A decision flowchart for whether an operation should be a method

One question, and it settles most cases.

No sensible subject exists, so it stays a plain function. The next lesson's __init__ addresses the same need from a different direction.

Verify: Ask how the next chapter handles it.

Why: The __init__ method builds a Time from arguments at the moment of creation, which is exactly the job int_to_time does — so the construction case has its own mechanism rather than being forced into a method. Recognising that this gap is filled deliberately makes __init__ read as an answer rather than as new syntax.

51. Trap: making everything a method

Trap

The trap

Every function in the module is moved inside the class, since the chapter is about methods.

Apply the transformation uniformly

Why: It is mechanical, so it can be applied to anything.

Functions with no natural subject end up invoked on an arbitrary object, or on the class, and the relationship the method syntax claims to express is not there. The form says something untrue about the code.

The fix

Convert the operations that are about an instance.

Ask whether there is an object to invoke it on

Why: Which is the book's own test.

Leave the others as functions

Why: int_to_time is the book's example, and it stays one.

The stated goal is to be able to choose: if you are comfortable converting from one form to another, you will be able to choose the best form for whatever you are doing. Converting everything is not choosing.

52. Predict: what does the method return?

Prediction

The subject supplies every field.

# start is 09:45:00
print(start.time_to_int())
ColumnContributionValue
hours9 x 360032400
minutes45 x 602700
seconds00

Predict first

What does this print?

  • 35100
  • 945
  • 540
  • 35160

Correct: 35100 — nine hours is 32400 seconds and forty-five minutes is 2700.

Why: The method converts each field to seconds and adds them, exactly as the function version did — the only change is that the fields come from self rather than from a parameter. Verifying with arithmetic you can do in your head is what confirms the conversion rather than merely that it ran.

53. Complete it: time_to_int as a method

Faded example

The subject replaces the parameter.

Fill in the blanks

def time_to_int(self):
minutes = self.hour * 60 + self.minute
return minutes * 60 + self.second

Why: The single parameter receives the subject, and by convention it is named self — so every reference to the Time's fields becomes self.hour and so on. The call is then start.time_to_int(), with empty parentheses, since the subject supplies the only input the method needs.

54. Think it through: why is creating an object not a method?

Socratic

The asymmetry is worth being precise about.

Discussion prompt

time_to_int becomes a method and int_to_time does not. Why is creating an object different in kind from operating on one?

Hint: What does the subject have to be?

Answer:

Because a method's subject is an existing instance, and creation is the process by which an instance comes to exist. There is nothing to be the subject until it is finished.

So a creation operation takes its inputs and produces an object, which is the shape of an ordinary function — whereas a method takes an object and does something with it.

Python does provide a mechanism for the creation case: the __init__ method, which runs at the moment an object is instantiated. That is the next lesson, and it is worth seeing this gap first so that __init__ reads as filling it.

55. Compare: a function and a method

Comparison

Fill the blanks. The book says the difference is syntactic.

Comparison matrix

QuestionA functionA method
Where is it defined?at the top levelinside a class definition
How is it invoked?f(obj, ...)obj.f(...)
Where does the first argument come from?the parenthesesthe subject, before the dot
What does it compute?the same thingthe same thing

Methods are semantically the same as functions, and there are two syntactic differences — which is exactly the two middle rows.

56. The procedure: converting a function into a method

Pattern

Five steps, and the book calls the whole thing purely mechanical.

  1. Check that the operation is about an instance of the class — that there is an object to invoke it on.
  2. Move the definition inside the class body, one level of indentation deeper.
  3. Rename the first parameter to self, and update every reference to it in the body.
  4. If a second parameter is another instance of the same class, name it other.
  5. Change the call sites from f(obj, x) to obj.f(x), remembering that the subject is no longer written in the parentheses.

Step 5 is where the argument-count error comes from. The subject is still an argument — it has just moved outside the parentheses, so a method needs one more parameter than its calls appear to supply.

Python documentation — Classes Classes

57. Check yourself 1 of 3: the two syntaxes

Check

Both invoke the same method.

Check your understanding

Which pair of calls does the same thing?

  • A. Time.print_time(start) and start.print_time() (correct)
  • B. print_time(Time) and Time.print_time()
  • C. start.print_time(start) and Time.print_time()
  • D. print_time(start) and start.print_time(start)

Answer: A

Why: In the function syntax, Time is the class and start is passed as a parameter; in the method syntax, start is the subject and is assigned to the first parameter. Both end with start bound to self, so both produce the same output.

Why B tempts people
This passes the class itself as the object, which has no hour attribute — and the second call has no subject.
Why C tempts people
The first passes start twice, giving one argument too many. The second has no subject to print.
Why D tempts people
The second passes start as both the subject and an argument, which is the one-too-many error.

58. Check yourself 2 of 3: the argument count

Check

Two things in the parentheses.

start.increment(1337, 460)
# TypeError: increment() takes 2 positional
# arguments but 3 were given
SourceCountNote
the subjectstartcounted
the parenthesestwo arguments1337, 460
totalthree

Check your understanding

Why does the message say three arguments were given?

  • A. The subject is also considered an argument (correct)
  • B. The method name counts as an argument
  • C. increment calls another method internally
  • D. Keyword arguments are counted twice

Answer: A

Why: The error message is initially confusing because there are only two arguments in parentheses — but the object before the dot fills the first parameter, so all together that is three. Adding one to what you can see makes every such message readable.

Why B tempts people
The name is not an argument; it is what is being called.
Why C tempts people
Internal calls have their own argument counts and do not affect this one.
Why D tempts people
There are no keyword arguments here, and they would be counted separately in any case.

59. Check yourself 3 of 3: which becomes a method

Check

One of these has no subject.

Check your understanding

Why does the book say int_to_time should not be rewritten as a method?

  • A. Because there would be no object to invoke it on (correct)
  • B. Because it is a pure function
  • C. Because it takes only one argument
  • D. Because methods cannot return objects

Answer: A

Why: Its input is an integer and its output is the Time, so no Time exists to be the subject — creating an object is not an operation on that object. The next chapter's __init__ method handles the construction case with its own mechanism.

Why B tempts people
increment as a method is also a pure function, which the book points out explicitly.
Why C tempts people
time_to_int as a method takes only its subject and is perfectly natural.
Why D tempts people
Methods return objects routinely — increment returns a new Time.

60. Where this shows up outside this course

Real world

Putting an operation with the thing it operates on.

Discussion prompt

Think of instructions or tools organised by what they act on rather than in one long list. What does that organisation make easier?

Hint: A manual with a chapter per component.

Answer:

A manual with a section per part, tools stored by the job they belong to, settings grouped under the thing they configure — in each case the organisation answers what can I do with this? rather than what operations exist?

Which is the question you actually have when you are holding something. A flat list requires you to know the operation's name before you can find it.

That is what defining methods inside a class buys: the operations on a Time are found by looking at Time, rather than by scanning a module for functions whose first parameter happens to be one.

61. Confidence wager: commit before you check

Commit first

Answer, then rate your confidence. Everyone meets this one.

Predict first

You call start.increment(1337, 460) and get TypeError: increment() takes 2 positional arguments but 3 were given. Why three?

  • The subject is also considered an argument, so start, 1337 and 460 make three
  • Python counts the method name as an argument
  • The error message is misreporting the count
  • increment internally calls another method that adds an argument

Correct: The subject is also considered an argument, so start, 1337 and 460 make three.

Why: The book flags this because the message is initially confusing: there are only two arguments in parentheses. But method syntax passes the object before the dot as the first argument, so it counts too — and increment declares self and seconds, which is two. The rule worth holding is that a method call passes the subject plus whatever is in the parentheses, so add one to what you can see. The same rule explains the mirror-image error: defining a method with no parameters and calling it with empty parentheses gives takes 0 positional arguments but 1 was given, because the subject arrives whether or not you declared a parameter for it. And positional is in the message because a positional argument is one without a parameter name — as opposed to a keyword argument like sep='\t' or reverse=True, both of which you have already used.

62. Explain it to someone else

Explain it

The same code, moved and renamed.

Discussion prompt

A classmate asks what actually changed when print_time became a method, since the body is identical. Give them the three differences.

Hint: Where, how it is called, and what it says.

Answer:

Where it is defined: inside the class rather than beside it, which is what makes the relationship between the type and the operation explicit.

How it is invoked: start.print_time() rather than print_time(start), with the object moving from inside the parentheses to before the dot — and being assigned to the first parameter either way.

And what the program says about itself. The book is careful here: these features are not strictly necessary and most provide alternative syntax for things we have already done, but the alternative more accurately conveys the structure of the program.

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?

  • What makes a program object-oriented, and what the transformation buys
  • The two invocation syntaxes, and where the subject goes
  • The argument count, and positional against keyword arguments
  • self and other, and which operations should not be methods

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

Why: The first is worth reading carefully because the book is unusually honest: the features are not strictly necessary, and the benefit is in expression rather than computation. The two syntaxes are one idea — the subject fills the first parameter — and seeing the function form makes the mechanism visible. The argument count is where everyone trips once, and the fix is a single sentence. And the question of what should not be a method is the one that stops the transformation being applied blindly, which the book's int_to_time warning is there to prevent.

64. Synthesis: draw the map of this lesson

Connect it up

One page, from memory.

Draw it

Write one method call in both syntaxes and draw an arrow from the subject to the parameter it fills in each. Beside it, write the argument-count error and annotate where the third argument came from. Then write is_after as a method with its two conventional parameter names, and read the call aloud as a sentence. Finally state the one-question test for whether an operation should be a method, and name the book's example of an operation that fails it.

65. What you can do now

Recap

Four pages, and the code finally says what it means.

If you remember one thingIt is this
From the characteristicsThe features are not strictly necessary; they convey structure.
From the transformationOne level of indentation and a renamed parameter. Nothing else.
From the two syntaxesThe subject fills the first parameter, wherever it is written.
From the error messageAdd one to the arguments you can see.
From int_to_timeA method needs an object to be invoked on. Creation has none.

The next lesson introduces the two methods Python calls for you: __init__, which assigns the attributes at the moment an object is created and finally removes the assign-from-outside style, and __str__, which replaces the memory address with something worth reading.

Think Python, 2nd edition — Allen B. Downey §17.1-17.4, pp. 161-164 — 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.1-17.4, pp. 161-164
  2. Python documentation — Classes
  3. Python documentation — Built-in Functions

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

Book on Wyzant · Text (657) 465-8108