Library / SDK
holoviz/param avatar
holoviz/param

Param gives Python classes declarative attributes and a reactive API

Declarative parameters for robust Python classes and a rich API for reactive programming

521 stars89 forksPythonBSD-3-Clause

At a glance

What is it?
Param is a zero-dependency Python library that does two things: it lets you declare class attributes as rich Parameter objects with types, bounds, defaults and docstrings, and it provides composable APIs for reactive programming so that dependent attributes update automatically and the dependencies can be introspected by other frameworks. It is BSD-3-Clause licensed, requires Python 3.11 or later, and is the backbone of HoloViz's Panel and HoloViews.
Who is it for?
Param suits a Python developer building a library whose objects need both validated attributes and a way for a surrounding framework to observe changes, since the reactive API is introspectable rather than private, which is what lets Panel build a user interface over an ordinary Parameterized class.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The two halves of Param

Param is described as a zero-dependency Python library that provides two main features, and the readme is careful to present them as a pair rather than as a list of conveniences.

The first is easily creating classes with rich, declarative attributes, Parameter objects, that include extended metadata for various purposes such as runtime type and range validation, documentation strings, default values or factories, nullability. The readme says that in this sense Param is conceptually similar to libraries like Pydantic, Python's dataclasses, or Traitlets.

The second is a suite of expressive and composable APIs for reactive programming, enabling automatic updates on attribute changes, and declaring complex reactive dependencies and expressions that can be introspected by other frameworks to implement their own reactive workflows.

That last clause is the one that separates Param from the libraries it resembles. Pydantic, dataclasses and Traitlets are about the attributes themselves. Param's reactive layer is not a private mechanism for its own use: the dependency declarations are data, and another framework can read them and build its own behaviour on top. That is why it works as the backbone of HoloViz's Panel and HoloViews libraries, powering their rich interactivity and data-driven workflows.

The readme puts it plainly: this combination of rich attributes and reactive APIs makes Param a solid foundation for constructing user interfaces, graphical applications and responsive systems where data integrity and automatic synchronization are paramount.

The primary language is Python, the licence is BSD-3-Clause, the homepage is param.holoviz.org, and the last push was on 2026-10-17, with the latest release v2.4.2 from 2026-09-03.

Declaring attributes with Parameter objects

Param lets you create classes and declare facts about each of their attributes through rich Parameter objects, and then Param handles runtime attribute validation, type checking and range validation, plus documentation and serialization.

The example in the readme is a Processor class with three attributes:

python
class Processor(param.Parameterized):

    # An Integer parameter that allows only integer values between 0 and 10,
    # has a default of 3, and a custom docstring.
    retries = param.Integer(default=3, bounds=(0, 10), doc="Retry attempts.")

    # A Boolean parameter, False by default.
    verbose = param.Boolean(default=False, doc="Emit progress messages.")

    # A Selector parameter, with only two allowed values "fast" and "accurate",
    # "fast" by default.
    mode = param.Selector(
        default="fast", objects=["fast", "accurate"],
        doc="Execution strategy."
    )

Each declaration carries three kinds of information at once: the type constraint, the accepted values or range, and the documentation. The doc string is not a comment, it is metadata on the parameter, which is what lets a framework generate a user interface or a documentation page from the class without asking the author to write it twice.

The validation is enforced at assignment time rather than at class definition time. Setting retries to 42 raises, and the readme shows the error message: Integer parameter 'Processor.retries' must be at most 10, not 42.

Two access patterns are shown for reaching the parameter objects themselves. The .param namespace allows you to get a hold on Parameter objects via indexing or attribute access, so processor.param['mode'].objects and processor.param.mode.objects both give the list of allowed values.

The namespace also offers many more useful methods, such as .update() to update multiple parameters at once, and .values() to obtain a dict of parameter name to parameter value. The instance itself behaves as a usual instance of a class, and Parameterized implements a nice and simple repr, so a processor prints as Processor(mode='accurate', name='Processor00042', retries=3, verbose=False).

Reactivity, and the depends decorator

The reactive side is what the other half of the library is for, and the readme's first example shows both features in about fifteen lines.

python
class UserForm(param.Parameterized):
    age = param.Integer(bounds=(0, None), doc='User age')
    name = param.String(doc='User name')
    submit = param.Event()

    @param.depends('submit', watch=True)
    def save_user_to_db(self):
        print(f'Saving user to db: name={self.name}, age={self.age}')
        ...

The mechanism is the depends decorator with watch=True, which declares that the method should be called automatically when the value of the submit attribute changes. The submit attribute is declared as an Event parameter, which exists to simulate a button in a user interface, and setting it triggers the method:

python
user.submit = True  # => Saving user to db: name=Bob, age=25

The Event type is the small detail that makes this work as a UI primitive. An ordinary Boolean parameter set to True would stay True, and nothing would fire a second time, so a button that can be pressed again is not representable. An Event is a value that is consumed on read, which is exactly the semantics a button needs, and the same reasoning applies to a file dialog or a checkbox in a panel.

The reactive API is not limited to methods that watch a single attribute. The readme describes declaring complex reactive dependencies and expressions, and the point of the design is that those declarations are introspectable. A framework that wants to know what a class depends on can ask, rather than having to monkey-patch or reimplement the dependency tracking.

That is why the library describes the APIs as expressive and composable rather than as a mechanism for automatic updates. The automatic update is the simple case; the introspectability is the feature that lets Panel build a user interface that shows a slider, a text field and a plot which are all connected through the dependency graph of an ordinary Python object.

Installing it and what the packaging says

Param is published on PyPI and on conda-forge, and the readme's badges show versions for PyPI, conda-forge, the conda defaults channel and a pyviz channel, so it is available from the usual places.

The pyproject.toml shows that the project requires Python 3.11 or later, and the classifiers list 3.11 through 3.15, so it tracks new Python releases closely. The build backend is hatchling with hatch-vcs, and the version is dynamic, which means it is derived from the version control tags rather than written by hand.

The dependency story is the headline feature, because there are no runtime dependencies at all. The project name is param and the description is Declarative parameters for Python classes and a rich API for reactive programming.

What is in the pyproject.toml instead is an unusually thorough set of optional dependency groups, and they describe how the library is tested. The tests group pulls pytest, pytest-asyncio and pytest-cov. The tests-deser group adds xlrd, openpyxl, odfpy, pyarrow and tables, which is a list of spreadsheet and columnar file formats, so that is the deserialization test suite. The tests-examples group adds pytest-xdist and nbval, and nbval validates Jupyter notebooks, which is how the documentation examples are kept honest. The tests-pypy group is described as a set of dependencies that work with pypy and are quick to install, with cloudpickle, ipython, jsonschema, nest-asyncio and numpy, so that is the alternative interpreter run. The tests-full group combines them with numpy and pandas.

That structure is a reasonable answer to a problem libraries usually ignore: Param has a large surface, and testing it under an alternative interpreter, against notebooks, and against deserialization of half a dozen file formats are separate jobs with separate dependency needs.

The extras also carry aiohttp, pandas and panel in the examples group, which is how the Panel integration is exercised.

The repository root shows the rest: a benchmarks/ directory, a binder/ directory for the runnable notebook environment, a doc/ directory, a numbergen/ directory, the param/ package itself, a tests/ directory, scripts/, a pixi.toml for the development environment and a .pre-commit-config.yaml.

Where Param fits, and where it stops

The comparison worth making is with Pydantic and with a hand-rolled reactive system, and the readme addresses the first one directly.

The readme says that runtime attribute validation is a great feature that helps build defendable code bases, and that alternative libraries like Pydantic and others excel at input validation, and that if this is only what you need, you should probably look into them. Where Param shines is the reactive half.

That is an honest positioning statement, and it is worth taking seriously rather than treating Param as a Pydantic replacement. Pydantic is excellent at what it does, it is far more widely adopted, and if your requirement is validated data at a system boundary, Pydantic is the better-supported choice. Param earns its place when you need both validated attributes and a dependency graph that another framework can walk.

The Panel and HoloViews relationship is the proof of that. A Panel application is built from a Parameterized object, and the panel widgets are generated from the parameter declarations, the callbacks are generated from the dependency declarations, and the underlying class stays a plain Python object that can be tested without a browser. Param is not making the framework, it is making the framework possible without giving up ordinary Python objects.

The limits are worth naming. The reactive API is built around attribute dependencies rather than around a general expression language, so if you want arbitrary reactive expressions with operators and streams, this is not that library. Parameterized subclasses carry conventions of their own, around naming, around how instances compare and around what happens when you subclass, and those conventions are documented but they are conventions you adopt rather than things you ignore. The library also has a substantial API surface, and the readme itself notes that there are great introductions and overviews available elsewhere, on the HoloViz Discourse, on YouTube and in the docs, which is a way of saying that the reference documentation is larger than a README.

The zero-dependency constraint has a second-order effect worth understanding. It means Param does not pull in anything from the scientific Python stack, so it can sit at the bottom of a dependency tree without dragging a numerical stack along with it, and it means the reactive and validation machinery is implemented from scratch rather than adapted from NumPy or pandas.

On maintenance, the picture is strong. The releases are v2.4.2 from 2026-09-03, v2.4.1 and v2.4.0 both from 2026-06-09, the last push was on 2026-10-17, the project is classified as Development Status 5, Production/Stable, and it is Typed, so the type annotations ship with the package.

Editorial conclusion

Param suits a Python developer building a library whose objects need both validated attributes and a way for a surrounding framework to observe changes, since the reactive API is introspectable rather than private, which is what lets Panel build a user interface over an ordinary Parameterized class. It is the wrong tool if you only need input validation, because the README itself points you at Pydantic for that, and it is the wrong tool if you want a general reactive programming system with its own operators, since Param's reactivity is built around attribute dependencies. Before adopting it, read the class you intend to subclass carefully, because Parameterized subclasses have their own conventions around instantiation and equality.

Frequently asked questions

What is Param?

It is a zero-dependency Python library with two main features: easily creating classes with rich, declarative attributes, Parameter objects, carrying metadata for runtime type and range validation, documentation strings, defaults or factories and nullability, and a suite of expressive and composable APIs for reactive programming, enabling automatic updates on attribute changes and declaring dependencies that can be introspected by other frameworks.

How do I declare an attribute with Param?

Subclass param.Parameterized and assign a Parameter object as a class attribute, for example retries = param.Integer(default=3, bounds=(0, 10), doc="Retry attempts.") or mode = param.Selector(default="fast", objects=["fast", "accurate"], doc="Execution strategy."). Assigning a value outside the declared range raises, for example Integer parameter 'Processor.retries' must be at most 10, not 42.

How does Param implement reactive behaviour?

With the depends decorator. A method decorated with @param.depends('submit', watch=True) is called automatically when the watched attribute changes, and an Event parameter such as submit is used to model a button, since its value is consumed on read so it can fire again. The dependency declarations are data that other frameworks can introspect to build their own reactive workflows.

Which Python versions does Param support?

Python 3.11 or later, with the classifiers listing 3.11 through 3.15. The version is dynamic, derived from version control tags by hatch-vcs, and the project has no runtime dependencies at all.

Should I use Param or Pydantic?

If you only need input validation the README recommends Pydantic, which excels at it. Param earns its place when you need rich declarative attributes together with reactive dependencies that a framework such as Panel or HoloViews can introspect to build a user interface over an ordinary Python object.

How do you access a Parameter object from an instance?

Through the .param namespace, by indexing or attribute access. Both processor.param['mode'].objects and processor.param.mode.objects return the allowed values of a Selector. The namespace also offers .update() to set several parameters at once and .values() to get a dict of parameter name to parameter value.

Official sources

  1. holoviz/param on GitHub
  2. License: BSD-3-Clause
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/holoviz-param.svg)](https://hysenlabs.com/projects/holoviz-param)

Was this useful?