Understanding Function Parameters, Namespaces, and Scope in Python

Variable-Length Arguments

*args Parameter

When defining a function, you can use *args to collect any number of positional arguments into a tuple:

def process_values(*values):
    # Converts excess positional arguments into a tuple
    for value in values:
        print(value)

**kwargs Parameter

Similarly, **kwargs collects any number of keyword arguments into a dictionary:

def process_data(**data):
    # Converts excess keyword arguments into a dictionary
    for key, value in data.items():
        print(f"{key}: {value}")

Unpacking Arguments

* Operator with Arguments

The * operator can unpack iterables into positional arguments:

numbers = [1, 2, 3, 4]
def add(a, b, c, d):
    return a + b + c + d
    
result = add(*numbers)  # Unpacks the list into individual arguments

** Operator with Arguments

The ** operator unpacks dictionaries into keyword arguments:

person = {"name": "Alice", "age": 30}
def introduce(name, age):
    print(f"{name} is {age} years old")
    
introduce(**person)  # Unpacks the dictionary into keyword arguments

Combining *args and **kwargs

You can use both to accept any combination of arguments:

def flexible_function(*args, **kwargs):
    print("Positional arguments:", args)
    print("Keyword arguments:", kwargs)
    
# Example usage
sample_list = [1, 2, 3]
sample_dict = {"a": 100, "b": 200}
flexible_function(*sample_list, **sample_dict)

Function Objects

Everything in Python is an Object

In Python, functions are first-class objects, meaning they can be treated like any other object.

Using Functions as Objects

1. Referencing Functions

def greet():
    print("Hello, World!")
    
# Assigning function to another variable
say_hello = greet
say_hello()  # Calls the same function as greet()

2. Functions as Container Elements

def add(a, b):
    return a + b
    
def subtract(a, b):
    return a - b
    
# Storing functions in a list
operations = [add, subtract]
result = operations[0](5, 3)  # Calls add(5, 3)
print(result)  # Output: 8

3. Functions as Parameters

def apply_operation(func, x, y):
    return func(x, y)
    
def multiply(a, b):
    return a * b
    
# Passing function as argument
product = apply_operation(multiply, 4, 5)
print(product)  # Output: 20

4. Functions as Return Values

def get_multiplier(factor):
    def multiplier(number):
        return number * factor
    return multiplier
    
# Returning a function
double = get_multiplier(2)
print(double(10))  # Output: 20

Function Nesting

Introduction to Nesting

Just as we can nest loops, we can also nest functions within each other.

# Example of nested loops (multiplication table)
for i in range(1, 10):
    for j in range(1, i + 1):
        print(f"{j}×{i}={j*i}", end=" ")
    print()

Nested Functions

Functions can be defined inside other functions:

def outer_function():
    print("This is the outer function")
    
    def inner_function():
        print("This is the inner function")
    
    inner_function()  # This works
    return inner_function

# Calling the outer function
inner_func = outer_function()
# inner_function()  # This would cause an error - inner function is not accessible here

Namespaces and Scope

Understanding Namespaces

A namespace is a mapping from names to objects. Python has several types of namespaces:

Built-in Namespace

Contains built-in functions and types like len(), str(), int(), etc. This namespace is created when the Python interpreter starts and is destroyed when it closes.

Global Namespace

Contains names defined at the module level. It's created when the module is loaded and lasts until the module is unloaded.

Local Namespace

Contains names defined within a function. It's created when the function is called and destroyed when the function returns.

Namespace Execution Order

  1. Built-in namespace: Created when the Python interpreter starts
  2. Global namespace: Created when the module is loaded
  3. Local namespace: Created when a function is called

Namespace Search Order

When looking up a name, Python searches in this order:

  1. Local namespace
  2. Global namespace
  3. Built-in namespace

If a name isn't found in any namespace, a NameError is raised.

x = 10  # Global variable

def display_x():
    x = 20  # Local variable
    print(x)  # Prints local x

print(x)  # Prints global x
display_x()

Scope

Global Scope

Names in the global and built-in namespaces have global scope. They are accessible throughout the module.

Local Scope

Names in a local namespace have local scope. They are only accessible within the function where they are defined.

def outer():
    x = "outer"
    
    def middle():
        x = "middle"
        
        def inner():
            x = "inner"
            print(x)  # Prints "inner"
        
        inner()
    
    middle()

outer()

Important Note on Scope

The scope of a variable is determined at function definition time, not at call time:

x = 10

def func1():
    x = 20
    print(x)  # Prints 20

def func2():
    x = 30
    func1()  # This still prints 20, not 30
    print("func2's x:", x)  # Prints 30

func2()

Modifying Scope with Keywords

global Keyword

The global keyword allows you to modify a global variable from within a function:

counter = 0

def increment():
    global counter
    counter += 1

print(counter)  # Output: 0
increment()
print(counter)  # Output: 1

nonlocal Keyword

The nonlocal keyword allows you to modify a variable from an enclosing (but non-global) scope:

def outer():
    x = "outer"
    
    def middle():
        x = "middle"
        
        def inner():
            nonlocal x
            x = "modified"
            print("Inner function:", x)
        
        print("Before inner:", x)
        inner()
        print("After inner:", x)
    
    middle()

outer()

Important Notes on Modifying Scope

  1. You can modify mutable global objects (like lists) without using the global keyword.
  2. You must use the global keyword to modify immutable global objects (like numbers, strings, tuples).
# Example with mutable object
items = [1, 2, 3]

def modify_list():
    items.append(4)  # No need for global keyword

modify_list()
print(items)  # Output: [1, 2, 3, 4]

# Example with immutable object
number = 10

def modify_number():
    global number  # Required for immutable objects
    number = 20

modify_number()
print(number)  # Output: 20

Additional Example: Default Mutable Arguments

# Common pitfall with mutable default arguments
def append_to_list(item, target_list=[]):
    target_list.append(item)
    return target_list

# This accumulates items across calls
print(append_to_list(1))  # Output: [1]
print(append_to_list(2))  # Output: [1, 2]

# Better approach
def append_to_list_fixed(item, target_list=None):
    if target_list is None:
        target_list = []
    target_list.append(item)
    return target_list

# This works as expected
print(append_to_list_fixed(1))  # Output: [1]
print(append_to_list_fixed(2))  # Output: [2]

Tags: python function-parameters namespaces scope variable-length-arguments

Posted on Sat, 25 Jul 2026 16:14:43 +0000 by Tentious