Previous | Tutorial index | Next

Tutorial 2: Defining and Calling Functions – Start with def

Learning Objectives

Overview

Functions are the building blocks of reusable, organized, and readable code. They allow you to encapsulate logic, avoid repetition, and structure your programs into manageable pieces. In this tutorial, you will learn how to define your own functions in Python using the def keyword, how to pass data to them via parameters, how to return results, and how to call them effectively. We will also cover best practices like writing docstrings, understanding function scope, and the important distinction between pure and impure functions.

1. The def Keyword – Function Definition Syntax

1.1 Basic Structure

A function definition starts with the def keyword, followed by the function name, parentheses () containing optional parameters, a colon :, and an indented block of code.

def function_name(parameter1, parameter2): """Optional docstring.""" # function body return result

1.2 The Simplest Function

def say_hello(): print("Hello, world!")

This function takes no arguments, does not return a value, and simply prints a message.

1.3 Indentation – The Structural Foundation

Python uses indentation to define blocks of code. All lines inside the function must be indented consistently (usually 4 spaces). Incorrect indentation will cause IndentationError.

def bad_indentation(): print("This is fine") print("This will cause IndentationError") # different indentation level

2. Function Naming Conventions and Docstrings

2.1 Naming Functions

2.2 Docstrings – Documenting Your Functions

A docstring is the first string literal that appears inside a function. It is used to describe what the function does, its parameters, and its return value. This is a critical part of writing maintainable code.

def calculate_area(length, width): """ Calculate the area of a rectangle. Args: length (float): The length of the rectangle. width (float): The width of the rectangle. Returns: float: The area (length * width). """ return length * width help(calculate_area) # prints the docstring

3. Parameters vs. Arguments – The Crucial Distinction

This is a common source of confusion for beginners.

def square(number): # 'number' is a parameter return number * number result = square(5) # 5 is the argument

3.1 Positional Arguments

The order of arguments must match the order of parameters.

def introduce(name, age): print(f"{name} is {age} years old.") introduce("Alice", 30) # "Alice" -> name, 30 -> age

3.2 Keyword Arguments

You can specify which argument corresponds to which parameter by name, allowing you to change the order.

introduce(age=25, name="Bob") # works fine

3.3 Default Parameters

You can assign default values to parameters. If the caller omits an argument, the default is used.

def greet(name, greeting="Hello"): return f"{greeting}, {name}!" print(greet("Alice")) # Hello, Alice! print(greet("Bob", "Hi")) # Hi, Bob!

Important: Default parameters are evaluated once at definition time. Avoid using mutable defaults like [] or {} unless you understand the implications (more on this later).

4. The return Statement

4.1 Returning a Value

The return statement exits the function and optionally passes back a value.

def add(a, b): return a + b sum_value = add(3, 4) # sum_value = 7

4.2 Returning Multiple Values

You can return multiple values by separating them with commas – Python packs them into a tuple.

def min_max(numbers): return min(numbers), max(numbers) low, high = min_max([1, 5, 3, 9]) # low=1, high=9

4.3 Functions Without return

If you do not include a return statement, or if you use return without a value, the function returns None.

def do_nothing(): pass print(do_nothing()) # None

5. Built‑in Functions vs. Custom Functions

5.1 Built‑in Functions

Python comes with many built‑in functions that are always available:

5.2 Custom Functions

Functions you define using def are stored in your module’s namespace and can be called just like built‑in functions.

# Custom function def double(x): return x * 2 # Calling both print(double(5)) # 10 print(len("hello")) # 5

6. Pure Functions vs. Impure Functions

6.1 Pure Functions

A pure function has two properties:

  1. It always returns the same output for the same input.
  2. It does not produce side effects (e.g., it does not modify global variables, write to files, or print to the console).

Example of a pure function:

def add(a, b): return a + b

This is deterministic, predictable, and easy to test.

6.2 Impure Functions

An impure function may rely on or modify state outside its scope, or it may produce side effects.

Examples:

counter = 0 def increment(): global counter counter += 1 # modifies global state – impure def log_message(msg): print(msg) # side effect (printing) – impure

Guideline: Aim to write pure functions whenever possible. They are easier to reason about, test, and debug. However, real‑world programs inevitably need impure functions for I/O, logging, and state management. The key is to isolate impure parts from pure logic.

7. Variable Scope – Local vs. Global

7.1 Local Variables

Variables defined inside a function are local – they exist only within that function.

def my_func(): x = 10 # local variable print(x) my_func() # print(x) # NameError: name 'x' is not defined

7.2 Global Variables

Variables defined at the top level of a script are global. They can be read inside functions, but to modify them you need the global keyword.

y = 5 # global variable def read_global(): print(y) # works fine def modify_global(): global y y = 99 # now modifies the global y

Best Practice: Minimize the use of global variables. Pass values as arguments instead of relying on globals.

8. Advanced Parameter Types (Brief Overview)

8.1 *args – Variable‑Length Positional Arguments

Allows a function to accept any number of positional arguments; they are collected into a tuple.

def sum_all(*numbers): return sum(numbers) print(sum_all(1, 2, 3)) # 6 print(sum_all(10, 20)) # 30

8.2 **kwargs – Variable‑Length Keyword Arguments

Allows a function to accept any number of keyword arguments; they are collected into a dictionary.

def print_info(**info): for key, value in info.items(): print(f"{key}: {value}") print_info(name="Alice", age=30, city="NYC")

8.3 Default Parameter Pitfall (Mutable Defaults)

Danger: Using a mutable default (e.g., def append_to_list(item, lst=[]):) can lead to unexpected behavior because the default object is shared across calls.

def bad_append(item, lst=[]): lst.append(item) return lst print(bad_append(1)) # [1] print(bad_append(2)) # [1, 2] – unexpected!

Fix: Use None as a sentinel.

def good_append(item, lst=None): if lst is None: lst = [] lst.append(item) return lst

Python 3.5+ supports type hints, which improve code clarity and can be used by static checkers like mypy.

def greet(name: str) -> str: return f"Hello, {name}!"

They do not enforce types at runtime, but they make your intentions clear.

10. Function as Objects – First‑Class Citizens

In Python, functions are objects. You can assign them to variables, pass them as arguments, and return them from other functions.

def square(x): return x ** 2 func = square print(func(5)) # 25

This is the foundation for higher‑order functions (e.g., map, filter, sorted with key).

📝 Quiz – Check Your Understanding

  1. Which keyword is used to define a function in Python?

    Answer(B) `def`
  2. What is a docstring?

    Answer(C) A string literal that documents the function’s purpose.
  3. What will be the output of this code?

    def test(a, b=5): return a + b print(test(3))
    Answer(B) `8` – because `b` defaults to 5, so 3+5=8.
  4. True or False: A function without a return statement implicitly returns 0.

    AnswerFalse – it returns `None`.
  5. What is the difference between a parameter and an argument?

    Answer(C) Parameter is the placeholder in the definition; argument is the value in the call.
  6. Which of the following is a pure function?

    Answer(A) – no side effects, deterministic output.
  7. What does the global keyword do?

    Answer(B) Allows a function to modify a global variable.
  8. What will this code output?

    def add(a, b): return a + b func = add print(func(2, 3))
    Answer(B) `5`
  9. What is the issue with this function definition?

    def append_to(item, lst=[]): lst.append(item) return lst
    Answer(B) Mutable default argument, leading to shared state across calls.
  10. What does *args represent in a function definition?

    Answer(C) A tuple of variable‑length positional arguments.

💻 Exercises – Practice Makes Perfect

Exercise 1: Basic Function
Write a function is_even(n) that takes an integer n and returns True if it is even, False otherwise. Test it with several numbers.

Sample Solution ```python def is_even(n): return n % 2 == 0

print(is_even(4)) # True print(is_even(7)) # False

</details> **Exercise 2: Rectangle Geometry** Define a function `rectangle_info(width, height)` that returns a tuple `(area, perimeter)`. Write a docstring for the function. Test with `width=5, height=3`. <details><summary>Sample Solution</summary> ```python def rectangle_info(width, height): """Return (area, perimeter) of a rectangle.""" area = width * height perimeter = 2 * (width + height) return area, perimeter print(rectangle_info(5, 3)) # (15, 16)

Exercise 3: Greeting with Default
Write a function greet_user(name, greeting="Hello") that returns a formatted greeting. Call it with a name only, and then with a custom greeting.

Sample Solution ```python def greet_user(name, greeting="Hello"): return f"{greeting}, {name}!"

print(greet_user("Alice")) # Hello, Alice! print(greet_user("Bob", "Hi")) # Hi, Bob!

</details> **Exercise 4: Variable‑Length Sum** Write a function `average(*numbers)` that returns the average of any number of numeric arguments. If no arguments are given, return `0.0`. <details><summary>Sample Solution</summary> ```python def average(*numbers): if not numbers: return 0.0 return sum(numbers) / len(numbers) print(average(10, 20, 30)) # 20.0 print(average()) # 0.0

Exercise 5: Pure vs Impure
Rewrite the following impure function as a pure function:

total = 0 def add_to_total(amount): global total total += amount return total

Then, write a pure version that takes the current total as an argument and returns the new total.

Sample Solution ```python def add_to_total(total, amount): return total + amount

Usage

new_total = add_to_total(10, 5) # returns 15

</details> --- ### 🏠 Homework – Deeper Thinking #### Short Answer Questions **1. Temperature Converter** Write a function `convert_temp(value, from_unit, to_unit)` that converts between Celsius, Fahrenheit, and Kelvin. Return the converted value rounded to 2 decimal places. Raise `ValueError` for invalid units. <details><summary>Sample Answer</summary> ```python def convert_temp(value, from_unit, to_unit): # Convert from_unit to Celsius first if from_unit == 'C': celsius = value elif from_unit == 'F': celsius = (value - 32) * 5 / 9 elif from_unit == 'K': celsius = value - 273.15 else: raise ValueError("Invalid from_unit") # Convert Celsius to target if to_unit == 'C': result = celsius elif to_unit == 'F': result = celsius * 9 / 5 + 32 elif to_unit == 'K': result = celsius + 273.15 else: raise ValueError("Invalid to_unit") return round(result, 2)

2. Password Validator
Write a function validate_password(password) that checks length, uppercase/lowercase/digit presence, and absence of substring 'password'. Return a tuple (bool, list_of_failures).

Sample Answer ```python def validate_password(password): failures = [] if len(password) < 8:failures.append("Atleast8characters")ifnotany(c.isupper()forcinpassword):failures.append("Missinguppercase")ifnotany(c.islower()forcinpassword):failures.append("Missinglowercase")ifnotany(c.isdigit()forcinpassword):failures.append("Missingdigit")if'password'inpassword.lower():failures.append("Contains'password'")return(len(failures) == 0,failures)```

3. Fibonacci with Memoization
Write a pure function fibonacci(n) using a default cache dictionary for memoization.

Sample Answer ```python def fibonacci(n, cache={0: 0, 1: 1}): if n not in cache: cache[n] = fibonacci(n-1) + fibonacci(n-2) return cache[n] ```

Essay Questions

4. Shopping Cart Functions
Design a set of functions to manage a shopping cart represented as a list of dictionaries. Write add_item, remove_item, total_price, and checkout functions. Decide whether they are pure or impure and document clearly.

Sample Answer ```python def add_item(cart, item, price, quantity): """Impure: modifies cart in place.""" for entry in cart: if entry['item'] == item: entry['quantity'] += quantity return cart.append({'item': item, 'price': price, 'quantity': quantity})

def remove_item(cart, item): """Impure: removes first matching item.""" for i, entry in enumerate(cart): if entry['item'] == item: del cart[i] return

def total_price(cart): """Pure: returns total without modifying cart.""" return sum(entry['price'] * entry['quantity'] for entry in cart)

def checkout(cart): """Impure: prints receipt and clears cart.""" print("Receipt:") for entry in cart: print(f"{entry['item']} x{entry['quantity']} = ${entry['price']*entry['quantity']:.2f}") cart.clear()

</details> **5. Function Composition** Write a function `compose(f, g)` that returns a new function `h` such that `h(x) = f(g(x))`. Then write a `chain` function that composes any number of functions in order. <details><summary>Sample Answer</summary> ```python def compose(f, g): return lambda x: f(g(x)) def chain(*funcs): def result(x): for f in funcs: x = f(x) return x return result # Example def square(x): return x * x def double(x): return x * 2 h = compose(square, double) # square(double(x)) print(h(3)) # 36 c = chain(square, double) # double(square(x)) print(c(3)) # 18

Homework Hints

Summary

In this tutorial, you have learned:

Functions are the primary mechanism for code reuse and abstraction in Python. Mastering them is essential for writing clean, efficient, and maintainable programs.

Next Steps: In Tutorial 3, we will explore Control Flow – conditionals and loops – and see how to combine them with functions to solve more complex problems.

Happy coding!

Previous | Tutorial index | Next