DEV Community

Python-T Point
Python-T Point

Posted on β€’ Originally published at pythontpoint.in

🐍 python args and kwargs explained simple β€” common mistakes and fixes

❓ Can You Really Use *args and **kwargs Beyond Simple Examples?

python args and kwargs explained simple

The *args and **kwargs syntax in Python is not just about passing extra arguments; it's about writing functions that adapt to evolving interfaces, wrap other functions cleanly, and avoid brittle parameter lists in real codebases. Most tutorials stop at toy examples, leaving developers unsure how to apply them in production-grade code.

πŸ“‘ Table of Contents

  • ❓ Can You Really Use *args and **kwargs Beyond Simple Examples?
  • 🐍 args β€” Handling *Variable Positional Inputs
  • πŸ”§ Use Case: Flexible Logging Layers
  • ⚠️ Gotcha: Order Matters
  • 🧩 *kwargs β€” Working with *Arbitrary Keyword Arguments
  • πŸ”§ Use Case: API Client Builders
  • ⚠️ Gotcha: Don’t Blindly Forward Unknown Kwargs
  • 🀝 Combining *args and **kwargs for Full Flexibility
  • πŸ” How Parameter Resolution Works
  • βš™οΈ Unpacking with * and ** in Function Calls
  • 🧠 When to Use args and kwargs in Real Projects
  • βœ… Do Use Them For
  • ❌ Avoid Overusing When
  • πŸ“š Example: Flexible Class Initialization
  • 🟩 Final Thoughts
  • ❓ Frequently Asked Questions
  • Can *args and **kwargs be used together in a function definition?
  • Is there a performance cost to using *args and **kwargs?
  • What happens if I pass a keyword argument that matches a named parameter and also include it in **kwargs?
  • πŸ“š References & Further Reading

🐍 args β€” Handling *Variable Positional Inputs

The *args syntax lets a function accept any number of positional arguments, collected into a tuple. When Python sees the * prefix on a parameter, it tells the function to pack all remaining positional arguments into a tuple accessible by the given name. This is implemented at the C-level in CPython using PyArg_ParseTupleAndKeywords and related APIs β€” the interpreter dynamically builds the tuple from the call stack.

def log_action(user, action, *details):
    print(f"User '{user}' performed '{action}'")
    if details:
        print(f"Details: {', '.join(str(d) for d in details)}")

# Usage
log_action("alice", "file_upload", "report.pdf", "size: 2MB", "encrypted=True")



User 'alice' performed 'file_upload'
Details: report.pdf, size: 2MB, encrypted=True
Enter fullscreen mode Exit fullscreen mode

πŸ”§ Use Case: Flexible Logging Layers

Functions that wrap actions β€” like audit logging in admin systems β€” often don’t know what arguments the wrapped function will receive. *args allows the wrapper to pass through all positional inputs untouched.

def audit_log(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        return func(*args, **kwargs)
    return wrapper

@audit_log
def transfer_funds(from_id, to_id, amount, reason=None):
    print(f"Transferred ${amount} from {from_id} to {to_id}")

transfer_funds(101, 205, 500, reason="refund")



Calling transfer_funds with args=(101, 205, 500), kwargs={'reason': 'refund'}
Transferred $500 from 101 to 205
Enter fullscreen mode Exit fullscreen mode

⚠️ Gotcha: Order Matters

*args consumes all unmatched positional arguments, so it must come after any required positional parameters. You can't define a function like def bad_func(*args, x) β€” Python raises a SyntaxError.


🧩 *kwargs β€” Working with *Arbitrary Keyword Arguments

The **kwargs syntax collects any unmatched keyword arguments into a dictionary. Mechanistically, when Python processes a function call, keyword arguments not matched to formal parameters are packed into a dict object. This is efficient for configuration-heavy workflows because dictionary lookups are O(1), and the structure mirrors JSON-like data common in APIs and config files.

def create_user(name, email, **profile):
    user = {"name": name, "email": email}
    user.update(profile)  # Add optional fields
    print(f"Created user: {user}")
    return user

# Usage
create_user("Bob", "bob@example.com", role="admin", team="infra", active=True)



Created user: {'name': 'Bob', 'email': 'bob@example.com', 'role': 'admin', 'team': 'infra', 'active': True}
Enter fullscreen mode Exit fullscreen mode

πŸ”§ Use Case: API Client Builders

When interfacing with REST APIs, query parameters or headers often vary by endpoint. Using **kwargs lets you write generic request wrappers.

import requests

def api_get(endpoint, **options):
    base_url = "https://api.example.com/v1"
    url = f"{base_url}/{endpoint}"

    # Extract specific keys, pass the rest as params
    headers = options.pop('headers', {})
    timeout = options.pop('timeout', 5)

    response = requests.get(url, params=options, headers=headers, timeout=timeout)
    return response.json() if response.ok else None

# Flexible calls
api_get("users", role="dev", active=True, timeout=10)
api_get("servers", region="us-west-2", headers={"Authorization": "Bearer xyz"})
Enter fullscreen mode Exit fullscreen mode

This pattern keeps your interface clean while allowing full control over HTTP parameters β€” all without bloating the function signature.

⚠️ Gotcha: Don’t Blindly Forward Unknown Kwargs

Passing every unknown keyword argument directly to another system can introduce security or stability risks. Always validate or sanitize **kwargs when interfacing with external systems. (Also read: 🐍 python multiple inheritance examples β€” common mistakes and how to fix them)

Use *args and **kwargs to defer decisions, not avoid design.


🀝 Combining *args and **kwargs for Full Flexibility

A function can accept both *args and **kwargs, making it capable of wrapping any callable with any signature. (Also read: 🐍 How to set up CI/CD for a Python Flask app using GitHub Actions β€” common mistakes and key tips)

This combination is foundational in decorators, middleware, and proxy functions β€” especially in frameworks like Django, FastAPI, or Flask, where handlers need to remain agnostic to underlying signatures.

def retry_on_failure(max_retries=3):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_retries + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Attempt {attempt} failed: {e}")
                    if attempt == max_retries:
                        raise
            return None
        return wrapper
    return decorator

@retry_on_failure(max_retries=2)
def unstable_api_call(user_id):
    import random
    if random.random() < 0.7:
        raise ConnectionError("Network timeout")
    return {"status": "success", "data": f"profile_{user_id}"}

# Try calling
unstable_api_call(123)



Attempt 1 failed: Network timeout
Attempt 2 failed: Network timeout
...
# May eventually succeed or raise after 2 attempts
Enter fullscreen mode Exit fullscreen mode

πŸ” How Parameter Resolution Works

Python resolves function arguments in this order: (Also read: πŸ“¦ Dockerfile best practices Python Flask β€” common mistakes and how to fix them)

  1. Positional arguments (matched to named parameters)
  2. Keyword arguments (by name)
  3. Default values for missing parameters
  4. *args collects unmatched positional arguments
  5. **kwargs collects unmatched keyword arguments

The interpreter uses a stack frame to bind names, and the * and ** operators control how excess values are packed or unpacked.

βš™οΈ Unpacking with * and ** in Function Calls

Just as *args packs positional arguments during definition, using * in a function call unpacks a sequence into positional arguments.

args = ["Alice", "edit_post", "post_id=456", "draft=True"]
log_action(*args)  # Equivalent to log_action("Alice", "edit_post", "post_id=456", "draft=True")
Enter fullscreen mode Exit fullscreen mode

Similarly, ** unpacks a dictionary into keyword arguments:

kwargs = {
    "name": "Charlie",
    "email": "charlie@example.com",
    "role": "analyst",
    "department": "data"
}
create_user(**kwargs)
Enter fullscreen mode Exit fullscreen mode

This bidirectional use β€” packing on definition, unpacking on call β€” is what makes the args and **kwargs syntax so powerful in dynamic codebases. *(More onPythonTPoint tutorials)


🧠 When to Use args and kwargs in Real Projects

Knowing how to use *args and **kwargs is not enough β€” you need judgment about when to apply them.

βœ… Do Use Them For

  • Decorators β€” they must work with any function signature.
  • API wrappers β€” when forwarding arguments to another function or service.
  • Base classes or mixins β€” passing arguments up the MRO via super().init(*args, **kwargs).
  • Configuration layers β€” where optional settings are passed down.

❌ Avoid Overusing When

  • The function has a clear, stable interface β€” explicit is better.
  • You're hiding required parameters behind **kwargs β€” it hurts discoverability.
  • You're building public APIs β€” users prefer autocomplete-friendly signatures.

πŸ“š Example: Flexible Class Initialization

In inheritance hierarchies, *args and **kwargs let child classes pass arguments up without knowing the parent’s full signature.

class Database:
    def __init__(self, host, port, **options):
        self.host = host
        self.port = port
        self.ssl = options.get("ssl", False)
        self.timeout = options.get("timeout", 30)

class MongoDatabase(Database):
    def __init__(self, db_name, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.db_name = db_name

# Usage
mongo = MongoDatabase(
    db_name="logs",
    host="10.0.1.100",
    port=27017,
    ssl=True,
    timeout=60
)
print(mongo.__dict__)



{'host': '10.0.1.100', 'port': 27017, 'ssl': True, 'timeout': 60, 'db_name': 'logs'}
Enter fullscreen mode Exit fullscreen mode

This pattern is common in ORM models, SDKs, and configuration systems β€” and it’s a real-world example of why the *args and **kwargs syntax matters beyond syntax.


🟩 Final Thoughts

*args and **kwargs are not just syntactic sugar β€” they’re tools for building adaptable, maintainable layers in Python applications. Used wisely, they reduce coupling between components, enable clean decorators, and simplify inheritance.

However, like any dynamic feature, they trade off some clarity for flexibility. The key is knowing when to lock down an interface with explicit parameters, and when to leave it open using *args and **kwargs. In mature codebases, you’ll often see them used deep in infrastructure code β€” middleware, wrappers, base classes β€” while public APIs remain explicit and documented.

Mastering the *args and **kwargs syntax means understanding both the mechanics and the design philosophy: defer decisions when you must, but document and constrain when you can.

❓ Frequently Asked Questions

Can *args and **kwargs be used together in a function definition?

Yes β€” a function can accept both *args and **kwargs, provided they appear in the correct order: regular arguments, then *args, then keyword-only arguments or **kwargs. The syntax def func(a, *args, x=1, **kwargs): is valid and commonly used in frameworks.

Is there a performance cost to using *args and **kwargs?

There is minimal overhead: *args creates a tuple, and **kwargs creates a dictionary. These are lightweight operations in CPython. The bigger concern is readability and debugging β€” stack traces and IDE hints may be less precise when arguments are hidden behind *args and **kwargs.

What happens if I pass a keyword argument that matches a named parameter and also include it in **kwargs?

Python raises a TypeError for ambiguous assignments. For example, if a function has a parameter name, you can't pass name both as a positional/keyword argument and inside **kwargs. The interpreter resolves names strictly and prevents duplication.

πŸ“š References & Further Reading

  • Official Python documentation on calls and definitions β€” covers *args and **kwargs in depth: docs.python.org
  • Python data model reference for function call resolution: docs.python.org
  • Real-world decorator patterns using *args and **kwargs: docs.python.org

Top comments (0)