DHI / DHI/python-package-development

Add fluent interface pattern as teaching example

Open
#36 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Jupyter Notebook
Stars
8
Forks
1
Avg merge
4m
Merged PRs (30d)
1

Description

Add a section or exercise covering API design patterns for simplifying functions with many parameters.

Good reference example: https://github.com/DHI/modelskill/discussions/492 — the `scatter()` function has grown a long signature:

```python
def scatter(
x: np.ndarray,
y: np.ndarray,
*,
bins: int | float = 120,
quantiles: int | Sequence[float] | None = None,
fit_to_quantiles: bool = False,
show_points: bool | int | float | None = None,
show_hist: Optional[bool] = None,
show_density: Optional[bool] = None,
norm: Optional[colors.Normalize] = None,
backend: Literal["matplotlib", "plotly"] = "matplotlib",
figsize: Tuple[float, float] = (8, 8),
xlim: Optional[Tuple[float, float]] = None,
ylim: Optional[Tuple[float, float]] = None,
reg_method: str | bool = "ols",
title: str = "",
xlabel: str = "",
ylabel: str = "",
skill_table: Optional[str | Sequence[str] | Mapping[str, str] | bool] = False,
skill_scores: Mapping[str, float] | None = None,
skill_score_unit: Optional[str] = "",
ax: Optional[Axes] = None,
**kwargs,
) -> Axes:
```

### Alternative patterns to simplify

**1. Fluent interface (method chaining)**

Each component gets its own method. Easy to add/remove parts.

```python
(Comparer(x, y)
.plot()
.scatter(alpha=0.5)
.qq([0.05, 0.5, 0.75, 0.95])
# .reg_line(equation=True)
.skill_table(("n", "bias"))
).show()
```

**2. Configuration objects (dataclasses)**

Group related parameters into typed config objects.

```python
@dataclass
class ScatterStyle:
bins: int = 120
show_points: bool = True
show_density: bool = False
norm: colors.Normalize | None = None

@dataclass
class Layout:
figsize: tuple[float, float] = (8, 8)
xlim: tuple[float, float] | None = None
ylim: tuple[float, float] | None = None
title: str = ""
xlabel: str = ""
ylabel: str = ""

scatter(x, y, style=ScatterStyle(bins=50), layout=Layout(title="My plot"))
```

**3. Presets / named styles**

Offer common configurations as named presets, with overrides.

```python
scatter(x, y, preset="minimal") # just points + 1:1 line
scatter(x, y, preset="full") # density + qq + regression + skill table
scatter(x, y, preset="presentation") # large fonts, clean layout
scatter(x, y, preset="minimal", title="Hm0") # preset + override
```

**4. Composition of small functions**

Instead of one function that does everything, provide building blocks that work with a standard Axes.

```python
fig, ax = plt.subplots()
plot_scatter(ax, x, y, show_density=True)
plot_qq(ax, x, y, quantiles=[0.25, 0.5, 0.75])
plot_reg_line(ax, x, y)
add_skill_table(ax, x, y, metrics=["bias", "rmse"])
```

Each pattern has trade-offs worth discussing: discoverability, type safety, composability, backwards compatibility, learning curve.

Relevant topics: method chaining, `Self` return type, builder pattern, dataclasses as config, API design trade-offs.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.