Skip to main content

1. Defining and Calling Functions

A function is a named block of code that performs a specific task. You define it once, then call it whenever you need that task done.

Function Syntax

Every function follows this pattern:
Key parts:
  • def: keyword that creates a function.
  • Function name followed by parentheses ().
  • Colon : to start the function body.
  • Indented code block (the function body).

Naming Functions

Follow these rules for function names (Snake Case):
  • Use lowercase letters.
  • Separate words with underscores.
  • Be descriptive about what the function does.

2. Parameters & Arguments

Parameters let you pass data into functions. Instead of hardcoding values, you make functions flexible to work with different inputs.
[!NOTE] The variables in the function definition are parameters. The actual values you pass when calling the function are arguments.

Positional Arguments

By default, Python matches the arguments you pass to the parameters in the definition by their position (order).

Default Values

You can give parameters default values to make them optional:
[!TIP] Always put parameters with default values at the end of the parameter list.

Keyword Arguments

You can call functions using parameter names for clarity, which allows you to pass them in any order:

3. Return Values

Use the return statement to send a value back from a function to the caller.
[!IMPORTANT] When Python encounters a return statement, it immediately exits the function. Any code after the return statement will not execute.

Returning Multiple Values

You can return multiple values from a function by separating them with commas. Python wraps them in a tuple automatically:

4. Flexible & Enforced Arguments

Python provides advanced options for handling dynamic numbers of arguments and enforcing calling styles.

Variable-Length Arguments (*args)

Prefix a parameter name with a single asterisk * to accept any number of positional arguments. Inside the function, args is a tuple.

Variable Keyword Arguments (**kwargs)

Prefix a parameter name with a double asterisk ** to accept any number of keyword arguments. Inside the function, kwargs is a dictionary.

Argument Ordering Rules

When mixing argument types, you must define them in this exact order:
  1. Standard positional arguments
  2. *args
  3. Keyword-only arguments
  4. **kwargs

Positional-Only (/) and Keyword-Only (*) Parameters

  • Positional-Only (/): Parameters before / must be passed positionally.
  • Keyword-Only (*): Parameters after * must be passed as keyword arguments.

Recursion

Recursion is a programming technique where a function calls itself directly or indirectly to solve a problem. It works by breaking down a complex problem into smaller, more manageable sub-problems of the same type. Every recursive function must have two main components:
  1. Base Case: The stopping condition that returns a value directly without making further recursive calls. Without a base case, the function will call itself infinitely, leading to a stack overflow.
  2. Recursive Step: The part of the function where it calls itself with a modified argument, moving the inputs closer to the base case.

Example 1: Factorial of a Number

The factorial of nn (written as n!n!) is the product of all positive integers less than or equal to nn. Mathematical definition:
  • 0!=10! = 1 (Base case)
  • n!=n×(n−1)!n! = n \times (n-1)! (Recursive step)

Example 2: Fibonacci Numbers

The Fibonacci sequence is a series of numbers where each number is the sum of the two preceding ones, usually starting with 0 and 1. Mathematical definition:
  • F(0)=0F(0) = 0, F(1)=1F(1) = 1 (Base cases)
  • F(n)=F(n−1)+F(n−2)F(n) = F(n-1) + F(n-2) (Recursive step)

Python Recursion Limit

To prevent stack overflow and out-of-memory crashes due to infinite loops, Python enforces a maximum recursion depth (typically set to 1000). You can check and modify this limit using the sys module:

5. Variable Scope & LEGB Rule

The scope of a variable refers to the region of a program where that variable is accessible.

LEGB Lookup Order

When you reference a variable name, Python searches for it in this strict order:
  1. Local: Inside the current function.
  2. Enclosing: Inside any enclosing (outer) functions.
  3. Global: Top-level variables defined in the module/file.
  4. Built-in: Python’s pre-loaded functions (like len, print).
If not found, Python raises a NameError.

The global and nonlocal Keywords

  • global: Declares that a variable inside a function refers to the global scope, allowing you to modify it.
  • nonlocal: Declares that a variable inside a nested function refers to the enclosing (outer) scope, allowing you to modify it.
Python treats functions as first-class objects, meaning they can be assigned to variables, passed as arguments, returned from other functions, and stored in collections.

Functions as First-Class Objects

Functions can be:
  • Assigned to variables.
  • Passed as arguments.
  • Returned from functions.
  • Accessed using attributes like __name__.

Lambda Functions

A lambda is a small anonymous function consisting of a single expression.
Lambdas are commonly used with functions like map(), filter(), and sorted().
Use lambda functions only for simple expressions. For complex logic, use a normal function.

Variable-Length Arguments (*args and **kwargs)

  • *args collects extra positional arguments into a tuple.
  • **kwargs collects extra keyword arguments into a dictionary.

Closures

A closure is a nested function that remembers variables from its enclosing scope.

Decorators

A decorator extends the behavior of a function without modifying its source code.

Decorators with Arguments

Use *args and **kwargs to support functions with any number of arguments.
Python supports Functional Programming (FP), where functions are treated as first-class objects. In this paradigm, functions can be passed as arguments, returned from other functions, and used to build reusable and declarative code. Functional programming in Python is mainly based on:
  • Declarative Programming
  • Higher-Order Functions

Generators

A generator is a special type of function that produces values one at a time instead of returning them all at once. It uses the yield keyword instead of return. Generators are memory-efficient because they generate values only when needed.

Advantages

  • Uses less memory for large datasets.
  • Produces values lazily (on demand).
  • Suitable for processing streams of data or large files.

Creating a Generator

Output

Using next()

A generator can be iterated manually using the next() function.
Output

Generator Expression

Similar to list comprehensions, Python supports generator expressions.
Output

Generator vs List


Declarative Programming

Declarative programming focuses on what you want to achieve, while imperative programming focuses on how to achieve it.

Imperative vs Declarative

Imperative (How):
Declarative (What):

Higher-Order Functions

A Higher-Order Function is a function that:
  • Accepts one or more functions as arguments.
  • Returns a function as its result.

Passing Functions as Arguments

Returning Functions

Real-World Example


Built-in Higher-Order Functions

map()

Applies a function to every element of an iterable.
You can also pass a normal function when the logic is more complex.

filter()

Returns only the elements that satisfy a condition.

reduce()

The reduce() function (from the functools module) combines all elements into a single value.

Functional Pipeline Example

The following example combines filter(), map(), and reduce() to compute the sum of squares of even numbers.
The same pipeline can also be written in a single statement.

Pythonic Alternative

Although map(), filter(), and reduce() are useful, Python often provides a simpler and more readable solution using comprehensions and built-in functions.
Use list comprehensions and built-in functions like sum() for better readability. Use map(), filter(), and reduce() when working with functional pipelines or callback-based APIs.

Practice & Exercises

To reinforce what you’ve learned in this section (defining functions, scopes, lambda expressions, closures, decorators, and functional programming), practice with these interactive notebooks:

Follow-Along Practice

Practice function definitions, positional/keyword arguments, variable scope rules, closures, wrapper decorators, and higher-order functions (map, filter, reduce).💻 VS Code | 🚀 Colab | 📥 Download

Practice Exercises

Test your knowledge with hands-on exercises including positional/keyword argument formatters, nonlocal scope counters, closure factories, execution timer decorators, and functional pipelines.💻 VS Code | 🚀 Colab | 📥 Download