Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 4

Flexible Arguments: Python vs. Java

Recommended resources

Hello! Welcome to the final lesson in our first module, "Pythonic Foundations for Java Developers."

In our previous lessons, we've explored how functions in Python are first-class objects, can be nested, and can form closures that capture state. These concepts highlight Python's functional programming capabilities.

Today, we'll build on this by examining another aspect of Python's function-handling flexibility: its system for passing a variable number of arguments. Your goal for this lesson is to distinguish between Python's flexible argument passing (*args, **kwargs) and Java's varargs. This is a key difference between the two languages and understanding it is crucial for writing adaptable and idiomatic Python functions.

1. Handling Arbitrary Arguments in Python

In Java, if you want a method to accept a variable number of arguments, you use varargs (e.g., public void myMethod(String... names)). Python offers a more powerful and flexible two-part system for this.

Let's watch a video that breaks down this system piece by piece.

Python *args and **kwargs Demystified: Everything You Need to Know

The video 'Python *args and **kwargs Demystified' by k0nze provides a very clear, step-by-step explanation of how Python handles arbitrary arguments. We'll watch it in segments.

Please watch the following three segments: *Using args (04:47 - 06:42): Focus on how *args collects multiple positional arguments into a tuple. **Using kwargs (08:40 - 10:48): Observe how **kwargs collects multiple keyword (named) arguments into a dictionary. **Combining *args and kwargs (11:51 - 14:21): Pay attention to the required order of parameters when using everything together in one function signature.

Let's summarize the key points from the video:

  • *args (Arbitrary Positional Arguments): When you place *args in a function definition, it "collects" any extra positional arguments passed during a function call into a tuple. The name args is a strong convention, but the asterisk * is the operator doing the work.

    def summarize(title, *points):
        print(f"Summary for: {title}")
        print(f"Type of 'points': {type(points)}")
        for i, point in enumerate(points, 1):
            print(f"{i}. {point}")
    
    summarize("Chapter 1", "Introduction", "Key Concepts", "Initial Setup")
    # Output:
    # Summary for: Chapter 1
    # Type of 'points': <class 'tuple'>
    # 1. Introduction
    # 2. Key Concepts
    # 3. Initial Setup
    
  • **kwargs (Arbitrary Keyword Arguments): Similarly, **kwargs collects any extra keyword arguments (e.g., name="value") into a dictionary. Again, kwargs is the convention; the double asterisk ** is the operator.

    def process_data(**kwargs):
        print(f"Type of 'kwargs': {type(kwargs)}")
        print("Processing the following data:")
        for key, value in kwargs.items():
            print(f"- {key}: {value}")
    
    process_data(user_id=101, status="active", source="web-form")
    # Output:
    # Type of 'kwargs': <class 'dict'>
    # Processing the following data:
    # - user_id: 101
    # - status: active
    # - source: web-form
    
  • Order Matters: When you combine standard parameters, *args, and **kwargs, they must appear in that specific order:

    1. Standard positional/keyword arguments
    2. *args
    3. Keyword-only arguments (if any)
    4. **kwargs

2. The Reverse: Unpacking Arguments from Collections

The * and ** operators can also be used when calling a function. This is known as "unpacking." It allows you to pass elements from a list/tuple as positional arguments or items from a dictionary as keyword arguments.

This is extremely useful when you have your arguments already collected in a data structure.

4. More Control Flow Tools — Python 3.14.0 documentation

The official Python documentation provides a concise explanation of this 'unpacking' mechanism. It's the inverse of the 'collecting' mechanism we just saw.

Please read section 4.9.5, 'Unpacking Argument Lists'. Focus on the examples showing * with a list and ** with a dictionary.

Here's a quick demonstration:

def create_user(username, email, is_admin=False):
    print(f"Creating user '{username}' with email '{email}'. Admin: {is_admin}")

# --- Unpacking a list/tuple with * ---
user_details_list = ["jdoe", "jdoe@example.com"]
create_user(*user_details_list)
# This is equivalent to: create_user("jdoe", "jdoe@example.com")

# --- Unpacking a dictionary with ** ---
user_details_dict = {
    "username": "asmith",
    "email": "asmith@example.com",
    "is_admin": True
}
create_user(**user_details_dict)
# This is equivalent to: create_user(username="asmith", email="asmith@example.com", is_admin=True)

This unpacking mechanism is a powerful feature that promotes writing clean, adaptable code.


3. Comparison with Java's Varargs

Now, let's connect this back to your Java experience. You're familiar with varargs, which provides a way to handle a variable number of arguments.

Javadocs for Python users

This article, 'Javadocs for Python users', is perfect for us. It directly explains Java's varargs in a way that's easy to compare with Python.

Please read the section titled 'Varargs' and the summary table under 'Java vs Python'. This will crystallize the differences.

Let's summarize the key distinctions in a table. Given your background, this should clarify the design philosophies of each language.

Feature Python (*args, **kwargs) Java (varargs)
Syntax *args for positional, **kwargs for keyword. Type... varName
Argument Types Positional (*args): Can be of any type.
Keyword (**kwargs): Key-value pairs.
All arguments must be of the same specified type (or its subtypes).
Internal Data Structure *args is a tuple.
**kwargs is a dict.
The varargs parameter is an array of the specified type.
Parameter Position *args must follow standard positional arguments. **kwargs must be last. The varargs parameter must be the last one in the method signature.
Flexibility Very High. The ability to accept arbitrary named arguments with **kwargs is particularly powerful for optional settings and configurations. Moderate. It only handles a variable number of positional arguments of a single base type. There is no built-in equivalent for keyword arguments.

The most significant difference is Python's native support for arbitrary keyword arguments (**kwargs). This is a common idiom in Python libraries for passing optional configuration, as it's self-documenting and doesn't depend on argument order.

For example, the popular requests library uses **kwargs to allow users to pass optional parameters like headers, cookies, or timeouts in a clean way.

import requests

# The 'params' and 'timeout' arguments are collected by **kwargs
# in the requests.get function definition.
response = requests.get(
    "https://api.github.com/search/repositories",
    params={"q": "python"},
    timeout=5
)

4. Practice Exercise: A Generic Function Logger

Let's write a utility function that would be impossible to create so elegantly in Java without reflection. Your task is to create a wrapper function that can log a call to any other function, no matter its signature.

  1. Define a function log_call that accepts a function func as its first argument, followed by *args and **kwargs.
  2. Inside log_call, print the name of the function being called (you can get it from func.__name__).
  3. Print the positional arguments (args) and keyword arguments (kwargs) that were passed.
  4. Call func by unpacking *args and **kwargs into it.
  5. Store the result of the call and print it.
  6. Define two simple, different functions to test your logger:
    • add(a, b) which returns a + b.
    • greet(name, greeting="Hello") which returns f"{greeting}, {name}!".
  7. Call log_call with each of your test functions and appropriate arguments.
Click here to see a possible solution
def log_call(func, *args, **kwargs):
    """
    Logs the call to any function, printing its name, arguments,
    and return value.
    """
    print(f"--- Calling function: {func.__name__} ---")
    print(f"Positional args: {args}")
    print(f"Keyword args: {kwargs}")
    
    result = func(*args, **kwargs)
    
    print(f"Result: {result}")
    print("--- Call finished ---\n")
    return result

# Test functions
def add(a, b):
    return a + b

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

# --- Test sequence ---
log_call(add, 5, 10)
log_call(greet, "Alice")
log_call(greet, name="Bob", greeting="Hi")

Expected Output:

--- Calling function: add ---
Positional args: (5, 10)
Keyword args: {}
Result: 15
--- Call finished ---

--- Calling function: greet ---
Positional args: ('Alice',)
Keyword args: {}
Result: Hello, Alice!
--- Call finished ---

--- Calling function: greet ---
Positional args: ()
Keyword args: {'name': 'Bob', 'greeting': 'Hi'}
Result: Hi, Bob!
--- Call finished ---

This exercise demonstrates the primary use case for *args and **kwargs: creating highly generic functions that can wrap or delegate to other functions without needing to know their specific signatures.


Conclusion

Congratulations on completing the first module! We've covered some of the most fundamental "Pythonic" concepts that often differ from a Java developer's perspective.

Key Takeaways:

  • Python uses *args to collect an arbitrary number of positional arguments into a tuple.
  • Python uses **kwargs to collect an arbitrary number of keyword arguments into a dictionary.
  • This is more flexible than Java's varargs, which only handles positional arguments of a single type and stores them in an array.
  • The * and ** operators can also be used during a function call to unpack iterables and dictionaries into arguments.
  • This pattern is essential for writing generic functions that can wrap or accept any other function, a concept known as higher-order functions.

In our next module, we will dive into Python's object model and decorators. You will see immediately how closures and the *args/**kwargs syntax we learned today are the essential building blocks for creating decorators. This will bring us much closer to understanding the complex code snippet that inspired this course.