Add blog about param 2.4 typing story#98
Merged
Merged
Conversation
hoxbro
reviewed
May 21, 2026
| * Add CI jobs for `mypy`, `pyright`, `pyrefly`, and `ty` | ||
|
|
||
| ### 🐛 Bug Fixes | ||
| * Change `UndefinedType` sentinel from a custom class instance to an `enum.Enum` member for improved compatibility with type checkers No newline at end of file |
Contributor
There was a problem hiding this comment.
I would not define this as a bug fix.
hoxbro
reviewed
May 21, 2026
maximlt
reviewed
May 21, 2026
|
|
||
| <hr> | ||
|
|
||
| If you're using [Anaconda](https://www.anaconda.com/downloads), you can install the latest version of Param with `conda install param`. If you prefer pip, use `pip install param`. |
Contributor
There was a problem hiding this comment.
That line feels a little "outdated" citing only Anaconda and pip.
philippjfr
commented
Jun 2, 2026
Co-authored-by: Simon Høxbro Hansen <hoxbro@protonmail.com> Co-authored-by: Maxime Liquet <35924738+maximlt@users.noreply.github.com> Co-authored-by: Philipp Rudiger <prudiger@anaconda.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
title: "Param 2.4.0 Release"
date: "2025-05-21"
description: "Release announcement for Param 2.4.0, introducing full typing support"
author: "Philipp Rudiger"
categories: [release, param]
image: "images/param_typing.png"
aliases:
What is Param?
Param is an open-source Python library that lets you define classes with strongly typed, validated parameters. It is the foundation of the HoloViz ecosystem, powering Panel, hvPlot, HoloViews, and many other libraries. Param handles runtime validation, serialization, and dependency tracking so you can focus on the logic of your application rather than on boilerplate data-checking code.
New Release!
We are excited to announce the 2.4.0 release of Param! This release brings first-class static typing support to Param, a long-awaited improvement that makes the entire Param-based ecosystem significantly easier to use in modern Python development environments.
allow_None,item_type, andclass_.mypy,pyright,pyrefly, andtyin CI.py.typedPEP 561 marker is now included, signaling to tools that Param ships its own type annotations.SelectorandLiteraltypes, and how to work around them today.Many thanks to the contributors to this release.
If you're using Anaconda, you can install the latest version of Param with
conda install param. If you prefer pip, usepip install param.Full Typing Support
Param 2.4.0 introduces comprehensive static typing support, making
Parameterizedclasses significantly more useful in modern development workflows. If you use VSCode (via Pylance), PyCharm, or any other editor with a Python language server, you will now get accurate autocompletion, inline documentation, and type-error highlighting for all Param-based code, including the entire HoloViz ecosystem built on top of it.Before this release, Param's parameters were effectively untyped from a static analysis perspective. A value like
slider.valuewould be inferred asAny, meaning your type checker could not help you catch bugs where the wrong type was passed or returned. With 2.4.0, types flow through correctly:This change is, of course, well overdue but it represents a first step towards modernizing not just Param but the entire HoloViz ecosystem.
Type Inference from Constructor Arguments
The core design decision in Param's typing approach is that types are inferred from the Parameter declaration, not from a separate type annotation. This means the same constructor call you already write for runtime validation also communicates intent to your type checker.
The inference works at two levels:
param.Stringimpliesstr,param.Integerimpliesint,param.Numberimpliesint | float, and so on.allow_None=TrueaddsNoneto the union.item_type=stron aListnarrows the element type.class_=MyModelon aClassSelectorpins the type toMyModel.Under the hood, this works through Python's overload mechanism.
Parameteris now a generic class —Parameter[_T]— and each subclass defines@overloadsignatures that perform type narrowing based on the keyword arguments present in the call. This is purely a static analysis mechanism: no runtime behavior changes.The
allow_NonePatternThe
allow_Noneargument deserves special attention because it is the most common way to control nullability. The inference rules are consistent:param.Integer()intparam.Integer(allow_None=False)intparam.Integer(allow_None=True)int | Noneparam.Number()int | floatparam.Number(allow_None=True)int | float | Noneparam.String()strparam.String(allow_None=True)str | NoneThe default for
allow_Nonevaries by Parameter subclass, so when in doubt, be explicit. Being explicit aboutallow_Nonealso serves as useful documentation for anyone reading your class definition.Listwithitem_typeA
param.Listwithoutitem_typeis inferred aslist[Any]. Providingitem_typenarrows the element type:ClassSelectorwithclass_For
ClassSelector, theclass_argument determines the inferred type. Combined withallow_None, it cleanly expresses optional object references:py.typed Marker
Param 2.4.0 ships a
py.typedmarker file in theparampackage, as specified in PEP 561. This signals to type checkers and build tools that Param provides its own inline type annotations and that no separate type stubs are needed. You do not need to install anyparam-stubspackage or configure anything manually — the types are bundled with the library.Type Checker Compatibility
Python's type checking ecosystem has matured considerably and now includes several competing tools with different strengths. Param 2.4.0 is verified against four of them in CI:
mypy is the original Python type checker and remains the most widely used, particularly in CI pipelines and pre-commit hooks. Param passes mypy's strict checking.
pyright is Microsoft's type checker, which powers the Pylance language server in VSCode. It is the primary target for Param's type annotations. If you or your users develop in VSCode, correct inference will surface automatically without any extra configuration.
pyrefly is a new type checker from Meta, written in Rust and still in beta. It ships its own language server and is focused on performance at scale.
ty is a new type checker from the Astral team (the authors of
ruffanduv), also written in Rust and still in beta.If you are developing a library built on Param, the recommendation is to use pyright as your primary type checker. Param's annotations are optimized for pyright first, and since it is the checker most users encounter implicitly through their editor, correct inference there benefits the widest audience.
Limitations
Type narrowing in Python's type system has real limits, and it is worth being honest about what Param's typing cannot do today.
SelectorandLiteralTypesThe most significant limitation involves
Selectorparameters with a fixed set of objects. Ideally,param.Selector(objects=["train", "eval"])would inferLiteral["train", "eval"]. Unfortunately, Python's type system does not currently support narrowing a generic type parameter based on the runtime values of a list argument, only based on the types of arguments.As a result,
Selectorcurrently infersAny:The workaround is to add a redundant explicit annotation with a
type: ignorecomment to suppress the assignment type mismatch:This is not elegant, and it is one of the reasons the typing story for Param is not finished with this release.
Future Direction
The typing approach in 2.4.0, inferring types from constructor arguments, is a significant improvement over the untyped state before it, but it has an inherent ceiling. The overload-based narrowing cannot express everything a developer might want to communicate.
The next step is annotation-first parameter declarations, which is being prototyped in PR #1066 and is planned for Param 3.0. The idea is to allow defining parameters using standard Python type annotations, similar to how dataclasses and Pydantic models work:
In this model, the type annotation is the source of truth for static analysis, and Param uses it to configure the parameter's runtime behavior. This eliminates the
Selector/Literalworkaround, removes the need for overloads in Param's internals, and aligns Param with the conventions that Python developers already know from dataclasses and Pydantic.The migration path from the current style to this new style is designed to be straightforward, so existing code will continue to work.
Practical Recommendations
If you are updating an existing Param-based codebase to take advantage of the new typing, here is what we recommend:
Integer,String,ClassSelector, etc.) instead of the baseParameterclass wherever possible. The more specific the subclass, the better the inferred type.allow_Noneexplicitly on parameters where nullability matters. This improves both runtime safety and the precision of the inferred type.Listparameters, provideitem_typewhenever the list is homogeneous. This is good practice regardless of typing, since it adds a free runtime validation.ClassSelectorparameters, always provideclass_. This is already common practice for correctness; it now also benefits type inference.Selectorparameters with a known fixed set of values, add an explicitLiteralannotation withtype: ignore[assignment]for now. This is a temporary workaround until Param 3.0.Run pyright alongside your tests. It is the checker most likely to benefit your users if you are building a library on top of Param, and catching type errors at development time is meaningfully cheaper than catching them at runtime.
Changelog
For the full list of changes, see the Param 2.4.0 changelog.
🚀 Features
Parametergeneric (Parameter[_T]) and add overloads for type narrowingpy.typedPEP 561 marker to theparampackageallow_None,item_type,class_, andboundsconstructor argumentstests/assert_types.pywithassert_type()assertions verified in CImypy,pyright,pyrefly, andty🐛 Bug Fixes
UndefinedTypesentinel from a custom class instance to anenum.Enummember for improved compatibility with type checkers