Python

Python Functions

Functions in Python are blocks of reusable code that perform a specific task. They help organize code, reduce repetition, and improve readability.

---

1. Defining a Function

Use the def keyword followed by the function name and parentheses. The body of the function is indented.

def greet():
            print("Hello, World!")

        greet()  # Calling the function
        
---

2. Function Parameters

Functions can accept inputs (parameters) to work with dynamic data.

def greet_user(name):
            print(f"Hello, {name}!")

        greet_user("Talha")
        

2.1 Default Parameters

def greet_user(name="Guest"):
            print(f"Hello, {name}!")

        greet_user()       # Hello, Guest!
        greet_user("Ali")  # Hello, Ali!
        

2.2 Keyword Arguments

You can specify arguments by name, in any order:

def describe_pet(name, species):
            print(f"{name} is a {species}")

        describe_pet(species="dog", name="Buddy")
        

2.3 Variable-length Arguments

Python allows you to define functions that can accept a variable number of arguments. This is useful when you don't know in advance how many arguments might be passed.

  • *args → used to pass multiple positional arguments as a tuple.
  • **kwargs → used to pass multiple keyword arguments as a dictionary.

Using *args (Positional Arguments)

def sum_all(*numbers):
            """
            Accepts any number of numbers and returns their sum.
            *numbers collects all positional arguments as a tuple.
            """
            print("Numbers received:", numbers)
            return sum(numbers)

        # Calling with different numbers of arguments
        print(sum_all(1, 2, 3))        # Output: Numbers received: (1, 2, 3) → 6
        print(sum_all(5, 10, 15, 20))  # Output: Numbers received: (5, 10, 15, 20) → 50
        

Using **kwargs (Keyword Arguments)

def print_info(**info):
            """
            Accepts any number of keyword arguments.
            **info collects them into a dictionary.
            """
            print("Info dictionary:", info)
            for key, value in info.items():
                print(f"{key}: {value}")

        # Calling with different keyword arguments
        print_info(name="Talha", age=25, city="Karachi")
        # Output:
        # Info dictionary: {'name': 'Talha', 'age': 25, 'city': 'Karachi'}
        # name: Talha
        # age: 25
        # city: Karachi
        

Combining *args and **kwargs

You can combine both to accept arbitrary positional and keyword arguments:

def display(*args, **kwargs):
            print("Positional args:", args)
            print("Keyword args:", kwargs)

        display(1, 2, 3, name="Talha", age=25)
        # Output:
        # Positional args: (1, 2, 3)
        # Keyword args: {'name': 'Talha', 'age': 25}
        

Unpacking Arguments

You can also unpack lists/tuples and dictionaries into functions using * and **:

nums = [1, 2, 3, 4]
        print(sum_all(*nums))  # Equivalent to sum_all(1,2,3,4) → 10

        info = {"name": "Talha", "age": 25}
        print_info(**info)
        # Equivalent to print_info(name="Talha", age=25)
        

Key Points:

  • *args must appear before **kwargs in the function definition.
  • You can combine regular parameters with *args and **kwargs:
def example(a, b, *args, **kwargs):
            print(a, b)
            print("Additional args:", args)
            print("Additional kwargs:", kwargs)

        example(1, 2, 3, 4, name="Talha", city="Karachi")
        # Output:
        # 1 2
        # Additional args: (3, 4)
        # Additional kwargs: {'name': 'Talha', 'city': 'Karachi'}
        

3. Return Values

Functions can return values using the return keyword. Without return, a Python function returns None by default. You can also optionally use type hints to indicate the expected return type.

Basic Example

def add(a: int, b: int) -> int:
            """Returns the sum of two numbers"""
            return a + b

        result = add(5, 10)
        print(result)  # Output: 15
        

Returning Multiple Values

You can return multiple values as a tuple, and type hints can specify this:

from typing import Tuple

        def get_user_info() -> Tuple[str, int]:
            name = "Talha"
            age = 25
            return name, age

        user_name, user_age = get_user_info()
        print(user_name)  # Talha
        print(user_age)   # 25
        

Returning Collections

Functions can return lists, tuples, dictionaries, etc. Type hints make this explicit:

from typing import List, Tuple

        # Returning a list of integers
        def get_numbers() -> List[int]:
            return [1, 2, 3, 4, 5]

        numbers = get_numbers()
        print(numbers)  # [1, 2, 3, 4, 5]

        # Returning a tuple of integers
        def get_numbers_tuple() -> Tuple[int, ...]:
            return (1, 2, 3, 4, 5)

        numbers_tuple = get_numbers_tuple()
        print(numbers_tuple)  # (1, 2, 3, 4, 5)
        

Key Notes

  • Functions without return return None by default.
  • You can return any Python object: numbers, strings, lists, dictionaries, tuples, or even functions.
  • Returning multiple values actually returns a tuple.
  • Type hints (-> int, -> List[int], etc.) improve readability and help IDEs, but are not enforced at runtime.

4. Lambda Functions (Anonymous Functions)

Lambda functions are small, one-line anonymous functions that are often used for short tasks. They are defined using the lambda keyword.

Basic Example

square = lambda x: x**2
        print(square(5))  # 25
        

Lambda with Multiple Arguments

add = lambda a, b: a + b
        print(add(3, 4))  # 7
        

Lambda in Functions

Can be used as a quick callback or in functions like map(), filter(), and sorted():

numbers = [1, 2, 3, 4, 5]

        squared = list(map(lambda x: x**2, numbers))
        print(squared)  # [1, 4, 9, 16, 25]

        even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
        print(even_numbers)  # [2, 4]
        

Key Notes

  • Lambda functions are limited to a single expression.
  • They are useful for short, throwaway functions.
  • Cannot contain statements, only expressions.
---

5. Scope of Variables in Functions

Python uses the LEGB rule to determine variable scope:

  • Local – inside the function
  • Enclosing – inside an enclosing function
  • Global – outside all functions
  • Built-in – Python built-in names

Example of Local vs Global

x = 10  # Global variable

        def func():
            x = 5  # Local variable
            print("Inside function:", x)

        func()           # Inside function: 5
        print("Outside function:", x)  # Outside function: 10
        

Using the Global Keyword

Modify a global variable inside a function:

x = 10

        def update():
            global x
            x = 20

        update()
        print(x)  # 20
        

Enclosing Scope Example (Nested Functions)

def outer():
            x = "Outer Variable"
            
            def inner():
                print("Inner sees:", x)  # Accesses enclosing scope variable
            inner()

        outer()  # Inner sees: Outer Variable
        

Key Notes

  • Local variables exist only inside their function.
  • Global variables can be accessed inside functions but need global to modify.
  • Enclosing variables are available to nested functions (closures).
---

6. Nested Functions

Functions can be defined inside other functions. The inner function can access variables from the enclosing function, creating closures.

Basic Example

def outer():
            x = "Hello"
            
            def inner():
                print(x)  # Accesses outer function variable
            
            inner()

        outer()  # Hello
        

Returning a Nested Function

Nested functions can also be returned from their enclosing function:

def outer_func(msg):
            def inner_func():
                print(msg)
            return inner_func

        func = outer_func("Hi Talha!")
        func()  # Hi Talha!
        

Use Cases of Nested Functions

  • Encapsulation – keeping helper functions private.
  • Closures – preserving state between function calls.
  • Decorators – modifying functions dynamically.
---

7. Best Practices

  • Use meaningful function names
  • Keep functions small and focused on one task
  • Use docstrings to describe function behavior
  • Use default arguments and keyword arguments for clarity
---

8. Interview Questions

Q1. What is a function in Python?

A block of code that performs a specific task and can be reused.

Q2. Difference between local and global variable in a function?

Local variables exist inside a function, global variables exist outside and can be accessed anywhere.

Q3. What is a lambda function?

A small anonymous function defined using the lambda keyword, usually for short operations.

Q4. Can functions return multiple values?

Yes, by returning a tuple: return a, b, c