Swiftorial Logo
Home
Swift Lessons
Tutorials
Learn More
Career
Resources

Python FAQ: Top Questions

13. Explain `*args` and `**kwargs` in Python.

In Python, `*args` and `**kwargs` are special syntaxes used in function definitions to allow a function to accept an **arbitrary (variable) number of arguments**. They are powerful tools for creating flexible functions that can handle varying inputs without needing to define a fixed number of parameters. The `*` and `**` are the actual operators; `args` and `kwargs` are just conventional names for the variables that hold these arguments (you can use other names like `*positional_arguments` or `**keyword_arguments`, but `*args` and `**kwargs` are strongly recommended for readability).

1. `*args` (Arbitrary Positional Arguments):

  • Purpose: Used to pass a non-keyword, variable-length argument list to a function. It allows you to pass an arbitrary number of positional arguments.
  • Behavior: When used in a function definition, `*args` collects all excess positional arguments passed during the function call into a **tuple**.
  • Usage in Function Definition: `def my_function(*args):`
  • Usage in Function Call (Unpacking): The `*` operator can also be used during a function call to "unpack" an iterable (like a list or tuple) into separate positional arguments.

2. `**kwargs` (Arbitrary Keyword Arguments):

  • Purpose: Used to pass a keyword, variable-length argument list to a function. It allows you to pass an arbitrary number of keyword arguments.
  • Behavior: When used in a function definition, `**kwargs` collects all excess keyword arguments (arguments passed with a `key=value` syntax) into a **dictionary**. The keys of the dictionary will be the argument names, and the values will be their corresponding argument values.
  • Usage in Function Definition: `def my_function(**kwargs):`
  • Usage in Function Call (Unpacking): The `**` operator can also be used during a function call to "unpack" a dictionary into separate keyword arguments.

Order of Arguments in Function Definition:

When defining a function that uses a combination of regular arguments, `*args`, and `**kwargs`, the order is crucial:

  1. Standard positional arguments.
  2. Default arguments.
  3. `*args`.
  4. Keyword-only arguments.
  5. `**kwargs`.

A common pattern: `def func(arg1, arg2='default', *args, kw_only_arg, **kwargs):`

These constructs are highly useful for creating flexible APIs, wrapper functions, and functions that need to accept inputs where the exact number or names of parameters might vary.


# --- Example 1: Using *args for arbitrary positional arguments ---

def sum_all_numbers(*numbers):
    """
    Calculates the sum of an arbitrary number of numbers.
    'numbers' will be a tuple containing all positional arguments passed.
    """
    print(f"Type of 'numbers': {type(numbers)}")
    total = 0
    for num in numbers:
        total += num
    return total

print("--- Using *args ---")
print(f"Sum of 1, 2, 3: {sum_all_numbers(1, 2, 3)}")
print(f"Sum of 10, 20: {sum_all_numbers(10, 20)}")
print(f"Sum of no numbers: {sum_all_numbers()}") # Returns 0

# --- Example 1.1: Unpacking a list/tuple with * in function call ---
print("\n--- Unpacking with * ---")
my_list_of_nums = [4, 5, 6, 7]
print(f"Sum of numbers from list {my_list_of_nums}: {sum_all_numbers(*my_list_of_nums)}") # Unpacks list into individual arguments

my_tuple_of_nums = (8, 9)
print(f"Sum of numbers from tuple {my_tuple_of_nums}: {sum_all_numbers(*my_tuple_of_nums)}")


# --- Example 2: Using **kwargs for arbitrary keyword arguments ---

def create_user_profile(**details):
    """
    Creates a user profile dictionary from arbitrary keyword arguments.
    'details' will be a dictionary containing all keyword arguments.
    """
    print(f"Type of 'details': {type(details)}")
    print("User Profile:")
    for key, value in details.items():
        print(f"  {key.replace('_', ' ').title()}: {value}")
    return details

print("\n--- Using **kwargs ---")
create_user_profile(name="Alice", age=30, city="New York")
print("-" * 20)
create_user_profile(username="Bob", email="bob@example.com", is_active=True, last_login="2023-01-15")
print("-" * 20)
create_user_profile() # No keyword arguments


# --- Example 2.1: Unpacking a dictionary with ** in function call ---
print("\n--- Unpacking with ** ---")
user_info = {"first_name": "Charlie", "last_name": "Brown", "occupation": "Student"}
create_user_profile(**user_info) # Unpacks dict into keyword arguments

more_info = {"status": "Active", "member_since": "2020-05-01"}
# You can combine direct keywords with unpacked dicts
create_user_profile(id=101, **user_info, **more_info)


# --- Example 3: Combining standard arguments, *args, and **kwargs ---

def process_data(category, *items, debug=False, **options):
    """
    Processes data based on category, a list of items,
    a debug flag, and arbitrary options.
    """
    print(f"\n--- Processing Data for Category: {category} ---")
    print(f"Items to process (from *items): {items}") # items is a tuple
    print(f"Debug mode: {debug}")
    if options:
        print(f"Additional options (from **options): {options}") # options is a dictionary
    else:
        print("No additional options.")
    print("---------------------------------")

process_data("Fruits", "Apple", "Banana", "Orange")
process_data("Vehicles", "Car", "Bike", "Truck", debug=True, color="red", wheels=4)
process_data("Animals", debug=False, species="mammal")
process_data("Numbers") # No items, no options
        

Explanation of the Example Code:

  • **`sum_all_numbers(*numbers)`:**
    • When called with `sum_all_numbers(1, 2, 3)`, the `*numbers` parameter collects `(1, 2, 3)` into a tuple named `numbers`. The loop then iterates over this tuple to calculate the sum.
    • `sum_all_numbers()` (no arguments) results in an empty tuple `()`, and the sum is correctly 0.
    • The unpacking example `sum_all_numbers(*my_list_of_nums)` demonstrates how the `*` operator, when used during a function call, takes an iterable (`my_list_of_nums`) and unpacks its elements as separate positional arguments to the function. This is equivalent to calling `sum_all_numbers(4, 5, 6, 7)`.
  • **`create_user_profile(**details)`:**
    • When called with `create_user_profile(name="Alice", age=30)`, the `**details` parameter collects `{"name": "Alice", "age": 30}` into a dictionary named `details`. The function then iterates over this dictionary to print the profile information.
    • The unpacking example `create_user_profile(**user_info)` shows how the `**` operator, when used during a function call, takes a dictionary (`user_info`) and unpacks its key-value pairs as separate keyword arguments to the function. This is equivalent to calling `create_user_profile(first_name="Charlie", last_name="Brown", occupation="Student")`.
    • You can also combine multiple unpacked dictionaries or mix them with direct keyword arguments, as seen in `create_user_profile(id=101, **user_info, **more_info)`.
  • **`process_data(category, *items, debug=False, **options)`:**
    • This function demonstrates the typical order and usage of standard arguments (`category`), `*args` (`items`), a default argument (`debug`), and `**kwargs` (`options`).
    • When `process_data("Fruits", "Apple", "Banana", "Orange")` is called:
      • `"Fruits"` is assigned to `category`.
      • `("Apple", "Banana", "Orange")` is collected into the `items` tuple.
      • `debug` defaults to `False`.
      • `options` is an empty dictionary `{}` as no keyword arguments are passed.
    • When `process_data("Vehicles", "Car", "Bike", "Truck", debug=True, color="red", wheels=4)` is called:
      • `"Vehicles"` is assigned to `category`.
      • `("Car", "Bike", "Truck")` is collected into the `items` tuple.
      • `debug` is explicitly set to `True`.
      • `{"color": "red", "wheels": 4}` is collected into the `options` dictionary.

The `*args` and `**kwargs` syntaxes provide immense flexibility, enabling the creation of highly adaptable functions for various programming scenarios, from simple utility functions to complex API endpoints.