python functions modules import

Advanced Functions and Modules in Python

Learning objectives

This pill dives deeper into functions and code structure:

  • understand how Python handles parameter passing (mutable vs immutable);
  • organise code into reusable modules;
  • use the if __name__ == "__main__": construct.

Pass by Value or by Reference?

In Python, parameter passing is technically defined as “Pass by Object Reference” (or Pass by Assignment). What matters is whether the object passed is mutable or immutable.

Immutable Objects (int, str, tuple)

If you pass a number or a string, the function works on a “local copy” of the reference. Modifying it inside the function has no effect on the external variable.

def try_modify(x):
    x = 100       # Assigns a new value only to local 'x'
    print(f"Inside: {x}")

number = 5
try_modify(number)
print(f"Outside: {number}")  # Still prints 5!

Mutable Objects (list, dict, set)

If you pass a list, the function receives a reference to the same object in memory. If you modify the content (e.g. .append()), the change persists outside too.

def add_element(lst):
    lst.append("New")  # Modifies the existing object

my_list = ["A", "B"]
add_element(my_list)
print(my_list)  # Prints ['A', 'B', 'New']

Note: Be careful: if inside the function you do lst = [...], you are creating a new local list and losing the link to the external one (similar behaviour to immutables).

Modules in Python

A module is simply a file ending in .py. It allows you to split code across multiple files to keep things organised.

Imagine you have a file maths.py:

# File: maths.py
PI = 3.14

def circle_area(radius):
    return PI * (radius ** 2)

You can use this module in another script (main.py) with import:

# File: main.py
import maths as m  # 'as' creates an alias

r = 5
print(m.circle_area(r))

The if __name__ == "__main__": block

When you run a Python file, the interpreter assigns the value "__main__" to the special internal variable __name__. If the file is imported as a module instead, __name__ will equal the filename.

This check is used to create scripts that can work both as standalone programs and as importable modules without executing unwanted code.

Ideal script structure:

# Standard library imports
import sys
import random

# Function definitions
def calculate():
    return random.randint(1, 100)

def main():
    """Main function that coordinates the program."""
    value = calculate()
    print(f"The calculated value is: {value}")

# Entry point
if __name__ == "__main__":
    # This block runs ONLY when you launch this file directly,
    # NOT when you import it.
    main()

Why use this structure?

  • Organisation: imports at the top, functions in the middle, execution at the bottom.
  • Reusability: you can import calculate() into another program without the main() output running automatically.
  • Scope: wrapping execution in a main() function avoids creating global variables that could interfere with each other.

In brief: always use the __main__ block in your scripts. Remember that lists passed to functions can be permanently modified, whereas numbers and strings cannot.