Python, Concept by Concept

unit

Functions

A science club receives temperatures in Celsius, but its display needs Fahrenheit. Your challenge is to build one converter tool, call it with several temperatures, and trust it to give a usable result every time.

By Lesson 3, you will package calculations and even a turtle polygon into tools with their own inputs and local names.

You will learn

  • Define and call a reusable function with def.
  • Give a function inputs through parameters and send a value back with return.
  • Use built-in number tools for common calculations.
  • Tell which names belong inside a function and which result the caller receives.

Lesson 1: Define & Call

A science club receives Celsius readings and needs one reusable Fahrenheit tool. First, see how a function is defined and called.

Functions that only print

Define greet with no parameters. Its indented body runs only when you call it.

def greet():
    print("Hello!")

greet()

Output

Hello!

Notice

Defining the function displays nothing. The call displays Hello!. This function prints but returns no useful value.

Now give greet one parameter. The caller supplies a name for that local value.

def greet(name):
    print("Hello, " + name + "!")

greet("Maya")

Output

Hello, Maya!

Notice

The call displays Hello, Maya!. name receives the argument for this call; the function still only prints.

Build the temperature-converter tool

A function is a named tool. This time it sends a result back with return. The input is a Celsius temperature, and the returned result is Fahrenheit.

def celsius_to_f(c):
    fahrenheit = c * 9 / 5 + 32
    return fahrenheit

print(celsius_to_f(-10))
print(celsius_to_f(0))
print(celsius_to_f(25))

Output

14.0
32.0
77.0

Visible payoff: one tool handles several inputs. The three calls return 14.0, 32.0, and 77.0. The / calculation makes each result a float.

Read the definition from top to bottom:

  • def tells Python that a function definition starts.
  • celsius_to_f is the function's meaningful name.
  • c is a parameter, a local name that receives one call's input.
  • The indented lines are the function body.
  • return fahrenheit sends the computed value back to the caller.

The definition stores the tool. A call such as celsius_to_f(25) uses it.

Small tools with one clear job

A function can return an integer or a boolean just as it can return a float. These two tools name their jobs and their inputs.

def square(n):
    result = n * n
    return result

def is_even(n):
    return n % 2 == 0

print(square(5))
print(is_even(14))
print(is_even(9))

Output

25
True
False

Notice

square(5) returns the integer 25. is_even(14) returns True, while is_even(9) returns False. The caller decides whether to print, save, compare, or calculate with a returned value.

Notice

Notice: printing is not returning

This function displays a number, but it does not return that number. Predict what happens when the caller tries to add 1 to the missing result.

Read the error

def f(n):
    print(n * n)

total = f(3) + 1
print(total)

Python first prints 9, then reports:

TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

Read the last line first. The + operation received a NoneType value and an integer. A function with no return gives back None, which means no useful value was returned. The caller cannot add 1 to that missing value.

Fix the boundary: let the function return its result, then let the caller choose to print or calculate with it.

def f(n):
    return n * n

total = f(3) + 1
print(total)

Output

10

Notice

The fixed call produces 10. print sends text to the screen; return sends a value to the caller. Exercise 2 is the one practice item pinned to this trap. In your other functions, focus on the value promised by the specification.

Try a real temperature

This interactive version reads one Celsius value, calls the converter, and displays its result.

Try it yourself

celsius = float(input("Celsius: "))
print(celsius_to_f(celsius))

Lesson 2: Functions that Compute

A number-puzzle table needs four reusable tools: test a prime, find a shared divisor, produce a Fibonacci number, and total the integers through a chosen endpoint. Each tool hides a loop behind a name and returns one answer.

Leave early with return

first_multiple(start, k) searches from start upward. The first matching number is returned immediately; the loop does not keep searching.

def first_multiple(start, k):
    number = start
    while True:
        if number % k == 0:
            return number
        number = number + 1

print(first_multiple(10, 4))

Output

12

Notice

The result is 12. Once return runs, the function is finished; later loop trips do not happen.

Test whether a number is prime

For n >= 2, try possible divisors from 2 through n - 1. The loop counter divisor names the number currently being tested. A divisor that leaves remainder 0 proves the answer is False; otherwise the function reaches return True.

def is_prime(n):
    for divisor in range(2, n):
        if n % divisor == 0:
            return False
    return True

print(is_prime(29))
print(is_prime(35))

Output

True
False

Notice

is_prime(29) returns True; is_prime(35) returns False as soon as divisor 5 is found. For n == 2, range(2, 2) supplies no values, so the final True is correct.

Try a real prime test

Read one integer and let is_prime test it.

Try it yourself

n = int(input("Number: "))
print(is_prime(n))

Greatest common divisor with while

Euclid's algorithm repeatedly replaces a pair with the second number and the remainder. When the second number becomes 0, the first number is the greatest common divisor. The changing b controls the loop.

def gcd(a, b):
    while b != 0:
        remainder = a % b
        a = b
        b = remainder
    return a

print(gcd(48, 18))
print(gcd(77, 28))

Output

6
7

Trace gcd(48, 18): the pairs become 48 and 18, then 18 and 12, then 12 and 6, then 6 and 0. The returned answer is 6. This is the least intuitive Lesson 2 tool, so it is the first item to defer when time is short.

Carry two Fibonacci values

The Fibonacci sequence starts 0, 1, 1, 2, 3, 5, 8, and so on. Two variables carry the current pair. A temporary variable named nxt protects the newly calculated value while a and b move forward.

def fib(n):
    a = 0
    b = 1
    for step in range(n):
        nxt = a + b
        a = b
        b = nxt
    return a

print(fib(0))
print(fib(1))
print(fib(7))

Output

0
1
13

Notice

The calls return 0, 1, and 13. The pair is updated in three clear steps: nxt = a + b, then a = b, then b = nxt. Each name holds one value at a time.

A hand-written running total

sum_to_n(n) adds every integer from 1 through n. The name total remembers the running result across loop trips.

def sum_to_n(n):
    total = 0
    for number in range(1, n + 1):
        total = total + number
    return total

print(sum_to_n(6))
print(sum_to_n(10))

Output

21
55

The calls return 21 and 55. Writing the loop exposes the running-total pattern.

One function can call another

sum_of_squares uses the square helper defined in Lesson 1. This is composition: small tools work together.

def sum_of_squares(n):
    total = 0
    for number in range(1, n + 1):
        total = total + square(number)
    return total

print(sum_of_squares(3))

Output

14

Notice

The result is 14 because square supplies 1, 4, and 9. The caller sees one result from sum_of_squares.

Python also provides small, already-built functions for common number jobs.

Built-in number tools

A built-in function is ready to call without defining it first. In this unit, use these number tools:

  • max(a, b) returns the larger of two numbers.
  • min(a, b, c) returns the smallest of three numbers.
  • sum(range(1, n + 1)) totals the integers supplied by a range.
  • abs(number) returns distance from zero.
  • round(number) returns a nearby whole number.
  • range(...) is the callable that supplies integers to a for loop.

The two-argument form round(number, 1) keeps one digit after the decimal point. len counts characters in a string today and items in a list in Unit 10.

print(max(7, 12))
print(min(9, 4, 15))
print(sum(range(1, 6)))
print(abs(-17))
print(round(3.6))
print(round(2.5))

Output

12
4
15
17
4
2

Notice

sum(range(1, 6)) returns 15, the same result a hand-written running-total loop would make for 1 through 5. max and min replace repeated comparisons when you only need an extreme number. abs(-17) returns 17.

Rounding has a tie rule worth noticing: round(2.5) == 2. Python sends an exact half toward the nearby even integer. Do not assume every .5 rounds upward.

Count characters in a string

Call the built-in len on a string to count its characters.

print(len("banner"))

Output

6

Notice

"banner" has 6 characters, so len("banner") returns 6.

A default parameter

A parameter can have a default value. If the caller omits that argument, Python uses the value in the definition.

def rule(n, symbol="-"):
    return symbol * n

print(rule(4))
print(rule(3, "="))

Output

----
===

Notice

rule(4) returns ----; rule(3, "=") returns ===. The second call replaces the default symbol.

Lesson 3: Scope

Imagine several teams calling the same scoring tool. Each call needs its own input names so one team's calculation cannot overwrite another's. Scope describes where a name can be used.

Parameters and body names are local

A parameter is a local name. A name first assigned in the function body is local too. Each call gets fresh local values.

score = 10

def add_bonus(score):
    bonus = 3
    score = score + bonus
    return score

new_score = add_bonus(score)
print(score)
print(new_score)

Output

10
13

The caller's score stays 10, and new_score becomes 13. Inside add_bonus, the parameter named score is local. Changing that local name does not leak out and replace the caller's name.

A function should take what it needs through parameters and return its result, rather than relying on outside names. This makes the tool honest about what it needs and what it provides.

Scope application: a polygon tool

Unit 06 drew polygons with repeated turtle commands. Now package that drawing rule as draw_polygon(n, side). The parameters n and side, the calculated angle, and the loop counter corner are local names inside this function.

The real program (assets/l3_draw_polygon.py)

# Bring in Python's turtle drawing tools.
import turtle


# Draw one closed regular polygon.
def draw_polygon(n, side):
    angle = 360 / n
    turtle.pendown()
    for corner in range(n):
        turtle.forward(side)
        turtle.left(angle)


turtle.pencolor("darkviolet")
draw_polygon(6, 56)

turtle.done()

Program

# Bring in Python's turtle drawing tools.
import turtle


# Draw one closed regular polygon.
def draw_polygon(n, side):
    angle = 360 / n
    turtle.pendown()
    for corner in range(n):
        turtle.forward(side)
        turtle.left(angle)


turtle.pencolor("darkviolet")
draw_polygon(6, 56)

turtle.done()

Program

# In the real program (assets/l3_draw_polygon.py), draw_polygon is defined above.
# Adding these two lines at the very end would show that `angle` is local to the function:
draw_polygon(6, 56)
print(angle)

Notice

The call can use angle while draw_polygon is running, but the next line cannot use that local name. Python reports NameError: name 'angle' is not defined. Read the error from the end: angle is not defined where print(angle) tried to use it.

Run the matching real program from this unit directory:

assets/l3_draw_polygon.py

The call supplies 6 and 56. Inside that one call, n is 6, side is 56, and angle is 360 / 6. The loop makes six pen-down moves and closes the hexagon. turtle.done() remains once at the end of the file, outside the function.

Return the result across the boundary

Local work stays protected inside a function, but a returned value crosses back to the caller on purpose.

def safe_temperature(c):
    fahrenheit = c * 9 / 5 + 32
    bounded = max(fahrenheit, 32)
    return round(bounded)

display_temperature = safe_temperature(-8)
print(display_temperature)

Output

32

Put it together: the caller provides an argument, the parameter receives it as a local name, the body computes with other local names, and return sends one result back. Meaningful names and short purpose comments make the boundary readable.

Try a real Celsius reading

The real program reads a value and passes it through safe_temperature.

Try it yourself

celsius = float(input("Celsius: "))
print(safe_temperature(celsius))

Recap

  • def gives a reusable set of instructions a name.
  • Parameters receive arguments when you call the function.
  • return sends a value back; print() only displays one.
  • Built-in tools such as max() and round() handle common number jobs.
  • Names made inside a function are local to that call.

You can now build a Celsius-to-Fahrenheit converter you can call with many readings.

Go to the exercises (32)

For parents and teachers: report a problem