Chapter 16: Functions

Every year the students of a school in Jaipur print certificates for the annual sports day. This year the computer club offered to do it with a Python program. Aarti wrote the part that printed the school name, the event name and the winner’s name. It took about ten lines. Then she copied those ten lines for the second event. Then for the third. By evening the file had the same ten lines repeated forty times.

The next morning the principal noticed a spelling mistake in the school name. Aarti had to correct it in forty places, and she still missed two of them.

Riyaz suggested something simpler. Write those ten lines once, give them a name, and then use that name every time a certificate is needed. If the school name ever changes, you change it in one place only. The rest of the program does not have to be touched at all. That idea, of naming a block of instructions and using the name instead, is what this chapter is about. See Fig. 16.0-1.

Diagram

Fig. 16.0-1 – On the left, a tall box labelled “Same 10 lines of code, copied 3 times” showing three identical stacked blocks. On the right, a single box labelled “print_certificate” with three arrows pointing into it from three small boxes labelled “Event 1”, “Event 2”, “Event 3”. A short caption strip between the two halves reads “written once, used many times”.

NoteCuriosity Corner

Q1. Why is copying the same lines of code again and again a poor way to write a program? Q2. What is the difference between a function Python already gives you and one you write yourself? Q3. Does the order in which you pass values to a function always matter? Q4. What happens if you call a function but leave out one of the values it expects? Q5. Can a function send back more than one value at a time?

The sections ahead will answer all five of these questions.

16.1 Why We Need Functions

In the school canteen, the recipe for lemon sharbat is written once on a card and kept in a box. When an order comes, the cook picks up the card and follows it. Nobody writes the whole recipe again on every order slip. The recipe is stored in one place, given a name, and used whenever it is needed.

A program can be organised in the same way. Some jobs come up again and again — printing a certificate, checking a battery level, converting marks into a percentage. Instead of typing those instructions every time, you can write them once, give them a name, and then use the name.

The problem with repeating code

Look at what happens when you do not do this. A school robotics lab has three robots, and the teacher wants a short battery report for each one.

battery report for three robots, written out three times Problem: Print the name, battery level and status of three lab robots. A robot with less than 30% battery must be marked for charging.

# Battery check for three lab robots, written out three times
level = 82
print("Robot: Rover")
print("Battery:", level, "%")
if level < 30:
    print("Status: Charge now")
else:
    print("Status: Ready")

level = 24
print("Robot: Lifter")
print("Battery:", level, "%")
if level < 30:
    print("Status: Charge now")
else:
    print("Status: Ready")

level = 55
print("Robot: Sweeper")
print("Battery:", level, "%")
if level < 30:
    print("Status: Charge now")
else:
    print("Status: Ready")

Output:

Robot: Rover
Battery: 82 %
Status: Ready
Robot: Lifter
Battery: 24 %
Status: Charge now
Robot: Sweeper
Battery: 55 %
Status: Ready

The program works, but look at what it costs you. The same six lines appear three times. If the lab adds a fourth robot, you copy them again. If the teacher later decides that the charging limit should be 40 instead of 30, you have to find and change that number in three separate places. Miss one, and the report quietly gives a wrong status for that robot.

That last point is the real danger. A program with repeated code does not break loudly when you edit it carelessly. It keeps running and gives an answer that looks fine but is wrong.

Q. The three blocks are identical, so why can’t I just fix the first one?

A. Python runs every line you write. The other two blocks are separate copies, and they carry on using the old value until you edit them too.

Writing it once instead

A better plan is to write the six lines once, give that block a name such as battery_report, and then use the name three times with a different robot each time. The instructions live in exactly one place. Change them there, and every use of the name gets the change automatically.

A block of instructions written once, given a name, and used whenever needed is called a function.

NoteDefinition

A function is a named block of code that carries out one particular task. It is written once and can be used as many times as needed, from anywhere in the program.

You have already been using functions without calling them by that name. print() is a function. So is input(). Somebody wrote the instructions for putting text on the screen once, gave them the name print, and every Python programmer in the world now uses that one name instead of writing those instructions again.

Q1. Why is copying the same lines of code again and again a poor way to write a program?

A. Because every copy has to be maintained separately. A change or a correction must be made in each copy, and a copy you forget will keep running with the old, wrong instructions. Writing the block once as a function means there is only one place to change.

What functions give you

Three things follow from writing a block once and naming it well.

The program becomes shorter, because a block that took six lines is now used in one. It becomes easier to correct, because a fix is made in a single place. And it becomes easier to read, because a line such as battery_report("Rover", 82) says plainly what is happening, while six lines of print and if statements make the reader work it out.

The idea is older than Python. In the 1940s, programmers noticed they were rewriting the same instruction sequences for every new problem, and began keeping them in a shared collection called a subroutine library. Functions are the same idea in a modern language.

There is one honest limitation. A function only helps when a job is genuinely repeated or genuinely worth naming. Wrapping a single print statement inside a function of its own adds a name to remember without saving you anything. Use a function where it makes the program clearer, not everywhere.

16.2 Defining and Calling a Function

Using a function always happens in two separate steps, and it helps to keep them apart in your mind from the start.

The first step is writing the function down: giving it a name, saying what values it needs, and listing the instructions it should carry out. This is the function definition. Writing a definition does not make anything happen. It is like writing the sharbat recipe on a card — the card by itself does not make a drink.

The second step is using it. You write the function’s name and hand it the values it needs, and Python then runs the instructions inside. This is a function call. The recipe card is picked up and followed.

The general form

Every function definition in Python follows the same shape.

def function_name(parameters):
    """docstring"""
    # function body
    # code to execute
    return expression

The first line begins with the keyword def, followed by the name you are giving the function and a pair of round brackets. It ends with a colon. Everything that belongs to the function is written below it, indented. Sections 16.2.1 to 16.2.3 look at each part of this shape in turn.

A call is much shorter. You write the name and the round brackets, with the values inside:

function_name(values)
The battery report, written once

In Section 16.1 the battery report for three robots was typed out three times. Here is the same job done with a function.

battery report for three robots, using a function Problem: Print the name, battery level and status of three lab robots, writing the report instructions only once. A robot with less than 30% battery must be marked for charging.

# The same battery report, written once as a function
def battery_report(name, level):
    print("Robot:", name)
    print("Battery:", level, "%")
    if level < 30:
        print("Status: Charge now")
    else:
        print("Status: Ready")

battery_report("Rover", 82)
battery_report("Lifter", 24)
battery_report("Sweeper", 55)

Output:

Robot: Rover
Battery: 82 %
Status: Ready
Robot: Lifter
Battery: 24 %
Status: Charge now
Robot: Sweeper
Battery: 55 %
Status: Ready

The output is exactly the same as before, but the report instructions now exist in only one place. Change 30 to 40 on that one if line, and all three robots are judged by the new limit at once. Adding a fourth robot takes one line, not six.

NoteDefinition

A function definition is the block of code that names a function and lists the instructions inside it. A function call is the use of that name in a program, which makes Python run those instructions.

How Python moves through the program

Follow the order in which the lines actually run, shown in Fig. 16.2-1. Python reads the def line and the indented block below it, notes that a function called battery_report now exists, and moves on without running any of it. It then reaches the first call. At that point it jumps up into the function, runs the instructions there with name holding "Rover" and level holding 82, and comes back down to the line after the call. The same jump happens again for the second call, and again for the third.

Diagram

Fig. 16.2-1 – Two columns. Left column shows the program lines in order: the def battery_report(...) block drawn as one shaded box, then three call lines below it. Right column shows numbered arrows: arrow 1 from the top of the program past the shaded box labelled “definition read, not run”; arrow 2 from the first call up into the shaded box labelled “jump in”; arrow 3 back down to the next line labelled “return here”; the same pair of arrows repeated for the second and third calls.

Q. Why does nothing print when I run a file that only has the def block in it?

A. Because a definition only stores the instructions under a name. Nothing runs until you call the function.

defining a function and forgetting to call it What goes wrong: The program runs, shows no error, and prints nothing at all. Everything the programmer wrote is sitting inside the definition, and no line outside it ever uses the name. The fix: After the definition, add at least one call at the left margin — with no indentation — so it is not part of the function body.

Where the definition must go

Python must have read a function’s definition before it reaches a call to that function. In a simple program this means the def block is written above the lines that use it. If you call a function on line 3 and define it on line 10, Python stops with an error saying the name is not defined.

This is a real limit, and it shapes how programs are laid out. Most Python files put all the definitions at the top and the lines that actually do the work at the bottom.

16.2.1 The Structure of a Function Definition

A function definition has four parts, and each one is doing a particular job. Take a small robotics example and look at it piece by piece.

A wheeled robot moves forward by turning its wheels. One full turn of a wheel carries the robot forward by the distance around the wheel’s rim. So if you know how far the robot must travel, and you know that rim distance, you can work out how many turns the wheel needs. That calculation is worth naming.

def wheel_turns(distance_cm, circumference_cm):
    turns = distance_cm / circumference_cm
    return turns

The four parts are labelled in Fig. 16.2.1-1.

Diagram

Fig. 16.2.1-1 – The two lines of the definition printed large, with four labelled callouts pointing to them: an arrow to def labelled “keyword: tells Python a function follows”; an arrow to wheel_turns labelled “function name”; an arrow to the brackets and the words inside labelled “parameters: the values the function needs”; an arrow to the colon labelled “colon ends the header line”; and a brace around the two indented lines below labelled “function body”.

The header line

The first line is called the header. It starts with the keyword def, which is short for “define”. This word tells Python that a function is being written, not ordinary instructions.

After def comes the name. You choose it, and the same rules apply as for variable names: letters, digits and underscores only, no spaces, and it cannot start with a digit. A name in lower case with underscores between words, such as wheel_turns, is the usual style in Python. Choose a name that says what the function does, because that name is what a reader will see everywhere else in the program.

Inside the round brackets you list the values the function needs in order to do its work. Each of these is a parameter — a name that stands for a value which will be supplied later, when the function is called. Here there are two parameters, distance_cm and circumference_cm. Inside the function they behave exactly like ordinary variables.

The header line ends with a colon, just as the header of an if statement or a loop does. The colon says that an indented block is coming next.

Q. What if a function does not need any values from outside?

A. You still write the empty brackets — def show_menu():. The brackets are part of how Python recognises a function, not an optional extra.

The function body

Everything indented below the header is the function body. These are the instructions Python runs whenever the function is called. The indentation is what marks them as belonging to the function; a line brought back to the left margin is outside the function again.

The body can be as long as it needs to be and can contain anything a normal program contains — calculations, if statements, loops, even calls to other functions.

missing colon or missing indentation in the header What goes wrong: Leaving out the colon at the end of the def line gives a syntax error. Writing the next line at the left margin instead of indenting it gives an error saying an indented block was expected. The fix: End every def line with a colon, and indent every line of the body by the same amount — four spaces is the usual choice.

The return statement

The last line of this function is return turns. A return statement sends a value back to the place where the function was called, and ends the function immediately. Nothing after it inside the function will run.

This is what makes a function useful as a piece of a bigger calculation. The call wheel_turns(120, 20) does not just do something quietly — it produces a value that you can store in a variable, print, or use in a further sum.

working out how many wheel turns a robot needs Problem: A robot must travel a given distance in centimetres. Given the distance around its wheel rim, find how many full and part turns the wheel must make.

def wheel_turns(distance_cm, circumference_cm):
    turns = distance_cm / circumference_cm
    return turns

answer = wheel_turns(120, 20)
print("Wheel turns needed:", answer)

Output:

Wheel turns needed: 6.0

checking the wheel turns by hand

Given: distance to travel = 120 cm, distance around the wheel rim = 20 cm

Find: the number of wheel turns needed

Working: one turn carries the robot 20 cm forward, so the number of turns is the total distance divided by the distance per turn.

\(\text{turns} = \dfrac{\text{distance}}{\text{circumference}} = \dfrac{120}{20} = 6\)

Here distance and circumference are both measured in centimetres, so the units cancel and the answer is a plain count.

6 turns

A function without a return statement is perfectly legal. The battery_report function in Section 16.2 had none — it printed its results and sent nothing back. The difference matters, and Section 16.8 looks at it properly.

16.2.2 Calling a Function

A definition sits quietly until something uses it. Calling a function is how you use it, and a call is short: the function’s name, followed by round brackets holding the values you are handing over.

The robotics club is buying parts. Four ultrasonic sensors at ₹250 each, and two motors at ₹180 each. The same small calculation — a count multiplied by a price — is needed for both, so it is worth writing once as a function.

cost of parts for the robotics club Problem: Work out what the club pays for sensors, what it pays for motors, and what the whole bill comes to.

def cost_of_items(count, price_each):
    return count * price_each

sensors = cost_of_items(4, 250)
motors = cost_of_items(2, 180)
print("Sensors cost Rs", sensors)
print("Motors cost Rs", motors)
print("Total bill Rs", sensors + motors)

Output:

Sensors cost Rs 1000
Motors cost Rs 360
Total bill Rs 1360

checking the club’s bill

Given: 4 sensors at ₹250 each, 2 motors at ₹180 each

Find: the total amount the club pays

Working:

cost of sensors = 4 × ₹250 = ₹1000

cost of motors = 2 × ₹180 = ₹360

total = ₹1000 + ₹360 = ₹1360

₹1360

Arguments: the values you hand over

The values written inside the brackets of a call are the arguments. In cost_of_items(4, 250) the arguments are 4 and 250.

It is easy to mix up arguments and parameters, so keep the two words apart by where they appear. A parameter is a name in the definition. An argument is a value in the call. When the call runs, each argument is copied into the matching parameter, and the function body then works with those names.

Table 16.1 – parameter vs argument | Feature | Parameter | Argument | |—|—|—| | Where it appears | In the function definition, inside the brackets after the name | In the function call, inside the brackets | | What it is | A name standing for a value not yet supplied | An actual value being supplied | | Example | count and price_each in def cost_of_items(count, price_each): | 4 and 250 in cost_of_items(4, 250) | | When it gets a value | At the moment the function is called | It is the value |

The matching is done by position. The first argument goes into the first parameter, the second into the second, and so on. So in cost_of_items(4, 250), count becomes 4 and price_each becomes 250.

Q. What happens if I swap the two values and write cost_of_items(250, 4)?

A. Python still matches by position, so count becomes 250 and price_each becomes 4. The answer, ₹1000, happens to be the same here, but the program now means something the club never asked for. With different numbers it would simply be wrong.

Using what comes back

A call to a function that returns a value can be used anywhere a value can be used. In the program above the returned value is stored in a variable, which is the clearest way to do it when you need the value again later.

You can also use a call directly inside a bigger expression or inside another call:

print("Total bill Rs", cost_of_items(4, 250) + cost_of_items(2, 180))

Python works out each call first, then adds the two results, then prints. This is shorter, but the earlier version with named variables is easier to read when the numbers mean different things.

leaving out the brackets in a call What goes wrong: Writing total = cost_of_items instead of cost_of_items(4, 250) does not run the function at all. Python treats the bare name as the function itself and stores that. Printing it shows a line describing a function object rather than a number, and any attempt to do arithmetic with it fails. The fix: A call always has brackets. Even a function that needs no values is called as show_menu(), never as show_menu.

Giving the wrong number of arguments

The number of arguments in the call must match the number of parameters in the definition. If you call cost_of_items(4) and leave out the price, Python stops and reports a TypeError, saying that one required positional argument named price_each is missing. Give too many, and it reports that the function takes 2 arguments but more were given.

This strictness is helpful rather than annoying. It catches a whole class of mistakes at the moment they happen, instead of letting the program run on with a missing value. Section 16.7 shows how default values let a function be called with fewer arguments on purpose.

16.2.3 The Docstring

Six months after you write a function, you will not remember what it expects or what it gives back. Somebody else reading your program will never have known. A function’s name helps, but a name can only say so much.

Python provides a place to write that explanation, right inside the function itself. A docstring is a piece of text placed as the very first thing in the function body, wrapped in triple quotes, that describes what the function does.

def safe_to_move(distance_cm):
    """Return True if the path ahead is clear.

    distance_cm is the reading from the front ultrasonic sensor.
    The robot may move only when nothing is within 20 cm.
    """
    return distance_cm > 20

Triple quotes — three double-quote marks, """ — are used because they let the text run over several lines. A one-line docstring is fine too, and is written on a single line between the same triple quotes.

The docstring is optional. Leave it out and the function works exactly the same. But it costs two lines to write and saves a great deal of guessing later.

What to put in it

Say what the function does, what each value it takes is meant to be, and what it gives back. Say it in plain sentences, not in code. The docstring above tells a reader three useful things: the answer is True when the path is clear, distance_cm comes from the front sensor, and the cut-off is 20 cm.

Do not describe how the function works line by line. That is what the code itself shows, and a step-by-step description goes stale the moment somebody edits the code.

checking whether a robot may move forward Problem: Given a reading from the robot’s front distance sensor, report whether the robot is allowed to move.

def safe_to_move(distance_cm):
    """Return True if the path ahead is clear.

    distance_cm is the reading from the front ultrasonic sensor.
    The robot may move only when nothing is within 20 cm.
    """
    return distance_cm > 20

print(safe_to_move(35))
print(safe_to_move(12))

Output:

True
False

With 35 cm ahead the path is clear, so the function returns True. With 12 cm the obstacle is inside the 20 cm limit, so it returns False.

Python can read it back to you

A docstring is not just a note for humans. Python stores it, and the built-in help() function prints it out. Add help(safe_to_move) to the end of the program above and you get:

Help on function safe_to_move in module __main__:

safe_to_move(distance_cm)
    Return True if the path ahead is clear.

    distance_cm is the reading from the front ultrasonic sensor.
    The robot may move only when nothing is within 20 cm.

This is exactly how the help for Python’s own functions is produced. Try help(len) and you will see the same layout.

Q. Is a docstring the same as a comment starting with #?

A. No. A comment is ignored completely by Python. A docstring is stored with the function, so tools like help() can show it. Use comments for short notes inside the body, and a docstring for the description of the whole function.

putting the docstring in the wrong place What goes wrong: If any line of code comes before the triple-quoted text, Python treats that text as an ordinary piece of text sitting in the middle of the function. It is no longer the docstring, and help() shows nothing. The fix: The docstring must be the first line of the function body, immediately below the def header.

Add a docstring to any function you have written so far, then call help() on it and read what Python prints. Now delete one line of your docstring and run help() again to see the change.

16.3 Built-in Functions

You have been calling functions since your very first Python program. print() is a function. So are input(), len() and int(). Nobody defined them in your file, yet they work the moment you type them.

They work because they come with the language. A built-in function is a function that is part of Python itself and is available in every program without any extra step.

NoteDefinition

A built-in function is a function supplied by the Python language itself. It can be used straight away in any program, with no need to define it and no need to import anything first.

That last part matters. Python also comes with a large collection of extra functions kept in separate files called modules — for example math.sqrt(), which finds a square root. Those are not built-in. You have to write an import line before you can use them. Built-in functions need no such line.

Some you will use often

Table 16.2 – common built-in functions, what each one needs and what it gives back | Function | What you give it | What it gives back | |—|—|—| | print() | Any values to display | Nothing; it writes the values on the screen | | input() | A message to show the user | Whatever the user typed, always as text | | len() | A string, list, tuple or dictionary | The number of items in it | | range() | A count, or a start and stop | A sequence of numbers, usually used in a for loop | | abs() | A number | That number without its sign | | round() | A number, and how many decimal places | The number rounded to those places | | sorted() | A list, tuple or string | A new list with the items in order | | int() | Text or a decimal number | The value as a whole number |

Note what len() and sorted() have in common: they do not care much what kind of collection you hand them. len("robot") gives 5, and len([4, 9, 2]) also gives 3 items counted the same way. One function covers many kinds of data.

Putting several to work

A robot’s front sensor takes five distance readings as it crosses a room. The teacher wants a small report: how many readings there were, the nearest and farthest objects seen, the readings in order, and the average distance.

Every part of that report can be got from built-in functions. len() counts, min() and max() pick out the smallest and largest, sorted() arranges, sum() adds up, and round() tidies the average to two decimal places.

a report from a robot’s distance readings Problem: From a list of distance readings in centimetres, print the number of readings, the nearest and farthest distances, the readings in order, and the average distance to two decimal places.

readings = [42.7, 18.3, 25.9, 61.2, 18.3]

print("Number of readings:", len(readings))
print("Nearest object at:", min(readings), "cm")
print("Farthest object at:", max(readings), "cm")
print("Readings in order:", sorted(readings))
print("Average distance:", round(sum(readings) / len(readings), 2), "cm")

Output:

Number of readings: 5
Nearest object at: 18.3 cm
Farthest object at: 61.2 cm
Readings in order: [18.3, 18.3, 25.9, 42.7, 61.2]
Average distance: 33.28 cm

checking the average distance

Given: readings of 42.7 cm, 18.3 cm, 25.9 cm, 61.2 cm and 18.3 cm

Find: the average distance, to two decimal places

Working:

total = 42.7 + 18.3 + 25.9 + 61.2 + 18.3 = 166.4 cm

number of readings = 5

average = 166.4 ÷ 5 = 33.28 cm

33.28 cm

Not one line of that program says how to count a list, sort it or add it up. Those instructions exist, but somebody wrote them long ago and gave them names. That is the whole idea of a function, working at the scale of the language itself.

Q. Why does sorted() leave the original list unchanged?

A. It builds and returns a new list rather than rearranging the one you gave it. That is safer, because your original readings are still available in the order they were taken.

Two small ones worth knowing

abs() gives a number without its sign, which is useful whenever you care about how far off something is but not in which direction. If a robot was told to stop 30 cm from a wall and actually stopped at 26 cm, then abs(26 - 30) gives 4 — the error is 4 cm, whether it stopped short or overshot.

round() cuts a long decimal down to a sensible size. round(3.14159, 2) gives 3.14. Without a second value, round() gives the nearest whole number.

expecting input() to give a number What goes wrong: age = input("Age: ") followed by age + 1 fails, because input() always returns text. Adding 1 to text is not allowed. The fix: Convert it first — age = int(input("Age: ")). Here two built-in functions are used together, the inner one running first.

Built-in functions are fast, well tested and used by everyone, so prefer them over writing your own version of the same thing. But they only cover general jobs. No built-in function knows what your robot is meant to do, or how your school calculates a grade. For that you write your own, which is what Section 16.4 is about.

16.4 User-defined Functions

Python’s own functions handle general jobs — counting, sorting, rounding. They cannot handle the job in front of you, because nobody who built Python knew about your school’s grading rule or your robot’s wiring. For that you write the function yourself.

A user-defined function is a function you write with def to do a task your program needs.

NoteDefinition

A user-defined function is a function created by the programmer, using the def keyword, to carry out a particular task. It must be defined in the program before it can be called.

The word “user” here means the person writing the program, not the person using it later.

One job, one function

The most useful habit in this whole chapter is to give each function one clear job, and let a bigger function call the smaller ones.

Take a school report. Turning marks into a percentage is one job. Turning a percentage into a grade is a second job. Printing a line for one student is a third, and it uses the first two. See Fig. 16.4-1.

Diagram

Fig. 16.4-1 – Three boxes. A large box on the left labelled report, with two arrows going out to smaller boxes on the right labelled percentage and grade. The arrow to percentage is labelled “marks in, percentage out”; the arrow to grade is labelled “percentage in, letter out”. A single arrow leaves report at the bottom to a small screen icon labelled “one printed line”.

printing a report line for each student Problem: For each of three students, work out the percentage from marks obtained out of a total, decide the grade from that percentage, and print one line naming all three.

def percentage(marks_got, marks_total):
    return marks_got / marks_total * 100

def grade(pct):
    if pct >= 80:
        return "A"
    elif pct >= 60:
        return "B"
    elif pct >= 40:
        return "C"
    else:
        return "D"

def report(name, marks_got, marks_total):
    pct = percentage(marks_got, marks_total)
    print(name, "scored", round(pct, 1), "percent and gets grade", grade(pct))

report("Meera", 172, 200)
report("Kabir", 118, 200)
report("Anil", 76, 200)

Output:

Meera scored 86.0 percent and gets grade A
Kabir scored 59.0 percent and gets grade C
Anil scored 38.0 percent and gets grade D

checking Kabir’s grade

Given: Kabir scored 118 marks out of 200. The grade bands are A from 80, B from 60, C from 40, and D below 40.

Find: Kabir’s percentage and grade

Working:

\(\text{percentage} = \dfrac{\text{marks obtained}}{\text{total marks}} \times 100 = \dfrac{118}{200} \times 100 = 59.0\)

59.0 is less than 60, so it does not reach band B. It is 40 or more, so it falls in band C.

59.0 percent, grade C

Notice how short report is. It reads almost like an English sentence, because the two hard parts have names of their own. If the school later changes the grade bands, you edit grade and nothing else. If it changes how percentages are rounded, you edit report and nothing else.

Q. Can one user-defined function call another one?

A. Yes, and it is the normal way to build a program. report calls both percentage and grade. A function can also call a built-in function, as report calls round() and print().

A grade rule written inside a function decides an outcome for every student the same way, which is fair only if the rule itself is fair and known. A cut-off buried in code that nobody outside sees cannot be questioned or corrected. Whenever a function decides something about people, the rule it applies should be published in plain words alongside it.

The same function, used many times

The second big gain is reuse. Once a function exists, a loop can call it for every item in a collection.

Before a dataset can be used to train a model — a program built from data, which you will meet later in the course — the labels have to be tidied up. People type them inconsistently: extra spaces, capitals in odd places. " Cat ", "CAT" and "cat" all mean the same animal, but a program compares them letter by letter and treats them as three different labels.

One small function fixes all of them.

tidying the labels in a small dataset Problem: A list of animal labels has been typed by different people. Remove the spaces at the ends and put everything in lower case, so that labels meaning the same thing look the same.

def tidy(label):
    return label.strip().lower()

labels = ["  Cat ", "DOG", "cat", "Dog "]
cleaned = []
for item in labels:
    cleaned.append(tidy(item))

print(cleaned)

Output:

['cat', 'dog', 'cat', 'dog']

The function is two lines long and is called four times. With four hundred labels the program would be exactly the same length — only the list would grow.

writing a function that prints when it should return What goes wrong: A function that prints its result instead of returning it cannot be used inside another calculation. total = tidy(" Cat ") would leave total empty if tidy only printed. The fix: Let a function return its result, and let the caller decide whether to print it, store it or use it further. Print inside a function only when printing is the actual job, as in report.

There is a cost to all this, and it is worth naming honestly. Splitting a program into many small functions means a reader has to jump between them to follow one idea. A function used only once, with a vague name, usually makes a program harder to read rather than easier. Write a function when the job has a name worth giving it.

16.5 Built-in and User-defined Functions Compared

Both kinds of function are used in exactly the same way. You write the name, you put the values in brackets, and you get an answer back. Python does not treat them differently at the moment of the call, and nothing in a line of code marks one kind out from the other.

def robot_id(number):
    return "RBT-" + str(number)

print(robot_id(7))
print(len(robot_id(7)))

Output:

RBT-7
5

There are three functions at work in the last line. robot_id is one you wrote, str() and len() came with Python, and print() did too. They are called the same way and their results feed into each other freely.

The difference is not in how they are used. It is in where they come from and who can change them.

Table 16.3 – built-in function vs user-defined function | Feature | Built-in function | User-defined function | |—|—|—| | Who provides it | The Python language itself | The programmer who writes the program | | Availability | Ready in every program, with nothing to import | Exists only after its def block has been written and read | | What it does | General jobs — counting, sorting, rounding, input and output | The particular job this program needs | | Can it be changed | No; the supplied version cannot be edited | Yes; you can rewrite it whenever the requirement changes | | Where the code lives | Inside Python, not in your file | In your own file, where you can read it | | Examples | print(), len(), abs(), round(), sorted() | battery_report(), percentage(), grade(), robot_id() |

Q2. What is the difference between a function Python already gives you and one you write yourself?

A. A built-in function comes with the language, is ready in every program without being defined or imported, and cannot be edited. A user-defined function is one you write with def for your own task; it exists only in programs where you have defined it, and you can change it whenever the task changes. Both are called in exactly the same way.

Q. What happens if I name my own function len?

A. Python accepts it, and from that point onwards len means your function instead of the built-in one. The original is hidden for the rest of the program. It is legal, but it will confuse anybody reading the code, so avoid the names of built-in functions when choosing your own.

Which one to reach for

The answer is nearly always the same. If a built-in function already does the job, use it. It has been tested by millions of programmers, it is usually faster than anything you would write, and a reader recognises it at a glance.

Write your own when no built-in does what you need, or when a piece of your program deserves a name of its own. Those two reasons cover almost every function you will ever write.

16.6 Functions and Modularity

A robot is not built as one solid object. It has a mechanical block, an electronic block and a computational block, and each of those is made of parts that fit together. If a motor burns out, you replace that motor. Nobody rebuilds the chassis to fix a wheel.

Programs are built the same way, and for the same reason. Modularity means building something as a set of separate parts, each with its own job, that fit together to make the whole. In a Python program, functions are those parts.

NoteDefinition

Modularity is the practice of dividing a large program into smaller, self-contained parts, each doing one clearly defined job. Functions are how a Python program is made modular.

Fig. 16.6-1 shows the difference in shape between a program written as one long stretch of code and the same program written as a set of functions.

Diagram

Fig. 16.6-1 – Two panels side by side. Left panel titled “One long program”: a single tall unbroken rectangle of code lines with a small red cross somewhere in the middle and a caption “a change anywhere means reading everything”. Right panel titled “Built from functions”: four small stacked boxes labelled days_late, fine, slip and “main program”, with arrows from “main program” to the others, and a small red cross inside only the fine box, captioned “a change stays inside one box”.

Five things modularity gives you

Encapsulation. A function keeps its working steps inside itself and shows the outside world only its name, the values it needs and the value it gives back. Whoever calls fine(5) does not need to know how the amount is worked out. Hiding the details this way is called encapsulation, and it is what lets you use a function you have not read.

Reuse. Once a function exists, it can be called from anywhere in the program, as many times as needed. The same code is not written twice, so it does not have to be maintained twice.

Readability. A program made of well-named functions can be read at the level of what it does before you go down into how. A line saying slip("Meera", 12, 17) tells you the point of it immediately.

Scalability. New work is added by writing a new function, not by threading extra lines through code that already works. A program can grow a great deal without becoming tangled.

Debugging. When something is wrong, modular code narrows the search. If the number of days is right but the amount is wrong, the mistake is inside fine and nowhere else. You have three lines to check instead of thirty.

Seeing it in a small program

The school library charges ₹2 for each day a book is late. Three separate jobs are hiding in that sentence: working out how many days late a book is, turning that number of days into an amount, and printing a slip for the student. Give each one a function.

library fine slips Problem: A book is due on one day of the month and returned on another. Work out how many days late it is, charge ₹2 for each late day, and print a slip for the student. A book returned on time carries no fine.

def days_late(due_day, returned_day):
    return returned_day - due_day

def fine(days):
    if days <= 0:
        return 0
    return days * 2

def slip(name, due_day, returned_day):
    late = days_late(due_day, returned_day)
    return name + ": " + str(late) + " days late, fine Rs " + str(fine(late))

print(slip("Meera", 12, 17))
print(slip("Kabir", 12, 12))

Output:

Meera: 5 days late, fine Rs 10
Kabir: 0 days late, fine Rs 0

Meera’s fine

Given: the book was due on day 12 and returned on day 17. The charge is ₹2 for each late day.

Find: the fine

Working:

days late = 17 − 12 = 5 days

fine = 5 × ₹2 = ₹10

₹10

Now suppose the school raises the charge to ₹3 a day. There is exactly one line to change — return days * 3 inside fine. The other two functions and the calls at the bottom stay untouched, because neither of them knows or cares what the rate is. That is encapsulation paying for itself.

Q. Why not put the whole fine calculation into slip and save two functions?

A. It would work for this small program. But then slip would be doing three jobs at once, and any other part of the program needing a fine would have to repeat the calculation. Separating them means the rule lives in one place.

Change the fine rate in the program above from ₹2 to ₹3 a day and run it again. Count how many lines you had to edit, then work out how many you would have edited if the calculation had been copied into every slip.

one function trying to do everything What goes wrong: A single function grows to forty lines, reads input, calculates, decides and prints. Nothing can be reused, and a fault anywhere means reading the whole thing. The fix: Look for the word “and” in your description of the function. “Work out the fine and print the slip” is two jobs, and two jobs means two functions.

Modularity is a habit, not a rule, and it can be overdone. Ten functions of two lines each, with names that do not mean much, are harder to follow than one clear function of fifteen lines. The test is whether each part has a job you can name in a few words.

16.7 Arguments Passed to a Function

You already know what an argument is: a value written inside the brackets of a call, which Python copies into the matching parameter before the function body runs. So far every call in this chapter has supplied its arguments in the same plain way — the right number of values, in the right order.

That is not the only way. Python offers a few different ways of handing values over, and each one exists to solve a real difficulty.

Think about a function that tells a robot to move:

def move(direction, speed, duration):

Three values, and a call such as move("left", 30, 2) is easy to get wrong. Was 30 the speed or the duration? What if most moves happen at the same speed and typing it every time is a nuisance? What if a function has to accept a list of readings whose length you do not know in advance?

Each of those questions has an answer, and together they make up the rest of this section. See Fig. 16.7-1.

Diagram

Fig. 16.7-1 – A single box on the right labelled def move(direction, speed, duration): with three small slots drawn inside it. Four arrows come in from the left, each from a labelled tag: “positional — matched by order”, “keyword — matched by name”, “default — value already waiting in the slot”, and “variable length — one arrow that splits into many”. Each arrow points to the slots of the function box.

The ways of supplying arguments are:

  1. Positional arguments — matched to parameters by the order they are written in.
  2. Keyword arguments — matched by naming the parameter in the call.
  3. Default arguments — a value written into the definition, used when the caller supplies none.
  4. Variable length arguments — used when the number of values is not fixed in advance.

These are not four different kinds of function. They are four ways of calling and defining, and a single function can use more than one of them at the same time.

Q. Does the function body know which way its values arrived?

A. No. By the time the body starts running, each parameter simply holds a value. How that value was supplied makes no difference to the code inside.

One rule runs through all four, and it is worth fixing in your mind before the details: the definition decides what values a function needs, and the call decides how those values are supplied. Sections 16.7.1 to 16.7.4 take each way in turn.

16.7.1 Positional Arguments

This is the ordinary way of passing values, and the one you have been using all through the chapter. A positional argument is matched to a parameter by where it appears in the call: first argument to first parameter, second to second, and so on.

Nothing in the call says which parameter a value belongs to. Its position says it.

sending a movement instruction to a robot Problem: Print the instruction a robot has been given — the direction, the speed and how long the move should last. Show what happens when the same values are supplied in a different order.

def move(direction, speed, duration):
    print("Moving", direction, "at speed", speed, "for", duration, "seconds")

move("left", 30, 2)
move(30, "left", 2)

Output:

Moving left at speed 30 for 2 seconds
Moving 30 at speed left for 2 seconds

The first call is right. In the second, the same three values were passed in a different order, and Python obediently put 30 into direction and "left" into speed.

Look closely at what did not happen. There was no error. Python does not know that a direction should be a word and a speed should be a number — those are meanings in your head, not rules in the code. It matched by position and carried on. On a real robot the instruction would be nonsense, and the fault would show up only in the robot’s behaviour.

Test any program that drives motors with the robot lifted, so its wheels are clear of the table and cannot pull it off the edge. Switch off the battery before changing any wiring, and never hold a wheel or joint to stop it while the motor is powered.

Q. Then how do I know the right order for a function’s arguments?

A. Read the def line, or the docstring. The order of the parameters in the definition is the order the arguments must follow. This is one more reason to write a docstring for anything you will use again.

passing arguments in the wrong order What goes wrong: The program runs without complaint but does the wrong thing, because Python matches by position and cannot check that the meanings fit. The fix: Check the call against the def line. Where a function takes several values of similar type, name them in the call using keyword arguments, which Section 16.7.2 explains.

Order matters, count matters

Two rules apply to positional arguments, and both are strict.

The number of arguments must equal the number of parameters. Give too few or too many and Python stops with a TypeError before the function body runs at all.

The order must match the definition. This one Python cannot check for you, which is why it is the more dangerous of the two.

Positional arguments are short to write and perfectly clear when a function takes one or two values whose meanings are obvious. len(readings) and abs(-4) need nothing more. It is the long calls, with four or five values of the same type, where position alone stops being enough.

16.7.2 Keyword Arguments

Position works well for short calls. It stops working well when a function takes several values that look alike, and the reader of the call has to remember which slot is which.

Python lets you name the parameter in the call itself. A keyword argument is an argument written as parameter_name=value, so Python matches it by name instead of by position.

announcing a school event Problem: Print a one-line notice giving the event, the day and the place. Show the same notice produced by a plain call, by a call using names only, and by a call that mixes the two.

def notice(event, day, place):
    print(event, "will be held on", day, "at the", place)

notice("Science quiz", "Monday", "library")
notice(place="library", event="Science quiz", day="Monday")
notice("Science quiz", place="library", day="Monday")

Output:

Science quiz will be held on Monday at the library
Science quiz will be held on Monday at the library
Science quiz will be held on Monday at the library

All three calls do the same thing. The second one lists the values in a completely different order from the definition, and it still works, because each value carries the name of the parameter it belongs to.

Q3. Does the order in which you pass values to a function always matter?

A. Only for positional arguments. When you pass a value as a keyword argument, written as parameter_name=value, Python matches it by name and the order in the call makes no difference. Positional arguments must still come before any keyword arguments in the same call.

Mixing the two styles

The third call above mixes both styles: one positional argument first, then two keyword arguments. This is allowed, but only in that order.

Every positional argument must come before every keyword argument. Once you have started naming, you cannot go back to relying on position, because Python would have no way of telling which slot the unnamed value was meant for.

a positional argument after a keyword argument What goes wrong: Writing notice(event="Science quiz", "Monday", "library") stops the program before it runs, with a SyntaxError saying a positional argument follows a keyword argument. The fix: Put all the plain values first and all the named ones after — or name them all.

misspelling the parameter name What goes wrong: notice(event="Science quiz", day="Monday", venue="library") gives a TypeError saying the function got an unexpected keyword argument venue. The definition has no parameter by that name. The fix: The name in the call must match the name in the def line exactly, including its spelling and its case.

Q. Is a keyword argument the same as a variable being assigned?

A. It looks the same, but it is not. place="library" inside a call does not create a variable called place in your program. It only tells Python which parameter the value belongs to.

When to use names

Use keyword arguments when a call would otherwise be hard to read. A call such as report_card(85, 90, 78, 92) tells a reader nothing, while naming each value makes the meaning obvious without looking up the definition.

They are also useful when a function has many parameters with default values and you want to change only one of them, which Section 16.7.3 shows.

The cost is length. len(readings) needs no name, and len(obj=readings) would only make it harder to read. Name a value when the name adds meaning, not as a habit.

16.7.3 Default Arguments

Some values are the same nearly every time. A scan usually runs at the same speed. A photograph in a dataset usually has no label yet. Typing that same value into every call is tedious, and the day you mistype it, the program quietly does something else.

Python lets the definition itself carry a value to fall back on. A default argument is a value written into the function definition, which is used when the caller does not supply one.

The general shape is:

def function_name(parameter1, parameter2=default_value):
    # function body

Here parameter1 must always be supplied by the caller. parameter2 need not be — if it is left out, the value written in the definition is used.

NoteDefinition

A default argument is a value given to a parameter in the function definition. If the call supplies a value for that parameter, the supplied value is used; if not, the default value is used.

One default in action

Before a picture dataset can be used, every image needs a label saying what is in it. Labelling takes time, so images start out with no label at all and are filled in as people work through them.

recording the label of an image in a dataset Problem: Print the file name of an image and its label. If no label has been decided yet, record it as unlabelled.

def label_photo(file_name, label="unlabelled"):
    print(file_name, "->", label)

label_photo("img_01.jpg", "tractor")
label_photo("img_02.jpg")

Output:

img_01.jpg -> tractor
img_02.jpg -> unlabelled

The second call gives one argument where the definition has two parameters. That is not an error here, because label already has a value waiting for it.

Q4. What happens if you call a function but leave out one of the values it expects?

A. It depends on whether that parameter has a default value. If it does, the default is used and the program runs normally. If it does not, Python stops with a TypeError saying a required argument is missing — it will not guess a value for you.

A default is a decision the program makes on the caller’s behalf, and it is easy to forget it is there. If a default is applied to information about people — marking an entry as unlabelled, or as belonging to some category — anyone reading the results later may take it as a recorded fact rather than a value nobody supplied. Choose defaults that are obviously placeholders, and say in the docstring what they mean.

Several defaults, and skipping one

A function can give defaults to more than one parameter. Combine that with keyword arguments and you can change just the one value you care about, leaving the rest alone.

a robot’s scan settings Problem: Print the settings for a scan — the number of steps, the speed and the direction. Sensible values should be used for anything the caller does not mention.

def scan_report(steps=10, speed=30, direction="right"):
    print("Scanning", steps, "steps to the", direction, "at speed", speed)

scan_report()
scan_report(20)
scan_report(direction="left")

Output:

Scanning 10 steps to the right at speed 30
Scanning 20 steps to the right at speed 30
Scanning 10 steps to the left at speed 30

The first call supplies nothing, so all three defaults are used. The second supplies one positional argument, which goes into the first parameter, steps. The third names direction and changes only that, leaving steps and speed at their defaults.

That third call is the reason keyword arguments and default arguments are so often used together. Without the name, there would be no way to skip past steps and speed to reach direction.

Q. Does the default value get worked out fresh on every call?

A. No. Python fixes the default when it reads the def line, and the same value is used for every call that leaves the parameter out.

The one rule about order

Parameters with defaults must come after parameters without them.

putting a default parameter before a plain one What goes wrong: Writing def label_photo(label="unlabelled", file_name): stops the program at once with a SyntaxError saying a parameter without a default follows a parameter with a default. The fix: List all the parameters the caller must supply first, then the ones with defaults. Python needs this so that plain positional arguments can be matched from the left with no ambiguity.

Defaults make a function easier to call, but they also hide values from the reader of a call. Someone looking at scan_report() cannot see what speed was used without opening the definition. Give defaults to values that genuinely have a sensible normal setting, and leave the important ones for the caller to state.

16.7.4 Fixed and Variable Length Arguments

Every function so far has had a fixed number of parameters. move takes exactly three values, label_photo takes one or two. These are fixed arguments — the definition decides how many values the function accepts, and a call must fit that.

That is fine when the number is known in advance. It is a problem when it is not.

A robot takes distance readings as it crosses a room. On a short crossing it might take two. On a long one, twenty. You cannot write a definition with twenty parameters and hope, and you certainly cannot write a different function for every possible count.

For this Python offers variable length arguments — a way of letting a function accept any number of values, however many the caller happens to have.

Many values under one name

Put a star before a parameter name in the definition, and that parameter collects all the remaining positional arguments into a tuple.

adding up any number of distance readings Problem: Add up the distances a robot has travelled in a crossing, when the number of readings is not known in advance.

def total_distance(*readings):
    total = 0
    for one in readings:
        total = total + one
    return total

print(total_distance(12, 8))
print(total_distance(12, 8, 15, 4))
print(total_distance())

Output:

20
39
0

The same function handled two readings, then four, then none at all. Inside the function, readings is an ordinary tuple, so a for loop can go through it and len() can count it.

checking the four-reading total

Given: readings of 12 cm, 8 cm, 15 cm and 4 cm

Find: the total distance

Working: 12 + 8 = 20 cm; 20 + 15 = 35 cm; 35 + 4 = 39 cm

39 cm

Q. Why does the empty call give 0 rather than an error?

A. With no arguments supplied, readings is an empty tuple. The loop then runs zero times and total keeps the value it started with, which is 0.

The name readings is your choice, like any parameter name. It is the star that matters. You will often see the name args used, simply because that is the common habit, but any sensible name works.

Fixed and variable together

A definition can have ordinary parameters first and a starred one after. The fixed ones are filled from the left as usual, and everything left over goes into the starred parameter.

A second star, written as **, does the same job for keyword arguments: it collects any named values the caller supplies into a dictionary.

printing a robot’s details Problem: Print a robot’s name, which every robot has, followed by any extra details the caller chooses to give — and different robots have different extras.

def robot_details(name, **extras):
    print("Robot:", name)
    for key in extras:
        print(key, ":", extras[key])

robot_details("Rover", wheels=4, sensor="ultrasonic")

Output:

Robot: Rover
wheels : 4
sensor : ultrasonic

name is a fixed parameter and had to be supplied. Everything else arrived as keyword arguments and was gathered into extras, which the loop then went through.

Table 16.4 – fixed length vs variable length arguments | Feature | Fixed length arguments | Variable length arguments | |—|—|—| | How many values | Decided by the definition | Any number, decided by the caller | | Written as | def f(a, b): | def f(*values): or def f(**named): | | What the parameter holds | One value each | A tuple for *, a dictionary for ** | | If the count is wrong | TypeError before the body runs | Cannot happen; any count is accepted | | Use it when | You know exactly what the function needs | The number of values changes from call to call |

forgetting that a starred parameter holds a collection What goes wrong: Writing return readings * 2 expecting each reading to double. readings is a tuple, so this repeats the whole tuple instead. The fix: Work through the tuple with a loop, or use a built-in function such as sum() or len() on it.

Variable length arguments buy flexibility, and they cost clarity. A definition reading def f(*args): tells a reader nothing about what the function expects, and Python can no longer catch a call that supplies the wrong number of values. Use fixed parameters wherever the number of values is genuinely known, and keep the starred form for the cases where it truly is not.

16.8 Return Values and Return Types

A function that only prints is a dead end. Its answer goes to the screen and nowhere else, so no other part of the program can use it. A function that returns its answer hands it back to the caller, who can then store it, print it, or feed it into the next calculation.

That is what return does, and it does two things at once. It sends a value back to the place where the function was called, and it ends the function immediately.

Where the value goes

Look at what happens on the line answer = wheel_turns(120, 20) from Section 16.2.1. Python jumps into the function, runs it, meets return turns, and comes back carrying the value. At that moment the whole call is replaced by that value, and the assignment then stores it in answer.

So a call to a returning function can go wherever a value can go — on the right of an =, inside a print(), inside an if condition, or inside another call.

Q. Does return display the value on the screen?

A. No. It only hands the value back. If nothing is done with it — no variable, no print() — the value is simply discarded.

writing code after a return What goes wrong: A line placed below return inside the same block never runs, because return ends the function at once. The program shows no error and the missing step is easy to overlook. The fix: Make return the last thing that happens on any path through the function. If some work must happen first, move it above the return.

A function with no return

Not every function needs to send something back. battery_report from Section 16.2 printed its results and had no return at all. Such a function still gives something back, though — Python quietly returns the special value None, which stands for “no value here”.

def show(message):
    print(message)

result = show("Robot ready")
print(result)

Output:

Robot ready
None

The first line came from inside the function. The second is what result holds: None, because show never returned anything. This is worth knowing, because a None turning up unexpectedly in your program is almost always a call to a function you thought was returning a value.

The kinds of value a function can return

Python does not restrict what a function may return. Whatever kind of value your code can hold, a function can hand back.

Table 16.5 – common return types and what each is used for | Return type | Example of what comes back | Typically used for | |—|—|—| | Single value | 39 or "RBT-7" | One answer — a total, a name, a measurement | | Boolean | True or False | A yes-or-no decision, used in an if | | Tuple | (34, 6, 85.0) | Several related answers at once | | List | ['cat', 'dog'] | A collection that may be changed afterwards | | Dictionary | {'name': 'Rover', 'wheels': 4} | Answers that each need a label | | None | nothing | Functions whose job is to do something, not to work something out |

The kind of value a function gives back is called its return type. Notice that Python does not state the return type anywhere in the definition. You find it by reading the return statements — one more reason to say it in the docstring.

Returning more than one value

A return can list several values separated by commas. Python packs them into a tuple and returns that one tuple.

an attendance summary for a class Problem: Given the number of students present and the class strength, work out how many are absent and the attendance as a percentage, and report all three figures together.

def attendance(present, total):
    absent = total - present
    percent = present / total * 100
    return present, absent, round(percent, 1)

p, a, pc = attendance(34, 40)
print("Present:", p)
print("Absent:", a)
print("Attendance:", pc, "percent")

Output:

Present: 34
Absent: 6
Attendance: 85.0 percent

The line p, a, pc = attendance(34, 40) takes the three values out of the returned tuple and puts them into three variables in one step. If you would rather keep them together, write figures = attendance(34, 40) and the whole tuple (34, 6, 85.0) is stored under one name.

checking the attendance figures

Given: 34 students present out of a class strength of 40

Find: the number absent and the attendance percentage, to one decimal place

Working:

absent = 40 − 34 = 6 students

\(\text{attendance} = \dfrac{\text{present}}{\text{total}} \times 100 = \dfrac{34}{40} \times 100 = 85.0\)

6 absent, 85.0 percent attendance

Q5. Can a function send back more than one value at a time?

A. Yes. Write the values after return, separated by commas, and Python packs them into a single tuple and returns that. The caller can either store the whole tuple under one name, or split it across several variables in one line, as in p, a, pc = attendance(34, 40).

More than one return statement

A function may contain several return statements, with only one of them running on any given call. The grade function in Section 16.4 works this way: whichever band the percentage falls into, that return runs and the function ends there.

This is a clear way to write a decision. Just make sure every path through the function reaches a return. If some branch of an if has none, that call returns None, and the caller receives nothing useful without any warning.

16.9 Summary

  1. Copying the same lines of code again and again makes a program long and unsafe to change, because every copy has to be corrected separately and a forgotten copy keeps running the old instructions.
  2. A function is a named block of code that carries out one particular task. It is written once and can be used as many times as needed.
  3. Using a function takes two steps. The function definition writes it down; the function call runs it. A definition on its own makes nothing happen.
  4. A definition begins with the keyword def, then the function name, then the parameters in round brackets, then a colon. Everything indented below is the function body.
  5. A parameter is a name in the definition. An argument is a value in the call. Arguments are copied into parameters when the call runs.
  6. A docstring is a description of the function written in triple quotes as the first line of the body. Python stores it, and help() prints it out.
  7. A built-in function comes with Python and is ready in every program without being defined or imported. Examples are print(), len(), range(), abs(), round() and sorted().
  8. A user-defined function is one the programmer writes with def for a task the language does not already cover. Both kinds are called in exactly the same way.
  9. Modularity means building a program from small parts, each with one clear job. Functions provide this, and bring encapsulation, reuse, readability, scalability and easier debugging.
  10. Positional arguments are matched to parameters by their order in the call. Getting the order wrong usually gives no error, only a wrong result.
  11. Keyword arguments are written as parameter_name=value and are matched by name, so their order does not matter. All positional arguments must come before any keyword arguments.
  12. A default argument is a value written into the definition, used when the caller supplies none. Parameters with defaults must be listed after those without.
  13. Fixed arguments mean the definition decides how many values the function takes. Variable length arguments, written with a star before the parameter name, let a function accept any number of values.
  14. A return statement sends a value back to the caller and ends the function at once. A function with no return statement gives back None.
  15. A function can return a single value, a Boolean, a tuple, a list or a dictionary. Several values listed after return are packed into one tuple, which the caller can split across separate variables.
  16. Functions are a tool, not a rule. Write one where a job has a name worth giving it, and prefer a built-in function whenever one already does what you need.

16.10 Key Terms

  1. Argument – a value written inside the brackets of a function call, which is copied into a parameter when the function runs.
  2. Built-in function – a function supplied by Python itself, usable in any program without defining or importing anything.
  3. Default argument – a value written into a function definition, used for that parameter when the call supplies none.
  4. Docstring – a description of a function, written in triple quotes as the first line of the function body, which help() can display.
  5. Encapsulation – keeping a function’s working steps inside it and showing the outside world only its name, the values it needs and the value it returns.
  6. Fixed arguments – arguments whose number is decided by the function definition, so every call must supply exactly that many.
  7. Function – a named block of code that carries out one particular task and can be used as many times as needed.
  8. Function body – the indented block of instructions below the def header, which runs each time the function is called.
  9. Function call – the use of a function’s name with brackets in a program, which makes Python run the instructions inside it.
  10. Function definition – the block of code beginning with def that names a function and lists the instructions inside it.
  11. Keyword argument – an argument written as parameter_name=value in a call, matched to its parameter by name rather than by position.
  12. Model – a program built from data, which is used later to work something out from new data.
  13. Modularity – building a program as a set of smaller self-contained parts, each doing one clearly defined job.
  14. Parameter – a name listed in a function definition that stands for a value to be supplied when the function is called.
  15. Positional argument – an argument matched to a parameter by the order in which it appears in the call.
  16. Return statement – a statement beginning with return that sends a value back to the caller and ends the function immediately.
  17. User-defined function – a function written by the programmer with def to carry out a task the language does not already provide.
  18. Variable length arguments – a way of writing a parameter, with a star before its name, that lets a function accept any number of values.

16.11 Objective Questions

  1. (Which keyword is used to begin a function definition in Python?)

    1. define
    2. def
    3. function
    4. return
  2. (Fill in the blanks: A name listed inside the brackets of a function definition is called a ____________, while a value written inside the brackets of a function call is called an ____________.)

  3. (State whether true or false: Python runs the instructions inside a function as soon as it reads the function definition.)

  4. (Which of these is a built-in function?)

    1. percentage()
    2. battery_report()
    3. sorted()
    4. tidy()
  5. (Match each item in Column A with its correct meaning in Column B.)

    Column A Column B
    a. def i. Sends a value back to the caller and ends the function
    b. Docstring ii. Collects any number of positional arguments into a tuple
    c. return iii. The keyword that begins a function definition
    d. *readings iv. A description of the function, written in triple quotes
  6. (What is the output of this program?)

def price(units, rate=8):
    return units * rate

print(price(5))
  1. (What value does a function give back if it has no return statement?)
    1. 0
    2. An empty string
    3. None
    4. It gives an error

16.12 Concept Questions

  1. (Define a function. State any two problems that writing a function solves in a long program.)

  2. (Differentiate between a parameter and an argument. Use one short example of your own to show both.)

  3. (A function is defined as def timer(hours, minutes, seconds):. Explain why a call written with keyword arguments is easier to read than one written with positional arguments only.)

  4. (What would happen if a parameter with a default value were written before a parameter without one? Give a reason for your answer.)

  5. (Write a function that takes a distance in metres and a time in seconds and returns the speed in metres per second. Show one call to your function and state the value it returns.)

  6. (Explain how dividing a program into small functions makes it easier to correct a mistake. Support your answer with an example of a rule that might need to be changed later.)

16.13 Case-Based Questions

  1. (The stationery counter of a school sells items at fixed rates. The record for one morning is shown below.)

    Item Rate per unit (₹) Quantity sold
    Notebook 40 3
    Pen 10 5
    1. Write a function named amount that takes a quantity and a rate, and returns the amount payable for that item.
    2. Using your function, work out the amount for notebooks, the amount for pens, and the total bill for the morning. Show the calculation.
    3. Most days only pens are sold, always at ₹10. Explain how a default argument would shorten the calls, and write the changed def line.
  2. (The robotics club checks the battery level of its four robots before every practice session. The readings taken one afternoon are shown below. A robot with a battery level below 25 percent must be charged before use.)

    Robot Battery level (percent)
    Bot-A 68
    Bot-B 21
    Bot-C 45
    Bot-D 12
    1. Name the built-in functions you would use to find how many readings were taken and which robot has the lowest battery level.
    2. Write a function named needs_charging that takes a battery level and returns True if the robot must be charged, and False otherwise. State the return type of your function.
    3. The club later buys two more robots, so the number of readings changes from session to session. Explain how a variable length argument would let one function add up the readings for any session.