Skip to content

Conversation

@ecomodeller
Copy link
Member

Keyword-Only Arguments: Improving API Clarity

What are keyword-only arguments?

Keyword-only arguments (introduced in Python 3 via PEP 3102) are function parameters that must be passed by name, not by position. They use the * separator in function signatures:

def function(positional_arg, *, keyword_only_arg=default):
    pass

# Valid
function(data, keyword_only_arg=value)

# Invalid - raises TypeError
function(data, value)

Why use keyword-only arguments?

For methods with multiple optional configuration parameters, keyword-only arguments dramatically improve code readability:

# Before: Which parameter is which?
cc.sel("SW_1", "c2", None, "2017-10-27", None, [0, 0, 10, 10])

# After: Crystal clear
cc.sel(model="SW_1", observation="c2", start="2017-10-27", area=[0, 0, 10, 10])

This prevents errors from parameter order confusion and makes code self-documenting.

User Experience: Deprecation Phase (v1.4)

When users pass optional parameters positionally, they'll see a helpful warning:

>>> cmp.sel("model_name", "2017-10-27")
FutureWarning: Passing model='model_name', start='2017-10-27' as positional 
argument(s) is deprecated since version 1.4 and will raise an error in version 
2.0. Please use keyword argument(s) instead.

The code still works—users just get guidance to update their usage. Since all current test usage is already keyword-based, most users won't see any warnings.

Implementation: Two-Phase Approach (SLEP009)

Following scikit-learn's enhancement proposal SLEP009:

Phase 1 (v1.4 - this PR):

  • Add @_deprecate_positional_args decorator to methods with 2+ optional parameters
  • Issues FutureWarning when options passed positionally
  • Code continues to work normally

Phase 2 (v2.0 - future):

  • Add * to function signatures
  • Remove decorator
  • Positional usage raises TypeError

Methods Updated

Core Comparison API (4 methods):

  • Comparer.sel() - 5 optional filter parameters
  • Comparer.gridded_skill() - 5 optional configuration parameters
  • ComparerCollection.sel() - 7+ optional filter parameters
  • ComparerCollection.gridded_skill() - 5 optional configuration parameters

Skill Assessment API (3 methods):

  • SkillTable.sel() - 2 optional parameters + kwargs
  • SkillTable.style() - 4 optional formatting parameters
  • SkillArrayPlotter.grid() - 7 optional display parameters

Plotting API (4 methods):

  • ComparerPlotter.kde() - 3 optional display parameters
  • ComparerPlotter.residual_hist() - 5 optional plotting parameters
  • ComparerCollectionPlotter.spatial_overview() - 3 optional display parameters
  • ComparerCollectionPlotter.temporal_coverage() - 5 optional display parameters

Total: 12 methods updated

Why These Methods?

Methods were selected based on:

  1. Multiple optional parameters (2+) where positional usage is ambiguous
  2. All current usage is already keyword-based (verified in tests)
  3. Significant readability improvement from explicit parameter names

Methods with single optional parameters or semantically distinct parameters (like skill(by, metrics)) were intentionally excluded.

Migration Path

Users seeing warnings should update their code:

# Old style (will warn in v1.4, error in v2.0)
cmp.sel("model", "2017-01-01")

# New style (works now and in the future)
cmp.sel(model="model", start="2017-01-01")

Related: Follows the pattern established by scikit-learn, scipy, and xgboost for gradual API improvements with clear user guidance.

Implements scikit-learn-style SLEP009 approach to gradually enforce
keyword-only arguments for methods with multiple optional parameters.

Changes:
- Created _deprecation.py with @_deprecate_positional_args decorator
- Applied decorator to 12 methods across comparison, skill, and plotting modules
- Methods selected have 2+ optional params where positional usage is ambiguous

Phase 1 (v1.4): Deprecation warnings when passing options positionally
Phase 2 (v2.0): Add * to signatures, remove decorator

All tests pass. No breaking changes - current usage is keyword-based.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants