Skip to main content

Understanding the * Pattern in Function Definitions

In Python, you’ll often see function signatures like this:

def write_dataset_meta(dataset_path: str, *, symbol: str, resolution: str, **extra: Any):
...

Let’s break it down step by step.


1. The Lone *: Keyword-Only Arguments

The single * means:

All parameters after the star must be passed by keyword, not by position.

That enforces readability and avoids mistakes where you accidentally swap arguments.

✅ Valid:

write_dataset_meta("path/to/file", symbol="SOL-PERP", resolution="1m")

❌ Invalid:

write_dataset_meta("path/to/file", "SOL-PERP", "1m")
# TypeError: write_dataset_meta() takes 1 positional argument but 3 were given

This is a keyword-only argument pattern — it forces you to write symbol=... and resolution=..., which makes your code more self-documenting.


2. **extra: Collecting Arbitrary Keyword Arguments

**extra means:

“Capture any additional keyword arguments into a dictionary.”

So, if you call:

write_dataset_meta(
"data/SOL-PERP_1m.parquet",
symbol="SOL-PERP",
resolution="1m",
venue="drift",
source="dl_candles_v1",
checksum="abc123"
)

Then inside the function, extra will contain:

{
"venue": "drift",
"source": "dl_candles_v1",
"checksum": "abc123"
}

You can merge these into your metadata dictionary like this:

meta = {
"symbol": symbol,
"resolution": resolution,
**extra,
}

3. Why This Pattern Is Useful

This combination — *, keyword-only args, **extra — is common for functions that:

  • Have a few core required parameters.
  • Need extensibility for optional fields.
  • Want to enforce readability at the call site.

You’ll often see this pattern in:

  • Data pipelines and ETL helpers.
  • Logging and tracing utilities.
  • API request builders.

4. Without the * (for comparison)

If we removed the *:

def write_dataset_meta(dataset_path: str, symbol: str, resolution: str, **extra: Any):
...

You could call it positionally:

write_dataset_meta("path/to/file", "SOL-PERP", "1m")

But that’s less explicit, and a swapped order could cause silent bugs.


✅ Summary

SyntaxMeaning
*Forces all following parameters to be keyword-only
**extraCollects arbitrary keyword arguments into a dict
CombinedClean, flexible, and self-documenting function interface

🧭 Best Practices

  • Use * when clarity at the call site matters more than brevity. (e.g. functions with multiple similar arguments like width, height, depth.)

  • Use **extra when the set of parameters might expand over time. (e.g. logging fields, metadata, API payloads.)

  • ⚠️ Avoid overusing **extra when strict schema validation matters. (You don’t want silently ignored typos like resoluton="1m".)

  • ✅ Combine both for stable APIs with optional flexibility, like:

    def log_event(event: str, *, level: str = "info", **fields): ...

1. *args — Collecting Positional Arguments

*args collects all positional arguments into a tuple.

Example:

def log_values(*args):
print(args)

log_values("SOL-PERP", "1m", "drift")
# ('SOL-PERP', '1m', 'drift')

Use this when:

  • The number of positional arguments is variable.
  • Order matters, but names don’t.

2. **kwargs — Collecting Keyword Arguments

**kwargs (like **extra) collects all keyword arguments into a dict.

def log_fields(**kwargs):
print(kwargs)

log_fields(symbol="SOL-PERP", resolution="1m", venue="drift")
# {'symbol': 'SOL-PERP', 'resolution': '1m', 'venue': 'drift'}

Use this for:

  • Flexible key/value fields.
  • Wrappers that forward arbitrary keyword arguments.

3. Mixing Both

You can use both in one function — order matters:

def example(pos1, *args, kw1=None, **kwargs):
...
SectionCapturesExample
pos1First positional argument"path/to/file"
*argsRemaining positional args (tuple)("SOL-PERP", "1m")
kw1Named keyword-only argumentkw1="value"
**kwargsAny remaining keyword args{"venue": "drift"}

4. Argument Unpacking

You can unpack arguments when calling functions, too:

args = ("path/to/file",)
kwargs = {"symbol": "SOL-PERP", "resolution": "1m"}

write_dataset_meta(*args, **kwargs)

This mirrors how *args and **kwargs collect them when defining a function.


🧩 Summary of All Four

SymbolCollectsExample Usage
*argsVariable positional argsfunc(1, 2, 3)
**kwargsVariable keyword argsfunc(a=1, b=2)
*Forces keyword-only args after itdef f(x, *, y)
**extraCollects extra keywords (alias of **kwargs)def f(x, **extra)