3a Calling Functions, Importing Modules, and Composition

This lesson gives the vocabulary of a function call, meets the int, float and str conversion functions and the way int chops rather than rounds, introduces modules and dot notation through the math module, and shows how expressions compose.

Subject: Python · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Lesson 3a Calling Functions, Importing Modules, and Composition

Title

Python · Chapter 3 — Functions

§3.1-3.3, pp. 17-19

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 §3.1-3.3, pp. 17-19 — the pages these objectives are drawn from

3. Before we start: you have already called a function

Warm-up

Two lessons ago you used something that fits the definition exactly. Find it.

Discussion prompt

You have used at least two functions already without the word being introduced. Name them, and for each one say what you put into it and what came back out.

Hint: One of them told you about types; the other put things on the screen.

Answer:

type is one: you put a value in and a type comes back. print is the other: you put a value in and characters appear on the screen.

They differ in one important way, which this lesson names. type hands something BACK to you — you could assign it to a variable. print does not; its effect is the display.

That distinction becomes a whole section later in this chapter. For now, notice that you already know how to call a function, and this lesson is mostly giving names to what you were doing.

4. The one idea behind this lesson: a function is a named computation you can invoke

Concept

In the context of programming, a function is a named sequence of statements that performs a computation. When you define a function you specify the name and the statements; later you can call the function by name.

function — A named sequence of statements that performs a computation.

It is common to say that a function takes an argument and returns a result. The result is also called the return value. Those three words are used precisely throughout the book and it is worth adopting them now.

Figure (svg): A diagram showing an argument going into a named function box and a return value coming out

Takes an argument, returns a result. Everything else is detail.

Think Python, 2nd edition — Allen B. Downey §3.1-3.3, pp. 17-17

5. The anatomy of a function call

Section

Section 1

6. Name, parentheses, argument, result

Concept

You have already seen a function call. Taking it apart gives four pieces, and every call you meet for the rest of the book has the same four.

>>> type(42)
<class 'int'>
PieceWhat it isIts job
typethe name of the functionsays which computation
( )the parenthesessay that it is being CALLED
42the expression inside themthe argument
<class 'int'>what comes backthe return value

The name of the function is type. The expression in parentheses is called the argument of the function. The result, for this function, is the type of the argument.

Think Python, 2nd edition — Allen B. Downey §3.1-3.3, pp. 17-17

7. Picture it: the parentheses are the verb

Picture it

Without them you have named the function. With them you have run it.

Figure (svg): Two columns contrasting mentioning a function's name with calling it, showing what each produces

This is the same distinction as print versus print('hi') from lesson 1a.

Later in this chapter you will see what a bare function name displays, and it is genuinely informative rather than an error.

8. Worked example: naming the parts of three calls

Worked example

For each, identify the function, the argument and the return value.

>>> type(42)
<class 'int'>
>>> int('32')
32
>>> str(3.14159)
'3.14159'
FunctionArgumentReturn value
type42the type int
intthe string '32'the number 32
strthe float 3.14159the string '3.14159'

Read the name before the parenthesis.

Why: That is the function. It says which computation is being asked for.

Read what is inside the parentheses.

Why: That is the argument — the value the computation is being applied to.

Read what appears afterwards.

Why: That is the return value, and at the prompt the interpreter displays it because a call is an expression.

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

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

Three functions, three arguments, three return values. Note that each return value has a type of its own, and it need not match the argument's type — int('32') takes a str and returns an int.

Verify: Ask type() about each return value.

Why: type(int('32')) reports int, and type(str(3.14159)) reports str. Composing the two calls confirms the types of the results rather than taking the display on trust — and it is also your first composition, which is where this lesson ends up.

9. Match each part of a call to its name

Matching

Four pieces, four words. The words are used precisely for the rest of the book.

Match the pairs

  • a. the word before the parentheses
  • b. the expression inside the parentheses
  • c. what the call produces
  • d. the parentheses themselves
  • r1. the function name
  • r2. the argument
  • r3. the return value, or result
  • r4. the sign that the function is being called rather than mentioned

Why: These four words do a lot of work later. In particular, argument and parameter are NOT synonyms — the argument is what the caller supplies and the parameter is the name the function knows it by — and keeping them apart from the start avoids a confusion that lesson 3b would otherwise have to untangle.

10. Worked example: a call is an expression

Worked example

This follows from the previous lesson's definitions. Work out what it licenses.

>>> n = int('32')
>>> n + 10
42
LineWhy it is legalResult
int('32')a call, which is an expression with a value32
n = int('32')the value is assignedn -> 32
n + 10an ordinary expression using n42

Recall the definition of an expression.

Why: A combination of values, variables and operators — and a function call has a value, which is what qualifies it.

Put the call where a value would go.

Why: The right-hand side of an assignment takes an expression, so a call is allowed there.

Use the result like any other value.

Why: Once assigned, n is an ordinary int. Nothing about its origin in a function call makes it special.

Figure (svg): A ladder showing int of the string thirty-two being evaluated to thirty-two and then assigned to n before ten is added

A call collapses to its return value, and then it is just a value.

42. A function call is an expression, so it can be assigned, and the resulting value behaves like any other value of its type.

Verify: Try the same thing with print instead of int.

Why: n = print('32') assigns something, and printing n shows None rather than 32 — because print does not return a useful value. That difference is the fruitful-versus-void distinction, and it arrives properly in lesson 3c.

11. Trap: confusing the argument with the return value

Trap

The trap

Asked what int('32') returns, a student says a string, reasoning from what went in.

Assume a function hands back the same kind of thing it was given

Why: Many operations do — adding two ints gives an int — so the habit is reasonable.

Conversion functions exist precisely to break that assumption. Their whole job is to hand back a different type from the one they were given.

The fix

Argument and return value are two separate things with two separate types.

Say the sentence out loud: it TAKES an argument and RETURNS a result

Why: The two halves of that sentence are about different values, which is why the book uses two different words.

Check with type() when you are unsure

Why: type(int('32')) settles it in three seconds and needs no reasoning at all.

This matters immediately: in chapter 5 you will read user input, which always arrives as a str, and convert it with int. Being clear that the conversion produces something of a different type is the whole point of doing it.

12. Predict: what type comes back?

Prediction

The argument's type does not determine the return value's type.

Predict first

What is the type of str(32)?

  • int, because 32 is an int
  • str, because the str function returns a string
  • float
  • It has no type until it is assigned

Correct: str — the str function returns a string, whatever type it was given.

Why: The function's job determines the type of what it returns. str converts its argument to a string, so it returns a str, and the fact that 32 came in as an int is exactly what made the conversion worth doing. This is the same reasoning as int('32') returning an int, and it is why these three functions are called conversion functions.

13. Think it through: why do the parentheses matter so much?

Socratic

Some languages let you call a function without them. Python does not.

Discussion prompt

Why might a language want you to write the parentheses even when a function takes no argument at all — as in print_lyrics() — rather than letting the bare name mean call it?

Hint: What else might you want to do with a function besides call it?

Answer:

Because you sometimes want the function itself rather than its result. Displaying it, checking its type, or later passing it to another function all need a way to name it without running it.

If the bare name meant call it, there would be no way to express the function itself, and a whole style of programming would become impossible.

This is why type(print) and print(print) do something sensible rather than erroring, and it is why the empty parentheses in print_lyrics() are required rather than optional. The parentheses are not punctuation; they are the operator that means run this.

14. Explain it yourself: takes and returns

Explain it to yourself

Two words, used precisely. Make sure you can use them about an unfamiliar function.

Discussion prompt

Somebody tells you about a function called len that takes a string and returns an integer. Without having seen it used, describe what you could write with it and what you could not.

Hint: The two words tell you where it can go in an expression and what you may do with the result.

Answer:

You could write len('hello'), and assign the result: n = len('hello'). You could use it in arithmetic, because it returns an integer: len('hello') + 1.

You could not sensibly write len(42) — it takes a string — and you could not concatenate its result to a string without converting, because the result is an int.

Being able to predict all of that from one sentence is what the vocabulary buys you. This is how documentation works: it tells you what a function takes and what it returns, and you work out the rest.

15. Conversion functions, and the one that does not round

Section

Section 2

16. int, float and str

Concept

Python provides functions that convert values from one type to another. The int function takes any value and converts it to an integer, if it can, or complains otherwise.

>>> int('32')
32
>>> int('Hello')
ValueError: invalid literal for int(): Hello
>>> float(32)
32.0
>>> str(32)
'32'
CallWhy it works or does notResult
int('32')the characters really do name an integer32
int('Hello')they do notValueError
float(32)any int can be a float32.0
str(32)anything can be written as text'32'

Notice the asymmetry: str always succeeds, because anything can be written down; float succeeds for ints and for strings that look like numbers; int is the fussiest, because most things are not integers.

Think Python, 2nd edition — Allen B. Downey §3.1-3.3, pp. 17-18

17. Picture it: which conversions can fail

Picture it

Three functions, and only two of them can refuse.

Figure (svg): Three boxes showing the str, float and int conversions with a note on which can fail

The fussier the target type, the more ways the conversion can go wrong.

The int column has two entries because it can fail in one way and surprise you in another. The surprise is next.

18. Worked example: int() chops, it does not round

Worked example

Predict both answers before advancing. One of them catches nearly everybody.

>>> int(3.99999)
3
>>> int(-2.3)
-2
ArgumentWhat int does to itResult
3.99999the fraction part is discarded3, not 4
-2.3the fraction part is discarded-2, not -3 and not -2.3
the rulechop toward zeronever rounds

Try the rounding assumption on the first one.

Why: Rounding 3.99999 would give 4. Python gives 3, so it is not rounding.

State what it actually does.

Why: int can convert floating-point values to integers, but it does not round off; it chops off the fraction part.

Check the rule on a negative number.

Why: Chopping -2.3 discards the .3 and leaves -2. Note this is toward zero rather than downward — rounding down would give -3.

Figure (svg): A number line style diagram showing 3.99999 and minus 2.3 both being chopped toward zero rather than to the nearest whole number

Both arrows point toward zero. Neither points toward the nearer whole number.

3 and -2. int chops off the fraction part rather than rounding, and it chops toward zero, so a negative number loses magnitude rather than gaining it.

Verify: Test the rule on a number where rounding and chopping would agree.

Why: int(3.2) gives 3, which both rules predict, so it tells you nothing. Only a value above the halfway point distinguishes them — which is why 3.99999 is the book's example rather than 3.2, and why choosing a discriminating test case is a skill in itself.

19. Predict: int(-2.9)

Prediction

You know it chops. Decide which way.

Predict first

What does int(-2.9) return?

  • -3
  • -2
  • -2.9
  • An error

Correct: -2 — the fraction part is chopped off, which for a negative number means moving toward zero.

Why: Chopping discards everything after the decimal point, leaving -2. This is different from rounding, which would give -3, and different from rounding down, which would also give -3. The rule to remember is toward zero, and negative numbers are the only place where chopping and rounding down disagree — which is exactly why this is worth testing on a negative.

20. Worked example: when a conversion refuses

Worked example

int('Hello') fails. Work out exactly why, and what that tells you about int('32').

>>> int('32')
32
>>> int('Hello')
ValueError: invalid literal for int(): Hello
>>> int('3.9')
ValueError: invalid literal for int(): 3.9
ArgumentWhat int makes of the charactersResult
'32'the characters spell an integer32
'Hello'they spell nothing numericValueError
'3.9'they spell a FLOAT, not an integerValueError

Understand what int does with a string.

Why: It reads the characters and asks whether they spell an integer. If they do, it produces that integer; otherwise it complains.

Read the error message.

Why: Invalid literal means the characters were not a valid way of writing an integer. The message names the offending text so you can see which value was the problem.

Notice the third case, which surprises people.

Why: '3.9' fails even though 3.9 is a perfectly good number, because it is not a way of writing an INTEGER. Converting it takes two steps: int(float('3.9')), which gives 3.

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

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

int accepts a string only when its characters spell an integer exactly. 'Hello' fails obviously and '3.9' fails subtly, because it spells a float. Both raise ValueError.

Verify: Check that the two-step conversion works and says what it does.

Why: int(float('3.9')) gives 3 — first read the text as a float, then chop. Being forced to write both steps is arguably a feature: it makes you say out loud that you are discarding the fraction, which is exactly the decision that int() would otherwise make silently.

21. Error analysis: four conversion attempts

Error analysis

Two work and two fail. Mark each, and name the error the failures produce.

Annotate

  • Line 1 works and gives the integer 42. The characters spell an integer exactly, which is what int requires of a string.
  • Line 2 fails with a ValueError. The characters spell a float, and int will not do two conversions at once — you would write int(float('42.0')).
  • Line 3 works and gives 42.0. float is more permissive than int about text, because anything that spells an integer also spells a float.
  • Line 4 works and gives 42, chopping the .7. This is the case where int does not refuse but does something you might not have wanted — which is more dangerous than a refusal.
  • The pattern: converting a STRING is all-or-nothing and raises ValueError when the text does not fit. Converting a FLOAT always succeeds and silently discards information.

That last distinction is worth carrying forward. A conversion that refuses tells you something; a conversion that silently loses part of the value does not.

22. Sort: does this conversion succeed?

Sorting

Two questions decide each one: what type is going in, and do the characters fit?

Sort into buckets

Sort each call.

succeeds
int('7'); int(7.9); float('7.5'); str(7.5)
raises ValueError
int('seven'); int('7.5')
ok
Either the characters spell exactly the target type, or the argument is a number being converted to another number — and converting a float to an int always succeeds by chopping. str never fails at all.
err
Both are strings whose characters do not spell an integer. 'seven' is obviously not one; '7.5' spells a float, and int will not perform two conversions in one step.

23. Two truths and a lie: conversions

Two truths and a lie

Two are true. Keep the lie.

Eliminate the wrong options

Rule out the two true statements.

  • A. int(3.9) gives 3
  • B. str never fails, whatever it is given
  • C. int('3.9') gives 3, because int chops

Survives elimination: C

Why: C is the lie, and it is a plausible one because it combines two true facts wrongly. int does chop floats, and '3.9' does look like 3.9 — but the chopping rule applies to FLOATS, not to strings. Given a string, int asks whether the characters spell an integer, and '3.9' does not, so it raises ValueError. The fix is int(float('3.9')), which does both jobs in the order they actually happen.

24. Missing information: which conversion do you want?

Missing information

There is often more than one defensible answer, and the code has to pick one.

Discussion prompt

A program reads the text '4.7' from a user and needs a whole number of items. Name at least two different whole numbers that could defensibly result, and say what extra information decides between them.

Hint: Chopping is not the only sensible thing to do with 4.7.

Answer:

Four, by chopping — int(float('4.7')). Five, by rounding — round(float('4.7')). And in some contexts five by always rounding up, because you cannot buy 4.7 boxes.

What decides is what the number MEANS. A count of completed items chops; a measurement rounds; a number of containers needed rounds up.

The reason this matters is that int() makes the choice for you, silently, and always chooses chopping. Knowing that it has made a choice — and that it might be the wrong one — is the difference between using it and relying on it.

25. Modules, import, and dot notation

Section

Section 3

26. Getting at functions that are not built in

Concept

Python has a math module that provides most of the familiar mathematical functions. A module is a file that contains a collection of related functions, and before you can use the functions in a module you have to import it.

module — A file containing a collection of related functions and variables.

>>> import math
>>> math
<module 'math' (built-in)>
>>> math.sqrt(2)
1.4142135623730951
LineWhat happensNote
import mathcreates a module object named maththe name math now exists
mathdisplaying it gives information about itit is an ordinary value
math.sqrtdot notation: module name, dot, function namereaches inside the module

The import statement creates a module object named math. The module object contains the functions and variables defined in the module, and to access one you specify the name of the module and the name of the function, separated by a dot. This format is called dot notation.

Think Python, 2nd edition — Allen B. Downey §3.1-3.3, pp. 18-18

27. Picture it: a module is a box of names

Picture it

Importing puts the box within reach. The dot reaches into it.

Figure (svg): A state diagram showing the name math pointing at a module object which contains sqrt, sin, log10 and pi

math is an ordinary name referring to an ordinary object. The dot is how you look inside it.

Note the third row: math.pi is a variable, not a function, so it takes no parentheses. Mixing that up is the most common dot-notation mistake.

28. Worked example: degrees to radians, and checking the answer

Worked example

The book's own example, and it ends with a check worth copying.

>>> import math
>>> degrees = 45
>>> radians = degrees / 180.0 * math.pi
>>> math.sin(radians)
0.7071067811865475
LineWhat happensValue
import mathmake the module availablemath exists
degrees / 180.0 * math.pileft to right: divide, then multiply0.7853981633974483
math.sin(radians)the sine of that angle0.7071067811865475

Import before using.

Why: Without the import statement, the name math does not exist and the next line would be a NameError.

Convert the units.

Why: The variable name radians is a hint that sin and the other trigonometric functions take arguments in radians. To convert from degrees, divide by 180 and multiply by pi.

Note the precedence.

Why: Division and multiplication are on the same level, so this runs left to right — divide by 180 first, then multiply by pi. That is the intended grouping here, which is worth confirming rather than assuming.

Figure (svg): A three-stage diagram showing degrees converted to radians and then passed to the sine function

The conversion is a separate step because the function will not do it for you.

About 0.7071. The angle was converted to radians first, because that is the unit the trigonometric functions expect.

Verify: Compare with a value you can compute another way.

Why: math.sqrt(2) / 2 also gives 0.7071067811865476, and the sine of 45 degrees really is the square root of two over two. Two independent routes to the same number is exactly the check the book performs, and it is the only way to catch a semantic error in a formula.

29. Predict: what does math.pi() do?

Prediction

The parentheses are the operator that means call this. Apply that to a variable.

Predict first

What happens if you write math.pi() with parentheses?

  • It returns pi, since the parentheses are optional
  • A TypeError, because a float is not callable
  • A NameError, because pi is not a function
  • It returns an approximation of pi to more digits

Correct: A TypeError, because math.pi is a float and a float is not something you can call.

Why: The parentheses ask Python to call whatever is to their left. math.pi is a float, and calling a float is not a defined operation, so Python reports that a float object is not callable. The message is worth recognising: object is not callable nearly always means you put parentheses after something that was not a function.

30. Worked example: function or variable?

Worked example

Two names from the same module, used differently. Work out which is which.

>>> math.pi
3.141592653589793
>>> math.sqrt(2)
1.4142135623730951
>>> math.sqrt
<built-in function sqrt>
ExpressionWhat kind of name it isResult
math.pia variable: no parenthesesits value
math.sqrt(2)a function, called with an argumentthe square root
math.sqrtthe same function, merely namedthe function object itself

Read math.pi.

Why: The expression math.pi gets the variable pi from the math module. Its value is a floating-point approximation of pi, accurate to about 15 digits. No parentheses, because there is nothing to call.

Read math.sqrt(2).

Why: A function name followed by parentheses containing an argument. This calls it.

Read math.sqrt with no parentheses.

Why: This names the function without calling it, and displaying it shows what it is. Not an error — just a different thing.

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

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

math.pi is a variable and takes no parentheses. math.sqrt is a function, and calling it requires parentheses and an argument. Writing math.pi() would be an error, and writing math.sqrt on its own is legal but computes nothing.

Verify: Ask type() about both.

Why: type(math.pi) reports float and type(math.sqrt) reports builtin_function_or_method. The types make the distinction concrete: one is a number, and one is a thing you can call.

31. Trap: using a module before importing it

Trap

The trap

A student writes math.sqrt(2) at the top of a script and gets a NameError saying that math is not defined.

Assume that anything built into Python is always available

Why: print and int are, so it is a reasonable generalisation.

Modules are not. The name math does not exist until an import statement creates it, which is why the error is about the NAME rather than about sqrt.

The fix

Import creates a name. Until it runs, that name does not exist.

Put the import at the top of the file

Why: It is a statement like any other and runs in order, so it must run before any line that uses the module.

Read the error for which name is missing

Why: name math is not defined points at the module, not at the function — which tells you the import is missing rather than the function name being wrong.

This is the same lesson as lesson 2a's: an assignment creates a name, and using a name before anything created it is a NameError. Import is just another way of creating one.

32. Discriminate: needs parentheses or not?

Discrimination

Functions are called; variables are used. Sort by which is which.

Sort into buckets

For each name from the math module, does using it require parentheses?

a function — needs parentheses and an argument
math.sqrt; math.sin; math.log10
a variable — used on its own
math.pi; math.e; math.tau
fn
Each of these names a computation that has to be given something to work on. Without parentheses you have named the function without running it, which is legal but computes nothing.
var
Each of these names a fixed number the module provides. There is nothing to compute and nothing to supply, so parentheses would ask Python to call a float, which is a TypeError.

33. Decode the notation: what does the dot mean?

Notation

You will see this notation constantly from here to the end of the book.

Annotate

  • Line 1 creates a name, math, referring to a module object. Nothing is computed and nothing is displayed.
  • On line 2, the part before the dot names the thing being looked inside — here, the module.
  • The dot itself means the thing called, inside that. It is an operator, like plus, and it produces a value.
  • The part after the dot, sqrt, is a name that exists inside the module. It does not exist on its own: typing sqrt(2) without the math prefix is a NameError.
  • The parentheses and the 2 then call whatever the dot produced, with 2 as the argument.
  • So the line reads: look inside math, take the thing called sqrt, call it with 2. Three operations, left to right.

Chapter 8 uses the same notation for string methods and chapter 15 for objects you define yourself. The meaning never changes: look inside the thing on the left for the name on the right.

34. Why are modules separate from the language?

Real world

Python could have made sqrt available without an import. It deliberately does not.

Discussion prompt

Give one reason a language might keep mathematical functions in a module rather than making them available everywhere by default, and one cost of that decision.

Hint: Think about names, and about how many functions exist in total.

Answer:

The benefit is names. There are thousands of functions across the standard library, and if all of them were available everywhere, they would collide with your own variable names constantly — you could not safely call a variable log, or pi, or sin.

It also means you can read a file's imports and know roughly what it does, which is real information you would not otherwise have.

The cost is exactly the trap above: a line that would work needs an extra statement to enable it, and forgetting that statement produces an error about a name rather than about what you actually forgot. It is a good trade, but it is a trade.

35. Composition: an expression can go almost anywhere

Section

Section 4

36. The feature that lets programs be built out of parts

Concept

So far the elements of a program — variables, expressions and statements — have been looked at in isolation. One of the most useful features of programming languages is their ability to take small building blocks and compose them.

composition — Using an expression as part of a larger expression, so that small pieces build up into large ones.

x = math.sin(degrees / 360.0 * 2 * math.pi)
x = math.exp(math.log(x + 1))
The composed partWhat kind of thing it isVerdict
degrees / 360.0 * 2 * math.pian arithmetic expression as an argumentlegal
math.log(x + 1)a call as the argument to another calllegal
the rulealmost anywhere a value can go, an expression can goone exception

The argument of a function can be any kind of expression, including arithmetic operators and even other function calls. This is the same rule you have been using since lesson 2a — anywhere a value is allowed, an expression is allowed — now applied to arguments.

Think Python, 2nd edition — Allen B. Downey §3.1-3.3, pp. 19-19

37. Picture it: calls inside calls

Picture it

The inner call is evaluated first, and its return value becomes the outer call's argument.

Figure (svg): A nested call diagram showing math.exp being given the result of math.log which was given x plus one

This is the same inside-out evaluation you met with print in lesson 1a. It has not changed; there are simply more layers now.

38. Worked example: evaluating a composed expression from the inside out

Worked example

Four layers. Peel them in the order Python does.

>>> degrees = 45
>>> math.sin(degrees / 360.0 * 2 * math.pi)
0.7071067811865475
StepWhat is evaluatedValue so far
degreeslook up the name45
45 / 360.0leftmost operator on its level0.125
0.125 * 2 * math.pileft to right0.7853981633974483
math.sin(0.7853...)now the call runs0.7071067811865475

Find the innermost thing that can be evaluated.

Why: The name degrees. Everything else depends on it.

Work outward through the arithmetic.

Why: The argument is an ordinary expression and obeys the ordinary precedence rules, so it is fully evaluated before the function sees anything.

Call the function last.

Why: By the time math.sin runs, its argument is a single number. The function never sees the expression that produced it.

Figure (svg): The state of the program after each line of Worked example evaluating a composed expression from the inside out, drawn as a ladder with one rung per traced line

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

About 0.7071. The whole argument expression is evaluated first, producing one number, and only then is math.sin called with it.

Verify: Assign the argument to a variable first and check the result is identical.

Why: radians = degrees / 360.0 * 2 * math.pi followed by math.sin(radians) gives the same value. That the two forms agree is what composition MEANS — writing the expression inline is a convenience, not a different computation.

39. Predict: how many times is the argument evaluated?

Prediction

Composition raises a question about efficiency that has a definite answer.

Predict first

In print_twice('Spam ' * 4), how many times is 'Spam ' * 4 computed?

  • Twice, once per print
  • Once, before the function is called
  • Four times, once per repetition
  • It depends on the function

Correct: Once, before the function is called. The argument is evaluated first, and the function receives the resulting value.

Why: The book states this directly: the argument is evaluated before the function is called, so 'Spam ' * 4 is only evaluated once. What the function receives is a finished string; it has no access to the expression that produced it and could not re-evaluate it if it wanted to. This matters more than it looks — it is why passing a slow computation as an argument costs you once rather than once per use inside the function.

40. Worked example: the one place an expression may not go

Worked example

Composition has exactly one exception in this chapter. Find out what it protects.

>>> minutes = hours * 60
>>> hours * 60 = minutes
SyntaxError: can't assign to operator
LineWhere the expression isResult
minutes = hours * 60expression on the right: legalassigns
hours * 60 = minutesexpression on the LEFT: not legalSyntaxError
the rulethe left side of an assignment has to be a variable namewith later exceptions

State the exception.

Why: Almost anywhere you can put a value you can put an arbitrary expression, with one exception: the left side of an assignment statement has to be a variable name.

See why the rule has to exist.

Why: An assignment points a name at a value. hours * 60 is not a name — there is nothing there to point. Python would have nowhere to put the result.

Read the error message.

Why: Can't assign to operator names the problem precisely: it found an operator where it needed a name.

Figure (svg): Two columns showing an expression allowed on the right of an assignment and forbidden on the left

One side names a place to put something. The other produces the something.

A SyntaxError. The left side of an assignment must be a variable name, because assignment repoints a name and an expression is not a name.

Verify: Check the direction by swapping the two sides.

Why: minutes = hours * 60 works and hours * 60 = minutes does not, from identical pieces. That asymmetry is the clearest possible demonstration that the equals sign has a direction — which was lesson 2a's point, now with an error message to back it.

41. Trap: writing the assignment backwards

Trap

The trap

Translating sixty minutes make an hour into code, a student writes hours * 60 = minutes, reading the sentence left to right.

Treat the equals sign as symmetric, as it is in mathematics

Why: In an equation you may write either side first, and the meaning is unchanged.

In Python the sides mean different things: one is a place, the other is a value. Swapping them is not a stylistic variation but a syntax error.

The fix

Decide which name you are creating, and put THAT on the left.

Ask: what am I giving a value to?

Why: If the answer is minutes, then minutes goes on the left and everything else on the right.

Read the finished line as becomes

Why: minutes becomes hours times sixty is a sentence with an obvious direction, and it is unambiguous about which side is which.

The book notes that there are exceptions to this rule later — assigning to several names at once, and to items inside lists — but none of them makes the left side an arbitrary expression. The left side always names somewhere to put something.

42. Rank: the order of evaluation in a nested call

Ranking

Put these steps in the order Python performs them for math.exp(math.log(x + 1)).

Put in order

  1. look up the value of x
  2. add 1 to it
  3. call math.log with that result
  4. call math.exp with that result

Why: Innermost first, working outward. The name is looked up, the arithmetic runs, the inner call runs on its result, and the outer call runs on that. Every layer is fully finished before the layer outside it begins, which is why you can read a nested expression by starting in the middle — and why a nested call is exactly as predictable as a sequence of separate lines.

43. Complete it: compose two calls into one line

Faded example

The two-line version is given. Write it as one expression.

Fill in the blanks

# two lines:
# text = str(42)
# n = len(text)
# one line:
n = len(str(42))

Why: The inner call produces a value, and a value is what len needs, so the call can go directly where the variable was. This is composition doing exactly what it promises: anywhere a value is allowed, an expression is allowed, and a call is an expression. Whether to write it as one line or two is a readability judgement rather than a correctness one — both compute the same thing.

44. Push the boundary: how deeply can calls nest?

Edge cases

There is a practical limit and a readability limit, and they are very far apart.

Discussion prompt

Nothing stops you writing int(float(str(int(float('3.7'))))). Is there a rule about how deep to go? Suggest one, and justify it by something other than taste.

Hint: Think about what happens when the value is wrong and you have to find out where.

Answer:

Python imposes no meaningful limit, so the constraint is entirely about people.

A defensible rule: nest as deeply as you can still name each intermediate value out loud. If you cannot say what math.log(x + 1) is a value OF, the expression wants a variable and a name.

The justification is debugging rather than style. When a deeply nested expression produces the wrong answer, you cannot see any of the intermediate values — you have to take it apart before you can print them. Naming the intermediates costs a line each and turns a mystery into a list of checkable values.

45. Putting it together: reading an unfamiliar call

Section

Section 5

46. Everything in this lesson, applied to code you have not seen

Concept

You now have the four skills this lesson set out to build: naming the parts of a call, predicting a conversion, reaching into a module, and reading a composed expression from the inside out. Together they let you make sense of a line you have never seen before.

import math
answer = int(math.sqrt(float('50')))
print(answer)
StepWhat runsValue
float('50')innermost: text to float50.0
math.sqrt(50.0)the square root7.0710678118654755
int(7.07...)chops the fraction7
print(answer)displays it7

Nothing in that line is new. It is four ideas from this lesson stacked, and reading it is a matter of applying them in the right order rather than knowing anything further.

Think Python, 2nd edition — Allen B. Downey §3.1-3.3, pp. 17-19

47. Picture it: peeling a nested expression

Picture it

Start in the middle and work outward, one layer at a time.

Figure (svg): A four-rung ladder showing the nested expression reducing from the innermost string conversion out to the final integer

One layer per rung. The order is inside-out, never left to right.

This is the single most useful reading habit in the chapter, and it works on expressions far longer than this one.

48. Worked example: predicting the type at every layer

Worked example

The value changes at each step, and so does its type. Track both.

>>> float('50')
50.0
>>> math.sqrt(50.0)
7.0710678118654755
>>> int(7.0710678118654755)
7
ExpressionType of the resultValue
'50'strthe starting value
float('50')float50.0
math.sqrt(...)float7.0710678118654755
int(...)int7

Note the starting type.

Why: A str, because it is in quotation marks — the rule from lesson 1b, unchanged.

Track the type through each call.

Why: float returns a float; math.sqrt takes a number and returns a float; int returns an int. Each function's return type is fixed by the function, not by what it was given.

Notice where information is lost.

Why: At the last step. The .07 is discarded and cannot be recovered, which is the price of asking for an int.

Figure (svg): The state of the program after each line of Worked example predicting the type at every layer, drawn as a ladder with one rung per traced line

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

str, then float, then float, then int — and the final step loses information permanently. The value 7 no longer knows it came from 7.07.

Verify: Check whether reordering the calls would give the same answer.

Why: int(float('50')) is 50, and math.sqrt(50) is 7.07..., so converting to int too early would give a different final answer entirely. The order of composition is part of the meaning, exactly as the order of statements was in lesson 1a.

49. Predict: what does this compute?

Prediction

Peel from the inside. Two of the four layers change the type.

int(float('7.9')) + int('2')
StepWhat runsValue
float('7.9')text to float7.9
int(7.9)chop7
int('2')text that spells an integer2
7 + 2ordinary addition9

Predict first

What is the value of this expression?

  • 9
  • 9.9
  • 10
  • A ValueError

Correct: 9 — the float is chopped to 7 and added to 2.

Why: Two conversions happen for different reasons. The first is a two-step conversion, float then int, because int cannot read '7.9' directly — and the int step chops rather than rounding, giving 7 rather than 8. The second is a direct conversion, because '2' spells an integer exactly. Seven plus two is nine, and it is an int because both operands are.

50. Worked example: reading a line with a mistake in it

Worked example

This line looks reasonable and does not work. Diagnose it using the lesson.

answer = math.sqrt('50')
PartWhat it isConsequence
'50'a strquotation marks
math.sqrttakes a numbernot a string
resultTypeErrormust be real number, not str

Identify the argument and its type.

Why: '50' is a str, because of the quotation marks. It looks like a number and is not one — lesson 1b's central point.

Ask what the function takes.

Why: math.sqrt takes a number. A str is not a number, however numeric its characters look.

Read the error and locate the fix.

Why: The message names both what was wanted and what arrived. The fix is to convert first: math.sqrt(float('50')).

Figure (svg): A traceback panel showing a TypeError from passing a string to math.sqrt, with the wanted and supplied types marked

A TypeError. The argument is a string, and math.sqrt requires a number — so the string has to be converted before the call, which is why the working version composes float inside sqrt.

Verify: Check the fixed version and confirm the conversion is what changed.

Why: math.sqrt(float('50')) gives 7.07..., with the same function and the same characters. Only the type of the argument changed, which confirms the diagnosis rather than merely producing a working line.

51. Trap: assuming a function will convert for you

Trap

The trap

A student passes a string to a function that wants a number, reasoning that Python can obviously see it is numeric.

Expect the language to be helpful about types

Why: It is helpful in many ways, and the characters really do spell a number.

Python declines, for the same reason it declined to add '2' and 2 in lesson 1b: guessing would sometimes be wrong, and a wrong guess is silent.

The fix

Convert deliberately, at the point where you know what you mean.

Ask what type the function takes

Why: The documentation says so, and the error message says so after the fact.

Wrap the argument in the right conversion

Why: float for a measurement, int for a count. Composition makes this a single extra pair of parentheses.

This becomes routine in chapter 5, where everything the user types arrives as a str. Converting at the boundary — as soon as the text arrives — is the habit that keeps the rest of a program free of this problem.

52. Error analysis: four lines, three problems

Error analysis

One of these works. Mark the other three and name what each gets wrong.

Annotate

  • Line 1 uses sqrt without the module prefix. The name sqrt does not exist on its own — it lives inside math — so this is a NameError.
  • Line 2 passes a string to a function that takes a number. The characters look numeric and the type is not, so this is a TypeError.
  • Line 3 puts parentheses after a variable. math.pi is a float, and calling a float is not defined, so this is a TypeError with the message that a float object is not callable.
  • Line 4 is correct and gives 4.0. Note the decimal point: math.sqrt always returns a float, even for a perfect square.
  • Three different errors, three different causes: a missing module prefix, a wrong argument type, and calling something that is not a function. Reading which error you got tells you which of the three it was.

Note that all three failures were avoidable by asking the same question in advance: what is this name, and what does it take?

53. Compare: the three conversion functions

Comparison

Fill the blanks. Three functions, three behaviours worth telling apart.

Comparison matrix

FunctionGiven a suitable stringGiven a float
intmust spell an integer exactly, else ValueErrorchops the fraction toward zero
floatreads it as a number, else ValueErrorreturns it unchanged
strreturns it unchangedwrites it out as text — never fails

The pattern down the table: the more restrictive the target type, the more ways the conversion can fail or lose information.

54. Explain it: how would you read this line to someone?

Explain it

Reading aloud is a real skill and it is how you check your own understanding.

Discussion prompt

Read the line answer = int(math.sqrt(float(text))) out loud to somebody, in a way that makes the order of operations audible. Then say which name they would need to have met before it made sense.

Hint: Start in the middle, not on the left.

Answer:

A good reading: take whatever text refers to, read it as a float, take the square root of that, chop it to a whole number, and call the result answer. The order of the clauses is the order of evaluation.

A poor reading goes left to right — answer equals int of math dot sqrt of... — which mirrors the characters and hides the computation.

They would need to have met text: everything else in the line is a function whose job is stated by its name, but text is a name from somewhere else in the program, and without knowing what is in it the line cannot be understood. That is worth noticing, because it means the WEAKEST part of a composed expression is usually the plain variable in the middle.

55. Compare: the four things you can put in a call

Comparison

Fill the blanks. Anything with a value can be an argument, which is what makes composition possible.

Comparison matrix

What you write as the argumentExampleWhen it is evaluated
a literal valuemath.sqrt(2)it already is a value
a variablemath.sqrt(n)looked up before the call
an arithmetic expressionmath.sqrt(a + b)computed before the call
another function callmath.sqrt(float(text))the inner call runs first

The right-hand column says the same thing four times, which is the point: the argument is always a finished value by the time the function starts.

56. The procedure: reading a line you have never seen

Pattern

This works on any expression, of any depth, and it is the habit this lesson exists to build.

  1. Find the innermost parentheses — the pair with nothing but a simple value or name inside.
  2. Evaluate that, and note the TYPE of the result as well as its value.
  3. Replace it with its value and repeat, working outward one layer at a time.
  4. At each function, check that the type going in is the type that function takes.
  5. When only one value is left, that is the value of the whole expression.
  6. If the line is an assignment, only now does the name on the left get repointed.

Step 4 is the one that catches real bugs. Most errors in composed expressions are type mismatches, and the type is easier to check layer by layer than at the end.

Python documentation — Built-in Functions Built-in Functions

57. Check yourself 1 of 3: int does not round

Check

One rule, applied to a value chosen so that rounding and chopping disagree.

>>> int(9.99)
9
StepWhat happensValue
9.99the argument, a floatclose to 10
int(9.99)chops off the fraction part9
rounding would give10but int does not round

Check your understanding

What does int(9.99) return?

  • A. 10
  • B. 9 (correct)
  • C. 9.99
  • D. A ValueError

Answer: B

Why: int converts floating-point values to integers but does not round off; it chops off the fraction part. So 9.99 becomes 9, losing almost a whole unit. This is worth knowing precisely because the loss can be nearly one, which is much larger than people expect from a conversion.

Why A tempts people
This is what rounding would give, and the assumption that int rounds is the single most common misconception about it. The book states explicitly that it does not.
Why C tempts people
The whole purpose of int is to produce an int, so the fraction cannot survive. Returning the argument unchanged would make the call pointless.
Why D tempts people
ValueError arises when int is given a STRING whose characters do not spell an integer. Given a float, int always succeeds — which is exactly what makes the chopping dangerous.

58. Check yourself 2 of 3: dot notation

Check

One of these misuses the dot. Find it.

Check your understanding

After import math, which of these is an error?

  • A. math.sqrt(16)
  • B. math.pi
  • C. sqrt(16) (correct)
  • D. math.sin(0)

Answer: C

Why: The name sqrt does not exist on its own. Importing a module creates one name — math — and everything the module provides is reached through it with a dot. Writing sqrt(16) is a NameError, and the message will say that sqrt is not defined, which points at the missing prefix rather than at the function.

Why A tempts people
Correct dot notation: the module name, a dot, the function name, and an argument. It gives 4.0.
Why B tempts people
Also correct. math.pi is a variable inside the module, so it is used without parentheses.
Why D tempts people
Correct as well. math.sin takes a number in radians, and 0 is a perfectly good one — it gives 0.0.

59. Check yourself 3 of 3: composition

Check

One of these breaks the single rule about where an expression may go.

Check your understanding

Which of these is a syntax error?

  • A. x = math.sqrt(a + b)
  • B. x = int(float('3.5'))
  • C. hours * 60 = minutes (correct)
  • D. print(int('7') + 1)

Answer: C

Why: Almost anywhere you can put a value you can put an arbitrary expression, with one exception: the left side of an assignment statement has to be a variable name. hours * 60 is an expression, not a name, so there is nowhere to put the result — and Python reports that it cannot assign to an operator.

Why A tempts people
An arithmetic expression as an argument, which is exactly the composition the section describes. It is evaluated before the call.
Why B tempts people
A call inside a call, which is also allowed. The inner one runs first and its result becomes the outer one's argument.
Why D tempts people
A call and an operator inside another call's argument. Composition nests to any depth, and this is only two layers.

60. Where this shows up outside this course

Real world

Composition is not a Python feature. It is what makes any notation powerful.

Discussion prompt

Find a notation outside programming where the result of one operation becomes the input to another, written inline rather than in steps — mathematics, cooking, music, spreadsheets. What does the notation gain, and what does it cost a reader?

Hint: A spreadsheet formula with a function inside a function is the everyday case.

Answer:

Spreadsheets are the clearest: ROUND(AVERAGE(A1:A10), 2) is exactly this, and it is written inline for the same reason — the intermediate value has no natural name and no cell to live in.

What it gains is that the whole computation is in one place and cannot get out of step. What it costs is visibility: when the answer is wrong, none of the intermediate values is on screen.

This is the same tradeoff as the boundary-push probe: compose for compactness, name the intermediates when you need to see them. Neither is universally right, and knowing that it is a choice is what matters.

61. Confidence wager: commit before you check

Commit first

Answer, then rate your confidence. This one combines two rules from two lessons.

Predict first

What does int('3.9') return?

  • 3, because int chops the fraction
  • 4, because int rounds
  • A ValueError, because '3.9' does not spell an integer
  • 3.9, unchanged

Correct: A ValueError, because '3.9' is a string whose characters spell a float rather than an integer.

Why: Two facts have to be combined and the order matters. int does chop floats — but that rule is about floats, and this argument is a string. Given a string, int asks whether the characters spell an integer exactly, and '3.9' does not, so it refuses. The chopping never gets a chance to happen. If you answered 3, you applied the right rule to the wrong type, which is the most instructive way to get this wrong: the fix is int(float('3.9')), which does the two conversions in the order they actually have to occur.

62. Explain it to someone else

Explain it

The vocabulary is the deliverable here. Using it precisely is the skill.

Discussion prompt

A friend asks what the difference is between math.sqrt and math.pi, since both start with math and a dot. Explain in under a minute, and give them one thing they can type to see the difference for themselves.

Hint: The check is the valuable half of the answer.

Answer:

Say: sqrt is a function, so it takes an argument and returns a result, and you call it with parentheses. pi is a variable, so it just IS a number, and parentheses would be asking to call a float.

Give them the check: type(math.sqrt) and type(math.pi). One reports a function and the other reports a float, which settles it without any argument from you.

If they want the failure too, math.pi() gives float object is not callable — an error message worth meeting once deliberately, because it is the standard signal that parentheses went after something that was not a function.

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?

  • The vocabulary: argument, returns, return value
  • The conversion functions, especially int chopping rather than rounding
  • Modules, import, and dot notation
  • Reading a composed expression from the inside out

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

Why: These settle at different speeds and matter for different reasons. The vocabulary is quick and pays off immediately, because every piece of documentation you read from now on is written in it. The conversion behaviour is two facts that stop surprising you once you have been caught by them. Dot notation becomes invisible within a week and then stays useful for the rest of the book, including chapters 8, 15 and 18. And inside-out reading is the one that keeps growing — the expressions get longer and the habit is what keeps them readable.

64. Synthesis: draw the map of this lesson

Connect it up

One page, no code, from memory.

Draw it

Draw a single function call as a box with an arrow going in and an arrow coming out, and label all four parts with the words from this lesson. Then, around it, show the three ways an argument can be supplied: a literal, a variable, and another call. Finally, mark the ONE place in a program where an expression is not allowed, and write beside it the error message Python gives there.

65. What you can do now

Recap

Three pages, and you can read and use any function in the standard library.

If you remember one thingIt is this
From the vocabularyA function takes an argument and returns a result. Two different values, two different types.
From conversionsint chops, and it chops toward zero. It never rounds.
From modulesimport creates one name. The dot reaches inside it.
From compositionInnermost first. The argument is a finished value before the call begins.

The next lesson stops using other people's functions and writes one: the def statement, the header and the body, and the difference between defining a function and calling it.

Think Python, 2nd edition — Allen B. Downey §3.1-3.3, pp. 17-19 — 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), §3.1-3.3, pp. 17-19
  2. Python documentation — Built-in Functions
  3. Python documentation — Modules

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

Book on Wyzant · Text (657) 465-8108