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
| Syntax | Meaning |
|---|---|
* | Forces all following parameters to be keyword-only |
**extra | Collects arbitrary keyword arguments into a dict |
| Combined | Clean, 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 likewidth,height,depth.) -
✅ Use
**extrawhen the set of parameters might expand over time. (e.g. logging fields, metadata, API payloads.) -
⚠️ Avoid overusing
**extrawhen strict schema validation matters. (You don’t want silently ignored typos likeresoluton="1m".) -
✅ Combine both for stable APIs with optional flexibility, like:
def log_event(event: str, *, level: str = "info", **fields): ...
🔄 Related Patterns
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):
...
| Section | Captures | Example |
|---|---|---|
pos1 | First positional argument | "path/to/file" |
*args | Remaining positional args (tuple) | ("SOL-PERP", "1m") |
kw1 | Named keyword-only argument | kw1="value" |
**kwargs | Any 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
| Symbol | Collects | Example Usage |
|---|---|---|
*args | Variable positional args | func(1, 2, 3) |
**kwargs | Variable keyword args | func(a=1, b=2) |
* | Forces keyword-only args after it | def f(x, *, y) |
**extra | Collects extra keywords (alias of **kwargs) | def f(x, **extra) |