CODE: Python Functions and Modules

Functions in Python define reusable operations that execute a task and optionally return a value. Modules group those operations into files that programs can import to extend capabilities without rewriting code.

Defining and Calling Functions

Functions are reusable blocks of code that perform a specific task. They allow you to organizereuse, and simplify your programs by breaking them into smaller pieces.


What is a Function?

  • A function is a group of statements that run only when called.
  • Functions help avoid repeating code and make programs easier to maintain.
  • Python has many built-in functions (like len()print()range()), and you can also define your own.

Defining a Function

You define a function in Python using the def keyword.

python
def greet():
    print("Hello, welcome to Python!")

  • def → keyword to define a function.
  • greet → function name.
  • () → parentheses (can hold parameters).
  • : → starts the function body.
  • Indentation → defines the function’s block.

Calling a Function

To use (execute) a function, call it by name followed by parentheses.

python
greet()   # Output: Hello, welcome to Python!

A function can be called multiple times:

python
for i in range(3):
    greet()


Functions with Parameters

Functions can accept inputs, called parameters (or arguments when passed).

python
def greet_user(name):
    print("Hello,", name)

greet_user("Amr")   # Output: Hello, Amr
greet_user("Sara")  # Output: Hello, Sara

  • Parameters make functions flexible and reusable.

Functions with Multiple Parameters

You can define multiple parameters separated by commas.

python
def add(x, y):
    print("Sum is:", x + y)

add(3, 5)   # Output: Sum is: 8


Return Values

Functions can return results using the return statement.

python
def square(n):
    return n * n

result = square(4)
print(result)   # Output: 16

  • Without return, the function returns None by default.

Default Parameters

You can set default values for parameters.

python
def greet_user(name="Guest"):
    print("Welcome,", name)

greet_user()           # Output: Welcome, Guest
greet_user("Amr")      # Output: Welcome, Amr


Keyword Arguments

You can call functions by explicitly naming parameters.

python
def introduce(name, age):
    print(f"My name is {name} and I am {age} years old.")

introduce(age=25, name="Ali")


Function Arguments (Positional, Keyword, `*args`, `**kwargs`)

Functions in Python can accept inputs in different ways. Understanding how arguments are passed makes your functions flexible and reusable.


1. Positional Arguments

  • The most common type of arguments.
  • Values are passed in order to the function’s parameters.

python
def subtract(a, b):
    print(a - b)

subtract(10, 3)   # Output: 7
subtract(3, 10)   # Output: -7

The order matters!

2. Keyword Arguments

  • Instead of relying on position, you can pass arguments by name.
  • Makes functions more readable and avoids mistakes with order.

python
def introduce(name, age):
    print(f"My name is {name} and I am {age} years old.")

introduce(age=25, name="Amr")
# Output: My name is Amr and I am 25 years old.


3. Default Arguments

  • Parameters can have default values.
  • If no value is provided, the default is used.

python
def greet(name="Guest"):
    print("Hello,", name)

greet()          # Output: Hello, Guest
greet("Sara")    # Output: Hello, Sara


4. Arbitrary Positional Arguments (`*args`)

  • Use *args when you don’t know how many positional arguments will be passed.
  • args collects all extra arguments into a tuple.

python
def total(*numbers):
    print(numbers)       # tuple of all arguments
    return sum(numbers)

print(total(1, 2, 3))      # Output: 6
print(total(5, 10, 15, 20))  # Output: 50


5. Arbitrary Keyword Arguments (`**kwargs`)

  • Use kwargs to accept any number of keyword arguments**.
  • kwargs collects them into a dictionary.

python
def profile(**info):
    print(info)

profile(name="Amr", age=30, country="Netherlands")
# Output: {'name': 'Amr', 'age': 30, 'country': 'Netherlands'}

You can access values like a normal dictionary:

python
def profile(**info):
    print("Name:", info.get("name"))
    print("Age:", info.get("age"))

profile(name="Sara", age=25)


6. Mixing Different Argument Types

Python functions can combine all these types, but the order matters:

**Order of arguments in function definition**

positional → args → default → *kwargs

python
def demo(a, b, *args, x=10, **kwargs):
    print("a:", a)
    print("b:", b)
    print("args:", args)
    print("x:", x)
    print("kwargs:", kwargs)

demo(1, 2, 3, 4, 5, x=99, name="Amr", age=30)

Output:

shell
a: 1
b: 2
args: (3, 4, 5)
x: 99
kwargs: {'name': 'Amr', 'age': 30}


Return Values and Scope (Local vs Global Variables)

Functions often need to send back results using return. Along with this, Python has rules about where variables “live,” called scope. Understanding both is key to writing clean, bug-free programs.


1. Return Values

  • A function can return data with the return keyword.
  • Without return, functions return None by default.
  • python
    def square(x):
        return x * x
    
    result = square(5)
    print(result)   # Output: 25

  • Functions can return multiple values as a tuple:
  • python
    def get_info():
        return "Amr", 30, "Netherlands"
    
    name, age, country = get_info()
    print(name, age, country)
    # Output: Amr 30 Netherlands


2. The Concept of Scope

Scope defines where a variable can be accessed in a program.

Python uses the LEGB rule:

  1. L (Local) → inside the current function.
  2. E (Enclosing) → in nested functions.
  3. G (Global) → variables defined at the top-level of a module.
  4. B (Built-in) → Python’s built-in names (like lenprint).

3. Local Variables

  • Variables defined inside a function are local to that function.
  • They exist only while the function runs.
  • python
    def greet():
        message = "Hello from inside!"  # local variable
        print(message)
    
    greet()
    # print(message)  # Error: name 'message' is not defined


4. Global Variables

  • Variables defined outside any function are global.
  • They can be read inside functions, but not modified directly unless declared global.
  • python
    counter = 0  # global variable
    
    def increase():
        global counter
        counter += 1
    
    increase()
    print(counter)  # Output: 1

Overusing `global` makes code harder to maintain. Prefer returning values instead.

5. Enclosing (Nonlocal) Scope

  • In nested functions, the inner function can access variables from the outer function, but cannot modify them directly.
  • Use nonlocal to modify them.

python
def outer():
    x = "outer value"
    def inner():
        nonlocal x
        x = "changed by inner"
    inner()
    print(x)

outer()  # Output: changed by inner


6. Best Practices

  • Keep variables local whenever possible.
  • Use return instead of global to pass results.
  • Avoid reusing variable names from outer scopes (reduces confusion).

Lambda Functions (Anonymous Functions)

Sometimes, you need a small, throwaway function for quick calculations. Instead of defining a full def function, Python lets you use lambda functions, also called anonymous functions.


1. What is a Lambda Function?

  • lambda function is a single-line function defined without a name.
  • Syntax:
  • lambda arguments: expression

  • Unlike def, it doesn’t need a name and can only contain a single expression (no statements, loops, or assignments inside).

2. Basic Example

python
# Normal function
def square(x):
    return x * x

# Lambda equivalent
square_lambda = lambda x: x * x

print(square_lambda(5))  # Output: 25


3. Multiple Parameters

Lambda functions can take multiple arguments.

python
add = lambda a, b: a + b
print(add(3, 7))   # Output: 10

compare = lambda x, y: x if x > y else y
print(compare(5, 8))  # Output: 8


4. Using Lambdas with Built-in Functions

Lambda functions are often used with higher-order functions like mapfilter, and sorted.

  • map() → applies a function to every element
  • python
    nums = [1, 2, 3, 4]
    squares = list(map(lambda x: x**2, nums))
    print(squares)  # [1, 4, 9, 16]

  • filter() → keeps only elements that meet a condition
  • python
    nums = [5, 10, 15, 20, 25]
    evens = list(filter(lambda n: n % 2 == 0, nums))
    print(evens)  # [10, 20]

  • sorted() → customize sorting
  • python
    people = [("Amr", 30), ("Sara", 25), ("Ali", 35)]
    sorted_people = sorted(people, key=lambda person: person[1])
    print(sorted_people)  # [('Sara', 25), ('Amr', 30), ('Ali', 35)]


5. Lambdas Inside Other Functions

Sometimes used as inline helpers.

python
def make_multiplier(n):
    return lambda x: x * n

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))  # Output: 10
print(triple(5))  # Output: 15


6. Limitations of Lambdas

  • Only one expression allowed (no multiple statements).
  • Harder to debug than normal functions.
  • Best used for short, simple logic, not complex operations.

Modules and the Import System

As your Python programs grow, it’s best to organize code into multiple files instead of keeping everything in one place. Python provides modules and the import system for this purpose.


1. What is a Module?

  • module is simply a Python file (.py) that contains functions, classes, or variables.
  • Modules help you reuse code and keep projects structured.

Example: Create a file called mymath.py

python
# mymath.py
def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

Now you can use this module in another Python file.


2. Importing a Module

Use the import keyword to bring in a module.

python
import mymath

print(mymath.add(2, 3))       # Output: 5
print(mymath.multiply(4, 5))  # Output: 20


3. Importing Specific Functions

You don’t have to import the entire module; you can import only what you need.

python
from mymath import add

print(add(10, 20))  # Output: 30

In this case, you can call `add()` directly without prefixing `mymath.`.

4. Using Aliases

You can give shorter names to modules or functions using as.

python
import mymath as mm
print(mm.add(5, 7))  # Output: 12

python
from mymath import multiply as mul
print(mul(3, 4))  # Output: 12


5. Standard Library Modules

Python comes with a large standard library.

Examples:

python
import math
print(math.sqrt(16))   # 4.0
print(math.pi)         # 3.141592653589793

import random
print(random.randint(1, 10))  # random number 1–10


You can import everything from a module, but this may cause name conflicts.

python
from math import *
print(sin(3.14))   # Works, but pollutes namespace


7. The Import Search Path

When you import a module, Python searches in:

  1. The current directory.
  2. The PYTHONPATH environment variable (if set).
  3. The standard library.
  4. Installed packages in site-packages.

python
import sys
print(sys.path)  # shows the list of directories Python searches


8. Packages (Folders with `__init__.py`)

  • package is a collection of modules inside a folder.
  • The folder must contain an __init__.py file (even if empty).

Example project structure:

python
myproject/
    mathutils/
        __init__.py
        add.py
        multiply.py

You can import like this:

python
from mathutils import add


9. Reloading Modules

Sometimes, during development, you want to reload a module after editing it.

python
import importlib
import mymath

# reload after changes
importlib.reload(mymath)


Built-in vs. User-defined Modules

Python’s modular system allows you to organize and reuse code. Modules can be either built-in (provided by Python itself) or user-defined (created by you or others).


1. Built-in Modules

  • These come with Python’s standard library.
  • No need to install or create them—just import and use.
  • They provide ready-to-use tools for math, random numbers, dates, file operations, networking, etc.

Examples:

python
import math
print(math.sqrt(25))      # 5.0
print(math.pi)            # 3.14159...

import random
print(random.randint(1, 10))  # random number between 1 and 10

import datetime
print(datetime.datetime.now())  # current date and time

Popular built-in modules:

  • os → interact with operating system (files, paths)
  • sys → system-specific parameters, Python path
  • math → mathematical functions
  • random → random numbers
  • datetime → dates and times
  • json → JSON encoding/decoding

2. User-defined Modules

  • user-defined module is any .py file you create yourself.
  • You can put functions, classes, and variables inside and import them into other programs.

Example: create a file mymath.py

python
# mymath.py
def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

Now use it in another file:

python
import mymath

print(mymath.add(2, 3))        # 5
print(mymath.multiply(4, 5))   # 20


3. Combining Both

You can freely use built-in modules alongside user-defined modules.

python
import math
import mymath

print(mymath.add(5, 10))    # user-defined
print(math.factorial(5))    # built-in


4. Why Use User-defined Modules?

  • Reusability → write once, use many times.
  • Organization → split large programs into smaller files.
  • Collaboration → different team members can work on different modules.

5. Key Differences

FeatureBuilt-in ModulesUser-defined Modules
SourcePart of Python’s standard libraryWritten by the programmer
AvailabilityAlways availableMust be created/imported manually
Examplesmathosrandomsysmymath.pyutils.py, project modules
PurposeProvide ready-made toolsSolve specific project needs