The Ultimate Python Cheat Sheet for *args and **kwargs


Python

Author: Bashir Alam
Reviewer: Deepak Prasad

Introduction

Welcome to this comprehensive guide on *args and **kwargs in Python! Whether you're a complete beginner or an experienced Pythonista, understanding these two special symbols is crucial for writing flexible and dynamic functions in Python. They are particularly useful for handling a variable number of arguments, providing a way to make your functions more modular and adaptable to different requirements.

 

Brief Explanation of *args

In Python, *args is used in function definitions to pass a variable number of non-keyword arguments. Simply put, it allows you to handle more arguments than the number of formal arguments that you initially defined. The arguments are then accessible as a tuple within the function body. This feature gives you the freedom to work with dynamic data and simplifies code structure, especially when you're unsure about the number of arguments your function might need.

def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3, 4))  # Output will be 10

 

Brief Explanation of **kwargs

**kwargs serves a similar purpose but is used for passing a variable number of keyword arguments. These keyword arguments are then accessible within the function as a dictionary. This is useful when you want to handle named arguments dynamically without explicitly defining them in the function signature.

def print_data(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_data(name="John", age=30)  # Output will be "name: John" and "age: 30"

Both *args and **kwargs can be incredibly useful for creating flexible APIs, making your functions more robust, or even for quickly prototyping functions where you're uncertain about the inputs you might need.

 

Basics of Function Arguments in Python

Understanding the different types of arguments is foundational to grasping the concept and utility of *args and **kwargs. In Python, there are primarily two types of arguments that a function can accept: positional arguments and keyword arguments.

 

1. Positional Arguments

Definition

Positional arguments are the most common and straightforward type of function arguments. When calling a function, these arguments need to be passed in a specific positional order. The function expects the arguments to be present in the defined sequence, and they get assigned to the corresponding parameters in the function definition.

Examples

Here is a simple function to calculate the area of a rectangle:

def calculate_area(length, breadth):
    return length * breadth

# When calling the function, '10' will be assigned to 'length' and '5' to 'breadth'
area = calculate_area(10, 5)  
print(area)  # Output: 50

In this example, 10 and 5 are positional arguments. The value 10 is assigned to the parameter length, and 5 is assigned to breadth based on their position in the function call.

 

2. Keyword Arguments

Definition

Keyword arguments, on the other hand, are arguments that are passed to a function by explicitly naming each one along with its value. This way, you can set the values out of order because the Python interpreter will use the names to match values to parameters.

Examples

Continuing with the area example:

# Calling function using keyword arguments
area = calculate_area(breadth=5, length=10)
print(area)  # Output: 50

Here, breadth=5 and length=10 are keyword arguments. You don't have to worry about the order in which you pass them; the function knows which is which by their names.

Both positional and keyword arguments can be used in the same function call, but positional arguments must appear before keyword arguments:

# Valid
area = calculate_area(10, breadth=5)

# Invalid
# SyntaxError: positional argument follows keyword argument
area = calculate_area(breadth=5, 10)

 

Unpacking Sequences: *args

What is *args?

*args is a way in Python to pass a variable-length list of positional arguments to a function. It allows you to pass any number of positional arguments to the function, which will then be packed into a tuple. This is extremely useful when you want your function to be flexible in terms of the number of arguments it can accept.

The Concept of Argument Unpacking

The term "unpacking" in this context refers to the action of taking elements from an iterable (like a list or tuple) and "unpacking" them into variables. With *args, Python does this automatically for you, bundling any extra positional arguments into a tuple that the function can then refer to.

The Role of the Asterisk (*)

The asterisk (*) plays the role of a "packer" here. It tells Python to collect all the remaining positional arguments and pack them into a new tuple. The word following the asterisk (args) is just a variable name, and you could technically use any other name you prefer. However, args is conventionally used for readability and ease of understanding.

 

How to Use *args?

Basic Usage and Syntax

To use *args in your function, you simply add *args as one of your function's parameters. Inside the function, args will be a tuple containing all the extra positional arguments.

# Defining a function that uses *args
def my_function(arg1, arg2, *args):
    print(arg1, arg2)
    print(args)

# Calling the function
my_function(1, 2, 3, 4, 5)  # Output: 1 2 \n (3, 4, 5)

Here are some practical examples to show the utility of *args.

Summing all arguments

def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3, 4))  # Output will be 10

Finding the average of all arguments

def average(*args):
    return sum(args) / len(args)

print(average(10, 20, 30, 40))  # Output will be 25.0

Collecting extra information

def user_info(username, email, *tags):
    print(f"Username: {username}")
    print(f"Email: {email}")
    print(f"Tags: {tags}")

user_info("john_doe", "john@example.com", "developer", "python")

 

When to Use *args?

The utility of *args lies in its ability to make your functions more flexible, accommodating a varied number of positional arguments. However, like any tool, it has its place and isn't always the best option. Let's delve into scenarios where it is beneficial and its limitations.

Scenarios Where *args is Beneficial

Dynamic Number of Arguments: When you're unsure about the number of positional arguments that will be passed to a function.

def print_all(*args):
    for arg in args:
        print(arg)

Forwarding Arguments: When you are wrapping another function or method and want to forward arguments to it.

def log_and_call(func, *args):
    print(f"Calling function: {func.__name__}")
    return func(*args)

Code Reusability: Functions that accept *args can be incredibly reusable because they can work with a range of argument counts.

def multiply(*args):
    result = 1
    for arg in args:
        result *= arg
    return result

Backward Compatibility: If you're extending an existing function and don't want to break code that uses that function, *args can help you accept new arguments without altering the function's original call signature.

Ease of Refactoring: When the list of arguments for a function is likely to change during development or refactoring, using *args can make the process easier.

Limitations of *args

Readability: Too many positional arguments can make code less readable and harder to debug. It may become challenging to understand what each argument is supposed to represent.

Type Safety: Since *args packs arguments into a tuple, you lose the type information that you might get from explicitly named parameters.

Mandatory Positional Arguments: If a function requires some arguments to be passed in all cases, *args can make it easy to accidentally omit them.

Multiple *args: Python doesn't allow more than one *args in function definitions, limiting its flexibility.

Keyword-Only Arguments: If a function uses *args, all subsequent parameters must be keyword-only arguments.

def func(arg1, *args, kwarg1="x"):
    pass

Ordering Constraints: *args must be placed before any keyword-only arguments but after any positional or default arguments in the function definition.

 

Unpacking Dictionaries: **kwargs

What is **kwargs?

The **kwargs syntax in Python allows you to pass a variable-length list of keyword arguments to a function. Here, kwargs stands for "keyword arguments," and the double asterisks ** act as a "packer" that gathers the remaining keyword arguments into a dictionary. This dictionary contains key-value pairs that you can easily manipulate within the function. Like *args, this provides flexibility and can make your functions more robust.

Explanation of Keyword Argument Unpacking

The term "unpacking" here signifies that the double asterisks ** will take the keyword arguments passed to the function and pack them into a dictionary. Inside the function, you can then iterate over this dictionary, providing flexibility for handling optional parameters or forward-compatible features.

The Role of Double Asterisks (**)

The double asterisks ** signify that any subsequent named arguments in the function call should be gathered into a dictionary. The dictionary will have the argument names as keys and their corresponding values as dictionary values. The word after the double asterisks (kwargs) is not a keyword; it's simply a variable name. Although kwargs is the conventional name used for this parameter, you can technically use any name you prefer.

Basic Usage and Syntax

Here's how to define and use a function with **kwargs:

# Defining a function that uses **kwargs
def user_profile(username, **kwargs):
    print(f"Username: {username}")
    for key, value in kwargs.items():
        print(f"{key}: {value}")

# Calling the function
user_profile("john_doe", email="john@example.com", age=30)

Output:

Username: john_doe
email: john@example.com
age: 30

Practical Examples

Optional Configuration Settings

def connect_to_database(**kwargs):
    host = kwargs.get("host", "localhost")
    port = kwargs.get("port", 3306)
    user = kwargs.get("user")
    # ... connect to the database

Building URL Query Strings

def build_url(base, **kwargs):
    query_params = "&".join(f"{k}={v}" for k, v in kwargs.items())
    return f"{base}?{query_params}"

 

How to Use **kwargs?

The **kwargs syntax allows you to pass a varying number of keyword arguments to a function. In the function definition, you use **kwargs to collect additional keyword arguments into a dictionary.

Basic Usage and Syntax

Here's the basic syntax:

def my_function(arg1, arg2, **kwargs):
    print("arg1:", arg1)
    print("arg2:", arg2)
    print("kwargs:", kwargs)

Calling the function:

my_function(1, 2, arg3=3, arg4=4)

Output:

arg1: 1
arg2: 2
kwargs: {'arg3': 3, 'arg4': 4}

Practical Examples

Dynamic Function Configuration: Use **kwargs to pass configuration options for functions or classes.

def initialize_engine(**kwargs):
    protocol = kwargs.get("protocol", "http")
    host = kwargs.get("host", "localhost")
    # initialize the engine

Data Serialization: Pass additional options for data serialization functions.

def serialize_to_json(data, **kwargs):
    indent_level = kwargs.get("indent", 4)
    # serialize data to JSON

Function Wrappers and Decorators: Use **kwargs to make your wrappers and decorators more flexible.

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Something is happening before the function is called.")
        func(*args, **kwargs)
    return wrapper

 

When to Use **kwargs?

Scenarios Where **kwargs is Useful

  • Extensibility: If you are building libraries or frameworks and want to offer an easy way to extend functions or methods.
  • Forward Compatibility: If you expect that a function will need additional arguments in future versions, you can use **kwargs to capture those without altering existing calls.
  • Variable Argument Length: When you want to allow a variable number of keyword arguments, especially when wrapping or proxying another function.
  • Optional Parameters: When a function has many parameters that are mostly optional, **kwargs makes the function easier to read and use.

Limitations of **kwargs

  • Readability: Too many keyword arguments can make the function call and function definition hard to understand.
  • Type Safety: **kwargs is flexible, but that means you lose the type-checking that you might get with explicitly declared keyword arguments.
  • Error Handling: It is easier to make a typo in a keyword when using **kwargs, and this will silently produce unwanted behavior, as **kwargs would accept it as a valid argument.
  • Documentation: When using **kwargs, auto-generating documentation can be less informative, as it won't list all the available options explicitly.
  • Positional Arguments: **kwargs can only capture keyword arguments, not positional arguments.

 

Comparing *args and **kwargs

Both *args and **kwargs are techniques in Python for passing a variable number of arguments to a function. They allow for more flexible and dynamic code but are used for different types of arguments.

Here's a quick table comparison to highlight the similarities and differences:

Feature *args **kwargs
Syntax in Function Definition def func(*args): def func(**kwargs):
Argument Type Positional Keyword
Data Type in Function Tuple Dictionary
Usage Example in Function Call func(1, 2, 3) func(a=1, b=2, c=3)
Inside Function args = (1, 2, 3) kwargs = {'a': 1, 'b': 2, 'c': 3}
Use-Case Pass any number of positional arguments Pass any number of keyword arguments
Unpacking Operator Single Asterisk * Double Asterisk **
Common Scenarios Mathematical operations, string formatting, function proxies Optional parameters, configuration settings, function wrappers
Type Safety Limited Limited
Readability Can decrease for many arguments Can decrease for many arguments
Error Handling Can handle through tuple unpacking Can handle through dictionary methods like .get()
Flexibility High High

When to Use Each

  • *args: When you have a function that should accept any number of positional arguments, *args is the syntax you'd use. It's common in functions that require many optional positional parameters.
  • **kwargs: When you have a function that could accept any number of keyword (named) arguments, **kwargs is the way to go. It is commonly used when you're unsure how many keyword arguments will be passed or when you're developing functions that are extensible or part of a framework.

 

Use Cases for *args and **kwargs

Use Cases for *args

Mathematical Operations: If you want to create a function that can take any number of numerical arguments and then perform an operation on them (e.g., sum), *args is very useful.

def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3, 4))  # Output: 10

String Formatting and Concatenation : You might want to concatenate or format multiple strings together. With *args, you can pass any number of strings.

def concatenate(*args):
    return "".join(args)

print(concatenate("Hello", " ", "World"))  # Output: "Hello World"

Handling Multiple Iterables in Functions like zip or map : Functions like zip and map can take any number of iterables. By using *args, you can make your own function that behaves similarly.

def my_zip(*iterables):
    return zip(*iterables)

Variable Argument LengthWhen you're unsure how many arguments will be passed to a function, or if you are writing a function that is a "catch-all" for various types of inputs, *args is the better option.

 

Use Cases for **kwargs

Dynamic Function ConfigurationWhen you're setting up engines, APIs, or other complex systems, **kwargs allows you to pass a number of optional keyword arguments to configure the behavior.

def initialize_database(**kwargs):
    config = {
        "host": kwargs.get("host", "localhost"),
        "port": kwargs.get("port", 5432),
        "user": kwargs.get("user", "root"),
    }
    # Initialize database

Attribute Initialization in Classes : When defining a class, **kwargs can be used to set multiple attributes at once, especially optional ones.

class Car:
    def __init__(self, **kwargs):
        self.make = kwargs.get("make", "Unknown")
        self.model = kwargs.get("model", "Unknown")

Building Decorators or Wrappers : **kwargs allows your decorator or wrapper to be flexible enough to handle any keyword arguments that are passed into the wrapped function.

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Something is happening before the function is called.")
        func(*args, **kwargs)
    return wrapper

Optional Parameters for Functions and Methods : When a function has many parameters that are mostly optional, **kwargs makes the function easier to read and use.

def plot_graph(x, y, **kwargs):
    color = kwargs.get("color", "blue")
    linestyle = kwargs.get("linestyle", "--")
    # Plot graph

 

Combining *args and **kwargs

How to Use Both in a Function

In some scenarios, you might need a function to accept any number of both positional and keyword arguments. Python allows you to use both *args and **kwargs in the same function definition. However, *args must appear before **kwargs.

Here's the basic syntax:

def func(arg1, arg2, *args, kwarg1=None, kwarg2=None, **kwargs):
    # Function Body
  • arg1 and arg2 are mandatory positional arguments.
  • *args will collect additional positional arguments.
  • kwarg1 and kwarg2 are optional keyword arguments.
  • **kwargs will collect additional keyword arguments.
def my_function(a, b, *args, kw1=None, kw2=None, **kwargs):
    print("a:", a)
    print("b:", b)
    print("args:", args)
    print("kw1:", kw1)
    print("kw2:", kw2)
    print("kwargs:", kwargs)

Calling the function:

my_function(1, 2, 3, 4, 5, kw1="python", kw2="rocks", kw3="always", kw4="forever")

Will output:

a: 1
b: 2
args: (3, 4, 5)
kw1: python
kw2: rocks
kwargs: {'kw3': 'always', 'kw4': 'forever'}

When to Use Both

Wrapper Functions and Decorators: When you're writing wrapper functions or decorators that should work with any kind of function, whether it accepts positional or keyword arguments.

def my_decorator(fn):
    def wrapper(*args, **kwargs):
        print("Additional functionality here.")
        return fn(*args, **kwargs)
    return wrapper

Flexible APIs: If you're designing an API that should be extraordinarily flexible, accepting any number of positional or keyword arguments for future extensibility.

Multi-Level Configuration: In complex systems where configurations have to be passed through multiple layers of functions, the ability to capture any number of unnamed and named arguments can be useful.

Logging and Auditing: When you want to capture every argument passed to a function for logging, debugging, or auditing, combining *args and **kwargs can be quite handy.

 

Varargs and Keyword Arguments Explained

What are Varargs? (*args)

The term "varargs" stands for "variable-length arguments," and it's represented in Python by the *args syntax within a function definition. This means that the function can accept an arbitrary number of positional arguments, which will be packed into a tuple.

Concept of Variable-length Arguments

Normally in Python, a function requires a specific number of arguments. Varargs free us from this constraint by allowing us to pass any number of positional arguments to the function. Internally, these extra arguments are packed into a tuple which can be iterated through, within the function body.

What are Keyword Arguments? (**kwargs)

The **kwargs syntax in Python function definitions is used to indicate that any number of keyword arguments can be passed to the function. Kwargs" stands for "keyword arguments," and they're received in the function as a dictionary.

Concept of Keyword Argument Unpacking

Just like *args packs additional positional arguments into a tuple, **kwargs packs additional keyword arguments into a dictionary. The keys in this dictionary are the argument names, and the corresponding values are the argument values.

Deep Dive into the Concept of Variable-length Arguments

*args In-Depth

When *args is included in a function definition, Python automatically collects all the unassigned positional arguments into a new tuple that is then assigned to *args.

def collect_args(*args):
    print(args)
  
collect_args(1, 2, 3, 4)  # Output will be a tuple: (1, 2, 3, 4)

You can also combine *args with regular positional arguments:

def collect_args(arg1, arg2, *args):
    print("arg1:", arg1)
    print("arg2:", arg2)
    print("args:", args)
  
collect_args(1, 2, 3, 4)  
# Output:
# arg1: 1
# arg2: 2
# args: (3, 4)

**kwargs In-Depth

With **kwargs, Python collects all unassigned keyword arguments into a dictionary. The keys are the argument names, and the values are the argument values.

def collect_kwargs(**kwargs):
    print(kwargs)
  
collect_kwargs(arg1=1, arg2=2)
# Output will be a dictionary: {'arg1': 1, 'arg2': 2}

Combining **kwargs with named keyword arguments is also possible:

def collect_kwargs(arg1, arg2, **kwargs):
    print("arg1:", arg1)
    print("arg2:", arg2)
    print("kwargs:", kwargs)

collect_kwargs(1, 2, arg3=3, arg4=4)
# Output:
# arg1: 1
# arg2: 2
# kwargs: {'arg3': 3, 'arg4': 4}

 

Common Mistakes and Pitfalls

1. Misunderstanding Argument Order

When using *args and **kwargs in a function definition, it's crucial to remember that the order of parameters should be: standard positional arguments, *args, named keyword arguments, and then **kwargs. Failing to maintain this order will result in a syntax error.

# Incorrect
def my_function(*args, x):
    pass

# Correct
def my_function(x, *args):
    pass

2. Overusing *args and **kwargs

While it's tempting to make all your functions incredibly flexible by adding *args and **kwargs everywhere, this can make your code less readable and harder to debug. Use them only when you genuinely need the extra flexibility.

3. Accidentally Overwriting Arguments

Be careful when combining *args and **kwargs with positional and keyword arguments. If an argument is passed that already has a defined keyword in the function, Python will throw a TypeError.

def func(a, b, **kwargs):
    print(a, b, kwargs)

# This will raise a TypeError
func(1, 2, a=3)

4. Not Iterating Over *args or **kwargs

Remember that *args is a tuple and **kwargs is a dictionary. If you intend to go through each argument, make sure to iterate over these collections correctly.

# Correct
def func_with_args(*args):
    for arg in args:
        print(arg)

# Correct
def func_with_kwargs(**kwargs):
    for key, value in kwargs.items():
        print(key, value)

5. Mixing *args and **kwargs Poorly

While Python allows you to use both *args and **kwargs in the same function definition, remember that *args has to appear before **kwargs. Also, any positional arguments should appear before *args, and named keyword arguments should appear before **kwargs.

# Correct
def my_function(a, b, *args, keyword1=None, **kwargs):
    pass

6. Neglecting Type Checking

While *args and **kwargs are flexible, they don't provide any type information. If your function expects arguments of certain types, you should include checks within the function body to handle incorrect types gracefully.

def add_integers(*args):
    if not all(isinstance(arg, int) for arg in args):
        raise TypeError("All arguments must be integers.")
    return sum(args)

 

Frequently Asked Questions (FAQs)

What are *args and **kwargs?

*args and **kwargs are special syntax in Python used for passing a variable number of arguments to a function. *args is used to pass non-keyword variable-length arguments, and **kwargs is used to pass keyword variable-length arguments.

How do I use *args?

To use *args in your function, you can do something like def my_function(*args):. Inside the function, args will be a tuple containing all the passed arguments.

How do I use **kwargs?

For **kwargs, the function signature would look like def my_function(**kwargs):. Inside the function, kwargs will be a dictionary containing all the keyword arguments passed to the function.

Can I use *args and **kwargs together?

Yes, you can use them together, but *args has to appear before **kwargs. For example, def my_function(*args, **kwargs): is correct.

What is the difference between *args and **kwargs?

The primary difference is that *args is used for non-keyword arguments, and **kwargs is used for keyword arguments. In the function, *args will be a tuple while **kwargs will be a dictionary.

Are *args and **kwargs mandatory?

No, they are not mandatory. You can choose to use them when you're not sure how many arguments your function will receive, and you want to make it flexible.

Can I use other names instead of args and kwargs?

Yes, the names args and kwargs are just conventions. You can use *varargs and **keyargs if you like, but sticking to the convention is recommended for readability.

How do I specify type for *args or **kwargs?

You can't directly specify types for *args and **kwargs in the function definition. However, you can check the type of arguments inside the function and raise errors if they're not what you expect.

Can I pass *args and **kwargs to another function?

Yes, you can pass *args and **kwargs to another function by using * and ** in the function call. For example, another_function(*args, **kwargs).

What's the order of using *args, positional arguments, and **kwargs?

When used together in a function definition, positional arguments should come first, followed by *args, then keyword arguments, and finally **kwargs. For example, def my_function(a, b, *args, kw1=None, **kwargs).

 

Conclusion

Summary of Key Takeaways

  • *args and **kwargs are powerful features in Python for handling a variable number of function arguments.
  • *args is used for variable-length non-keyword arguments and unpacks values into a tuple.
  • **kwargs is used for variable-length keyword arguments and unpacks values into a dictionary.
  • Both can be combined in function definitions, but *args must come before **kwargs.
  • While *args and **kwargs are not mandatory, they offer flexibility and scalability, making your functions more robust and easier to maintain.

Use-Cases and Best Practices

  • Use *args when you expect to pass a varying number of non-keyword arguments to your function.
  • Use **kwargs when you have a variable number of keyword arguments.
  • Always place *args before **kwargs in your function definitions.
  • It's possible to combine positional arguments, *args, keyword arguments, and **kwargs but they must be in this specific order.
  • Stick to the naming conventions (*args and **kwargs) for better readability and understanding of your code.

 

Additional Resources

 

Views: 510
Bashir Alam

Bashir Alam

He is a Computer Science graduate from the University of Central Asia, currently employed as a full-time Machine Learning Engineer at uExel. His expertise lies in Python, Java, Machine Learning, OCR, text extraction, data preprocessing, and predictive models. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment