Python Functions
A function is a reusable block of code that performs a specific task. A function runs only when it is called, making programs more organized, readable, and efficient.
Functions help you:
• Avoid repeating the same code
• Break large programs into smaller parts
• Make code easier to maintain and debug
Creating a Function
In Python, functions are created using the def keyword, followed by a function name and parentheses.
Syntax
Example: Simple Function
This defines a function named show_message. The code inside the function is indented to indicate it belongs to the function.
Calling a Function
A function does nothing until it is called. To call a function, write its name followed by parentheses.
Example
Calling a Function Multiple Times
Once defined, a function can be reused any number of times.
Example
Each function call executes the same block of code.
Function Naming Rules
Function names follow the same naming rules as variables:
• Must start with a letter or an underscore (_)
• Can include letters, numbers, and underscores
• Are case-sensitive
• Should be descriptive and meaningful
Examples of Valid Function Names
Why Use Functions?
Without functions, repeating the same logic leads to messy and inefficient code.
Example: Without Using Functions
Using Functions for Reusability
With functions, you write the logic once and reuse it easily.
Example: Using a Function
This approach reduces duplication and improves clarity.
Return Values in Functions
Functions can send data back using the return statement. Once return is executed, the function stops running.
Example: Function Returning a Value
Using Returned Values Directly
You can also use the returned value immediately.
Example
Functions Without a Return Statement
If a function does not explicitly return a value, Python returns None by default.
Example
Output:
None
The pass Statement in Functions
A function definition cannot be empty. If you want to define a function but implement it later, use the pass statement.
Example
Note: The pass keyword acts as a placeholder and avoids syntax errors during development.
Function Arguments in Python
Arguments are values that you pass to a function so it can work with data. They are written inside the parentheses when calling a function.
A function can accept one or more arguments, separated by commas.
Function with a Single Argument
Example
Here, username receives the value passed during each function call.
Parameters vs Arguments
Although often used interchangeably, there is a small technical difference:
• Parameter → The variable listed in the function definition
• Argument → The actual value passed when calling the function
Example
Required Number of Arguments
By default, the number of arguments passed must match the number of parameters.
Correct Usage
Incorrect Usage (Error)
Note: This will raise a TypeError.
Default Parameter Values
You can assign default values to parameters. If no argument is provided, the default value is used.
Example
Example: Default Country
Keyword Arguments
With keyword arguments, you specify the parameter name along with the value. This makes the order irrelevant.
Example
Positional Arguments
Arguments passed without keywords are called positional arguments. Their order matters.
Example
Order Change Affects Output
Mixing Positional and Keyword Arguments
You can combine both types, but positional arguments must come first.
Example
Passing Different Data Types as Arguments
Python functions can accept any data type.
Example: Passing a List
Example: Passing a Dictionary
Returning Values from Functions
Functions can send data back using the return statement.
Example
Returning Different Data Types
Example: Returning a List
Example: Returning a Tuple
Positional-Only Arguments
You can restrict parameters to accept only positional arguments using /.
Example
Keyword-Only Arguments
To allow only keyword arguments, use * before parameters.
Example
Combining Positional-Only and Keyword-Only Arguments
You can use both in the same function.
Example
Python *args and **kwargs
Normally, Python functions must be called with the exact number of arguments defined in the function.
But in real-world programs, you may not always know how many values will be passed.
Python solves this problem using:
• *args → for variable positional arguments
• **kwargs → for variable keyword arguments
These make functions flexible and reusable.
Arbitrary Positional Arguments (*args)
When you prefix a parameter with *, the function can accept any number of positional arguments.
All values passed this way are stored as a tuple inside the function.
Example: Using *args
Here, names becomes a tuple:
("Arjun", "Neha", "Rohan")
Understanding *args
Inside the function:
args is a tuple. You can loop through it or access elements by index.
Example
Using *args with Regular Parameters
Normal parameters must come before *args.
Example
message gets "Welcome". Remaining values go into users.
Practical Uses of *args
Example: Adding Multiple Numbers
Example: Finding the Largest Number
Arbitrary Keyword Arguments (**kwargs)
When you prefix a parameter with **, the function can accept any number of keyword arguments. Inside the function, these values are stored as a dictionary.
Example: Using **kwargs
Here, info becomes: {"name": "Riya", "age": 22, "city": "Jaipur"}
Understanding **kwargs
You can access values using dictionary keys.
Example
Using **kwargs with Regular Parameters
Normal parameters must appear before **kwargs.
Example
Combining *args and **kwargs
You can use both in the same function.
Correct Order:
1. Regular parameters
2. *args
3. **kwargs
Example
Unpacking Arguments
The * and ** symbols can also be used when calling functions to unpack values.
Example: Unpacking a List with *
This is the same as: multiply(2, 3, 4)
Example: Unpacking a Dictionary with **
This is the same as: welcome_user(first="Amit", last="Verma")
Variable Scope in Python
In Python, a variable’s scope determines where that variable can be accessed in a program. A variable is only available within the region where it is created. Understanding scope helps prevent unexpected errors and makes your code easier to manage.
Local Scope
A variable created inside a function belongs to the local scope. It can be accessed only within that function.
Example: Local Variable
Here, value exists only inside the calculate() function.
Accessing Local Variables in Nested Functions
A local variable can be accessed by inner functions defined inside the same outer function.
Example
The variable message is available to inner_function() because it is inside the enclosing scope.
Global Scope
A variable created outside any function belongs to the global scope. Global variables can be accessed from anywhere in the program.
Example: Global Variable
Both the function and the main program can access count.
Local vs Global Variables with Same Name
If a variable with the same name exists both inside and outside a function, Python treats them as separate variables.
Example
• Inside the function → local variable
• Outside the function → global variable
They do not affect each other.
The global Keyword
The global keyword allows a function to create or modify a global variable.
Example: Creating a Global Variable Inside a Function
Example: Modifying a Global Variable
Note: Without global, Python would create a new local variable instead of modifying the global one.
The nonlocal Keyword
The nonlocal keyword is used in nested functions to modify a variable from the enclosing (outer) function, but not the global scope.
Example
Here, nonlocal allows update_name() to change the value of username in the outer function.
The LEGB Rule
Python follows the LEGB rule when searching for a variable:
1. L – Local → Inside the current function
2. E – Enclosing → Inside outer functions
3. G – Global → At the top level of the module
4. B – Built-in → Python’s built-in names
Example: LEGB in Action
Output:
Inner: Local value
Outer: Enclosing value
Global: Global value
Python always picks the closest matching scope.
Python Decorators
A decorator allows you to add extra behavior to a function without modifying the function’s original code.
In simple terms:
• A decorator is a function
• It takes another function as input
• It returns a new enhanced function
Decorators are commonly used for logging, validation, authentication, caching, and formatting output.
Basic Decorator Concept
A decorator wraps a function and adds new behavior before or after calling it.
Example: Simple Decorator
How It Works:
• make_upper is the decorator
• greet is the function being decorated
• greet() is replaced with wrapper()
Using the Same Decorator on Multiple Functions
A decorator can be reused for many functions.
Example
Decorating Functions with Arguments
If the original function accepts arguments, the wrapper must accept them too.
Example
Using *args and **kwargs in Decorators
To make decorators flexible and compatible with any function signature, use *args and **kwargs.
Example
This allows the decorator to work with any number of arguments.
Decorators with Their Own Arguments
Sometimes, decorators themselves need parameters. This requires an extra level of wrapping.
Example: Decorator Factory
Using Multiple Decorators on a Function
You can stack multiple decorators on a single function. Decorators are applied from bottom to top.
Example
Execution Order:
• add_prefix runs first
• make_upper runs second
Preserving Function Metadata
Decorators replace the original function, which causes metadata like __name__ and __doc__ to be lost.
Example: Metadata Loss
Fixing Metadata with functools.wraps
Python provides functools.wraps to preserve original function information.
Example
Now the function keeps its original identity.
Lambda Functions in Python
A lambda function is a small, unnamed function created using the lambda keyword. It is mainly used for short operations where defining a full function would be unnecessary.
Lambda functions can accept any number of arguments, but they must contain only one expression, and that expression is automatically returned.
Syntax
lambda parameters : expression
The expression is evaluated and its result is returned.
Simple Lambda Example
Lambda with Multiple Arguments
Examples
Why Use Lambda Functions?
Lambda functions are most useful when you need a temporary, small function, especially inside another function. Consider a function that returns another function, where the multiplier is decided later.
Returning a Lambda from a Function
Example
When to Use Lambda Functions
Use lambda functions when:
• The function is simple and short
• The function is used only once
• You want cleaner and more readable code
Lambda with Built-in Functions
Lambda functions are often combined with built-in functions such as map(), filter(), and sorted().
Using Lambda with map()
The map() function applies a function to each item in a list.
Example: Square all numbers in a list
Using Lambda with filter()
The filter() function selects items that match a condition.
Example: Keep numbers greater than 10
Using Lambda with sorted()
The sorted() function can use a lambda to define custom sorting rules.
Example: Sort a list of dictionaries & Words
Recursion in Python
Recursion is a programming technique where a function calls itself to solve a problem by breaking it into smaller subproblems. Instead of using loops, recursion repeatedly applies the same logic until a stopping condition is reached.
Note: When used carefully, recursion can make code clean, readable, and mathematically elegant. However, incorrect recursion can lead to infinite function calls or excessive memory usage, so it must always be written with care.
Simple Recursion Example
Below is a recursive function that prints numbers from a given value down to 1.
Base Case and Recursive Case
Every recursive function must contain two essential parts:
1. Base Case: A condition that stops the recursion.
2. Recursive Case: The part where the function calls itself with a modified value.
Without a base case, the function would keep calling itself forever.
Example: Factorial Using Recursion
The factorial of a number is calculated as: n! = n × (n - 1)!
Why the Base Case Matters
The base case ensures that the recursion eventually stops. Always verify that your recursive calls move closer to this condition.
Fibonacci Sequence Using Recursion
The Fibonacci sequence is a well-known series where each number is the sum of the two previous numbers: 0, 1, 1, 2, 3, 5, 8, ...
Recursive Fibonacci Example
Find the 8th Fibonacci number:
Using Recursion with Lists
Recursion can also be applied to process collections such as lists by handling one element at a time.
Example: Sum of List Elements
Example: Find the Largest Number in a List
Recursion Depth Limit in Python
Python limits how deep recursion can go to prevent crashes caused by excessive memory usage. By default, this limit is usually around 1000 function calls.
Check the Current Recursion Limit
Increase the Recursion Limit (Use with Caution)
Generators in Python
Generators are special functions that produce values one at a time and pause their execution between each value. Instead of returning all results at once, they generate values on demand, making them highly memory-efficient.
When a generator function is called, it does not execute immediately. Instead, it returns a generator object, which can be iterated over like an iterator.
Basic Generator Example
A generator uses the yield keyword instead of return.
Here, values are produced one by one as the loop iterates.
The yield Keyword
The yield keyword pauses the function, saves its current state, and returns a value. Execution resumes from the same point the next time the generator is accessed.
Example: Generator That Counts Down
Unlike return, yield allows the function to continue execution later.
Why Generators Save Memory
Generators do not store all values in memory. They generate values only when needed, which is ideal for large datasets.
Example: Large Range Generator
Even though the range is large, memory usage stays low.
Using next() with Generators
You can manually retrieve values from a generator using next().
When no values remain, Python raises a StopIteration exception.
StopIteration Example
Generator Expressions
Generator expressions are similar to list comprehensions, but they use parentheses and do not create a list in memory.
List vs Generator Expression
Generator Expression with sum()
This computes the result without creating an intermediate list.
Fibonacci Sequence Using a Generator
Generators are perfect for infinite sequences like Fibonacci numbers.
This can run indefinitely without exhausting memory.
Advanced Generator Methods
Generators support additional methods for more control.
send() Method
The send() method sends a value into the generator.
close() Method
The close() method stops the generator’s execution.
