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
Title
Python · Chapter 3 — Functions
§3.1-3.3, pp. 17-19
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
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.
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
Think Python, 2nd edition — Allen B. Downey §3.1-3.3, pp. 17-17
Section
Section 1
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'>| Piece | What it is | Its job |
|---|---|---|
| type | the name of the function | says which computation |
| ( ) | the parentheses | say that it is being CALLED |
| 42 | the expression inside them | the argument |
| <class 'int'> | what comes back | the 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
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
Later in this chapter you will see what a bare function name displays, and it is genuinely informative rather than an error.
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'| Function | Argument | Return value |
|---|---|---|
| type | 42 | the type int |
| int | the string '32' | the number 32 |
| str | the float 3.14159 | the 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
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.
Matching
Four pieces, four words. The words are used precisely for the rest of the book.
Match the pairs
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.
Worked example
This follows from the previous lesson's definitions. Work out what it licenses.
>>> n = int('32')
>>> n + 10
42| Line | Why it is legal | Result |
|---|---|---|
| int('32') | a call, which is an expression with a value | 32 |
| n = int('32') | the value is assigned | n -> 32 |
| n + 10 | an ordinary expression using n | 42 |
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
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.
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.
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.
Prediction
The argument's type does not determine the return value's type.
Predict first
What is the type of str(32)?
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.
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.
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.
Section
Section 2
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'| Call | Why it works or does not | Result |
|---|---|---|
| int('32') | the characters really do name an integer | 32 |
| int('Hello') | they do not | ValueError |
| float(32) | any int can be a float | 32.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
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 int column has two entries because it can fail in one way and surprise you in another. The surprise is next.
Worked example
Predict both answers before advancing. One of them catches nearly everybody.
>>> int(3.99999)
3
>>> int(-2.3)
-2| Argument | What int does to it | Result |
|---|---|---|
| 3.99999 | the fraction part is discarded | 3, not 4 |
| -2.3 | the fraction part is discarded | -2, not -3 and not -2.3 |
| the rule | chop toward zero | never 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
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.
Prediction
You know it chops. Decide which way.
Predict first
What does int(-2.9) return?
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.
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| Argument | What int makes of the characters | Result |
|---|---|---|
| '32' | the characters spell an integer | 32 |
| 'Hello' | they spell nothing numeric | ValueError |
| '3.9' | they spell a FLOAT, not an integer | ValueError |
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
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.
Error analysis
Two work and two fail. Mark each, and name the error the failures produce.
Annotate
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.
Sorting
Two questions decide each one: what type is going in, and do the characters fit?
Sort into buckets
Sort each call.
Two truths and a lie
Two are true. Keep the lie.
Eliminate the wrong options
Rule out the two true statements.
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.
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.
Section
Section 3
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| Line | What happens | Note |
|---|---|---|
| import math | creates a module object named math | the name math now exists |
| math | displaying it gives information about it | it is an ordinary value |
| math.sqrt | dot notation: module name, dot, function name | reaches 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
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
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.
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| Line | What happens | Value |
|---|---|---|
| import math | make the module available | math exists |
| degrees / 180.0 * math.pi | left to right: divide, then multiply | 0.7853981633974483 |
| math.sin(radians) | the sine of that angle | 0.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
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.
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?
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.
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>| Expression | What kind of name it is | Result |
|---|---|---|
| math.pi | a variable: no parentheses | its value |
| math.sqrt(2) | a function, called with an argument | the square root |
| math.sqrt | the same function, merely named | the 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
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.
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.
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.
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?
Notation
You will see this notation constantly from here to the end of the book.
Annotate
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.
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.
Section
Section 4
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 part | What kind of thing it is | Verdict |
|---|---|---|
| degrees / 360.0 * 2 * math.pi | an arithmetic expression as an argument | legal |
| math.log(x + 1) | a call as the argument to another call | legal |
| the rule | almost anywhere a value can go, an expression can go | one 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
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.
Worked example
Four layers. Peel them in the order Python does.
>>> degrees = 45
>>> math.sin(degrees / 360.0 * 2 * math.pi)
0.7071067811865475| Step | What is evaluated | Value so far |
|---|---|---|
| degrees | look up the name | 45 |
| 45 / 360.0 | leftmost operator on its level | 0.125 |
| 0.125 * 2 * math.pi | left to right | 0.7853981633974483 |
| math.sin(0.7853...) | now the call runs | 0.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
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.
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?
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.
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| Line | Where the expression is | Result |
|---|---|---|
| minutes = hours * 60 | expression on the right: legal | assigns |
| hours * 60 = minutes | expression on the LEFT: not legal | SyntaxError |
| the rule | the left side of an assignment has to be a variable name | with 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
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.
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.
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.
Ranking
Put these steps in the order Python performs them for math.exp(math.log(x + 1)).
Put in order
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.
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.
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.
Section
Section 5
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)| Step | What runs | Value |
|---|---|---|
| float('50') | innermost: text to float | 50.0 |
| math.sqrt(50.0) | the square root | 7.0710678118654755 |
| int(7.07...) | chops the fraction | 7 |
| print(answer) | displays it | 7 |
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
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
This is the single most useful reading habit in the chapter, and it works on expressions far longer than this one.
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| Expression | Type of the result | Value |
|---|---|---|
| '50' | str | the starting value |
| float('50') | float | 50.0 |
| math.sqrt(...) | float | 7.0710678118654755 |
| int(...) | int | 7 |
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
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.
Prediction
Peel from the inside. Two of the four layers change the type.
int(float('7.9')) + int('2')| Step | What runs | Value |
|---|---|---|
| float('7.9') | text to float | 7.9 |
| int(7.9) | chop | 7 |
| int('2') | text that spells an integer | 2 |
| 7 + 2 | ordinary addition | 9 |
Predict first
What is the value of this expression?
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.
Worked example
This line looks reasonable and does not work. Diagnose it using the lesson.
answer = math.sqrt('50')| Part | What it is | Consequence |
|---|---|---|
| '50' | a str | quotation marks |
| math.sqrt | takes a number | not a string |
| result | TypeError | must 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.
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.
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.
Error analysis
One of these works. Mark the other three and name what each gets wrong.
Annotate
Note that all three failures were avoidable by asking the same question in advance: what is this name, and what does it take?
Comparison
Fill the blanks. Three functions, three behaviours worth telling apart.
Comparison matrix
| Function | Given a suitable string | Given a float |
|---|---|---|
| int | must spell an integer exactly, else ValueError | chops the fraction toward zero |
| float | reads it as a number, else ValueError | returns it unchanged |
| str | returns it unchanged | writes 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.
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.
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 argument | Example | When it is evaluated |
|---|---|---|
| a literal value | math.sqrt(2) | it already is a value |
| a variable | math.sqrt(n) | looked up before the call |
| an arithmetic expression | math.sqrt(a + b) | computed before the call |
| another function call | math.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.
Pattern
This works on any expression, of any depth, and it is the habit this lesson exists to build.
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
Check
One rule, applied to a value chosen so that rounding and chopping disagree.
>>> int(9.99)
9| Step | What happens | Value |
|---|---|---|
| 9.99 | the argument, a float | close to 10 |
| int(9.99) | chops off the fraction part | 9 |
| rounding would give | 10 | but int does not round |
Check your understanding
What does int(9.99) return?
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.
Check
One of these misuses the dot. Find it.
Check your understanding
After import math, which of these is an error?
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.
Check
One of these breaks the single rule about where an expression may go.
Check your understanding
Which of these is a syntax error?
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.
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.
Commit first
Answer, then rate your confidence. This one combines two rules from two lessons.
Predict first
What does int('3.9') return?
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.
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.
Exit ticket
One honest answer. It decides what the next lesson opens with.
Predict first
Which of these is still least solid for you?
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.
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.
Recap
Three pages, and you can read and use any function in the standard library.
| If you remember one thing | It is this |
|---|---|
| From the vocabulary | A function takes an argument and returns a result. Two different values, two different types. |
| From conversions | int chops, and it chops toward zero. It never rounds. |
| From modules | import creates one name. The dot reaches inside it. |
| From composition | Innermost 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
Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.