From c8038653bc8f16e595d58ac530948b115fb197cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Fri, 13 Dec 2024 12:14:33 +0100 Subject: [PATCH 01/11] playing around with types for parameters --- param/parameterized.py | 11 ++++++++--- param/parameters.py | 36 ++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/param/parameterized.py b/param/parameterized.py index ac0df0343..f8fdf545e 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -29,6 +29,7 @@ from itertools import chain from operator import itemgetter, attrgetter from types import FunctionType, MethodType +from typing import TypeVar, Generic from contextlib import contextmanager from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL @@ -52,6 +53,8 @@ gen_types, ) +T = TypeVar("T") + # Ideally setting param_pager would be in __init__.py but param_pager is # needed on import to create the Parameterized class, so it'd need to precede # importing parameterized.py in __init__.py which would be a little weird. @@ -1005,7 +1008,9 @@ def _sorter(p): cls.__signature__ = new_sig -class Parameter(_ParameterBase): + + +class Parameter(Generic[T], _ParameterBase): """ An attribute descriptor for declaring parameters. @@ -1431,7 +1436,7 @@ def _update_state(self): values, after the slot values have been set in the inheritance procedure. """ - def __get__(self, obj, objtype): # pylint: disable-msg=W0613 + def __get__(self, obj, objtype) -> T: """ Return the value for this Parameter. @@ -1455,7 +1460,7 @@ def __get__(self, obj, objtype): # pylint: disable-msg=W0613 return result @instance_descriptor - def __set__(self, obj, val): + def __set__(self, obj, val: T) -> None: """ Set the value for this Parameter. diff --git a/param/parameters.py b/param/parameters.py index 0259e2ae0..1f74dd421 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -36,7 +36,7 @@ from .parameterized import ( Parameterized, Parameter, ParameterizedFunction, ParamOverrides, String, Undefined, get_logger, instance_descriptor, _dt_types, - _int_types, _identity_hook + _int_types, _identity_hook, T ) from ._utils import ( ParamFutureWarning as _ParamFutureWarning, @@ -451,7 +451,7 @@ def __exit__(self, exc, *args): #----------------------------------------------------------------------------- -class Dynamic(Parameter): +class Dynamic(Parameter[T]): """ Parameter whose value can be generated dynamically by a callable object. @@ -481,7 +481,7 @@ class Dynamic(Parameter): @typing.overload def __init__( - self, default=None, *, + self, default: T = None, *, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, allow_None=False, per_instance=True, allow_refs=False, nested_refs=False @@ -515,7 +515,7 @@ def _initialize_generator(self,gen,obj=None): gen._saved_Dynamic_time = [] - def __get__(self,obj,objtype): + def __get__(self, obj, objtype) -> T: """ Call the superclass's __get__; if the result is not dynamic return that result, otherwise ask that result to produce a @@ -530,7 +530,7 @@ def __get__(self,obj,objtype): @instance_descriptor - def __set__(self,obj,val): + def __set__(self, obj, val: T) -> None: """ Call the superclass's set and keep this parameter's instantiate value up to date (dynamic parameters @@ -624,7 +624,7 @@ def sig(self): _compute_set_hook = __compute_set_hook() -class Number(Dynamic): +class Number(Dynamic[T]): """ A numeric Dynamic Parameter, with a default value and optional bounds. @@ -679,7 +679,7 @@ class Number(Dynamic): @typing.overload def __init__( self, - default=0.0, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None, + default: T = 0.0, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None, allow_None=False, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -702,7 +702,7 @@ def __init__(self, default=Undefined, *, bounds=Undefined, softbounds=Undefined, self.step = step self._validate(self.default) - def __get__(self, obj, objtype): + def __get__(self, obj, objtype) -> T: """ Same as the superclass's __get__, but if the value was dynamically generated, check the bounds. @@ -838,7 +838,7 @@ def __setstate__(self,state): -class Integer(Number): +class Integer(Number[int]): """Numeric Parameter required to be an Integer""" _slot_defaults = dict(Number._slot_defaults, default=0) @@ -887,7 +887,7 @@ def __init__(self, default=Undefined, *, bounds=Undefined, softbounds=Undefined, ) -class Date(Number): +class Date(Number[T]): """Date parameter of datetime or date type.""" _slot_defaults = dict(Number._slot_defaults, default=None) @@ -895,7 +895,7 @@ class Date(Number): @typing.overload def __init__( self, - default=None, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None, + default: T = None, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, allow_None=False, per_instance=True, allow_refs=False, nested_refs=False @@ -1000,7 +1000,7 @@ def deserialize(cls, value): # Boolean #----------------------------------------------------------------------------- -class Boolean(Parameter): +class Boolean(Parameter[bool]): """Binary or tristate Boolean Parameter.""" _slot_defaults = dict(Parameter._slot_defaults, default=False) @@ -1099,7 +1099,7 @@ def _reset_event(self, obj, val): self._post_setter(obj, val) @instance_descriptor - def __set__(self, obj, val): + def __set__(self, obj, val: T) -> None: if self._mode in ['set-reset', 'set']: super().__set__(obj, val) if self._mode in ['set-reset', 'reset']: @@ -1546,7 +1546,7 @@ def __init__(self, *, attribs=Undefined, **kw): super().__init__(default=Undefined, **kw) self.attribs = attribs - def __get__(self, obj, objtype): + def __get__(self, obj, objtype) -> T: """Return the values of all the attribs, as a list.""" if obj is None: return [getattr(objtype, a) for a in self.attribs] @@ -1576,7 +1576,7 @@ def _post_setter(self, obj, val): # Selector #----------------------------------------------------------------------------- -class SelectorBase(Parameter): +class SelectorBase(Parameter[T]): """ Parameter whose value must be chosen from a list of possibilities. @@ -2136,7 +2136,7 @@ def get_range(self): return _abbreviate_paths(self.path,super().get_range()) -class ClassSelector(SelectorBase): +class ClassSelector(SelectorBase[T]): """ Parameter allowing selection of either a subclass or an instance of a class or tuple of classes. By default, requires an instance, but if is_instance=False, accepts a class instead. @@ -2151,7 +2151,7 @@ class ClassSelector(SelectorBase): @typing.overload def __init__( self, - *, class_, default=None, instantiate=True, is_instance=True, + *, class_, default: T = None, instantiate=True, is_instance=True, allow_None=False, doc=None, label=None, precedence=None, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -2743,7 +2743,7 @@ def _validate(self, val): if self.check_exists: raise OSError(e.args[0]) from None - def __get__(self, obj, objtype): + def __get__(self, obj, objtype) -> T: """Return an absolute, normalized path (see resolve_path).""" raw_path = super().__get__(obj,objtype) if raw_path is None: From 8f46f78d77de459209ec41d5be9fd66d46f26e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Fri, 13 Dec 2024 13:12:22 +0100 Subject: [PATCH 02/11] Fix failing test_signature_parameters_constructors_overloaded_updated_match --- param/parameters.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/param/parameters.py b/param/parameters.py index 1f74dd421..765dbb0e4 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -488,7 +488,7 @@ def __init__( ): ... - def __init__(self, default=Undefined, **params): + def __init__(self, default: T = Undefined, **params): """ Call the superclass's __init__ and set instantiate=True if the default is dynamic. @@ -687,7 +687,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, bounds=Undefined, softbounds=Undefined, + def __init__(self, default: T =Undefined, *, bounds=Undefined, softbounds=Undefined, inclusive_bounds=Undefined, step=Undefined, set_hook=Undefined, **params): """ Initialize this parameter object and store the bounds. @@ -902,7 +902,7 @@ def __init__( ): ... - def __init__(self, default=Undefined, **kwargs): + def __init__(self, default: T = Undefined, **kwargs): super().__init__(default=default, **kwargs) def _validate_value(self, val, allow_None): @@ -2159,7 +2159,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, *, class_, default=Undefined, instantiate=Undefined, is_instance=Undefined, **params): + def __init__(self, *, class_, default: T = Undefined, instantiate=Undefined, is_instance=Undefined, **params): self.class_ = class_ self.is_instance = is_instance super().__init__(default=default,instantiate=instantiate,**params) From e74ae5921b5854d470e78af8b6f0848e9c8921fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Fri, 13 Dec 2024 13:31:28 +0100 Subject: [PATCH 03/11] Update list --- param/parameters.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/param/parameters.py b/param/parameters.py index 765dbb0e4..a83f3e771 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -2444,7 +2444,7 @@ def _validate(self, val): # List #----------------------------------------------------------------------------- -class List(Parameter): +class List(Parameter[T]): """ Parameter whose value is a list of objects, usually of a specified type. @@ -2467,7 +2467,7 @@ class List(Parameter): @typing.overload def __init__( self, - default=[], *, class_=None, item_type=None, instantiate=True, bounds=(0, None), + default: T = [], *, class_=None, item_type=None, instantiate=True, bounds=(0, None), allow_None=False, doc=None, label=None, precedence=None, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -2475,7 +2475,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, class_=Undefined, item_type=Undefined, + def __init__(self, default: T = Undefined, *, class_=Undefined, item_type=Undefined, instantiate=Undefined, bounds=Undefined, **params): if class_ is not Undefined: # PARAM3_DEPRECATION From 5915224f93660f8bcddde1778dce9e0bb3150628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Mon, 16 Dec 2024 14:48:58 +0100 Subject: [PATCH 04/11] Add more typevar T to classes --- param/parameterized.py | 6 +-- param/parameters.py | 114 ++++++++++++++++++++--------------------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/param/parameterized.py b/param/parameterized.py index f8fdf545e..8844d238d 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -1608,7 +1608,7 @@ def __setstate__(self,state): # Define one particular type of Parameter that is used in this file -class String(Parameter): +class String(Parameter[T]): r""" A String Parameter, with a default value and optional regular expression (regex) matching. @@ -1629,7 +1629,7 @@ def __init__(self, default="0.0.0.0", allow_None=False, **kwargs): @typing.overload def __init__( self, - default="", *, regex=None, + default: T = "", *, regex=None, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, allow_None=False, per_instance=True, allow_refs=False, nested_refs=False @@ -1637,7 +1637,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, regex=Undefined, **kwargs): + def __init__(self, default: T = Undefined, *, regex=Undefined, **kwargs): super().__init__(default=default, **kwargs) self.regex = regex self._validate(self.default) diff --git a/param/parameters.py b/param/parameters.py index a83f3e771..62ac8891b 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -864,7 +864,7 @@ def _validate_step(self, val, step): ) -class Magnitude(Number): +class Magnitude(Number[T]): """Numeric Parameter required to be in the range [0.0-1.0].""" _slot_defaults = dict(Number._slot_defaults, default=1.0, bounds=(0.0,1.0)) @@ -872,14 +872,14 @@ class Magnitude(Number): @typing.overload def __init__( self, - default=1.0, *, bounds=(0.0, 1.0), softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None, + default: T = 1.0, *, bounds=(0.0, 1.0), softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None, allow_None=False, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False ): ... - def __init__(self, default=Undefined, *, bounds=Undefined, softbounds=Undefined, + def __init__(self, default: T = Undefined, *, bounds=Undefined, softbounds=Undefined, inclusive_bounds=Undefined, step=Undefined, set_hook=Undefined, **params): super().__init__( default=default, bounds=bounds, softbounds=softbounds, @@ -946,7 +946,7 @@ def deserialize(cls, value): return dt.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f") -class CalendarDate(Number): +class CalendarDate(Number[T]): """Parameter specifically allowing dates (not datetimes).""" _slot_defaults = dict(Number._slot_defaults, default=None) @@ -954,14 +954,14 @@ class CalendarDate(Number): @typing.overload def __init__( self, - default=None, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None, + default: T = None, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, allow_None=False, per_instance=True, allow_refs=False, nested_refs=False ): ... - def __init__(self, default=Undefined, **kwargs): + def __init__(self, default: T = Undefined, **kwargs): super().__init__(default=default, **kwargs) def _validate_value(self, val, allow_None): @@ -1000,7 +1000,7 @@ def deserialize(cls, value): # Boolean #----------------------------------------------------------------------------- -class Boolean(Parameter[bool]): +class Boolean(Parameter[T]): """Binary or tristate Boolean Parameter.""" _slot_defaults = dict(Parameter._slot_defaults, default=False) @@ -1008,7 +1008,7 @@ class Boolean(Parameter[bool]): @typing.overload def __init__( self, - default=False, *, + default: T = False, *, allow_None=False, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -1016,7 +1016,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, **params): + def __init__(self, default: T = Undefined, **params): super().__init__(default=default, **params) self._validate(self.default) @@ -1037,7 +1037,7 @@ def _validate(self, val): self._validate_value(val, self.allow_None) -class Event(Boolean): +class Event(Boolean[T]): """ An Event Parameter is one whose value is intimately linked to the triggering of events for watchers to consume. Event has a Boolean @@ -1061,7 +1061,7 @@ class Event(Boolean): @typing.overload def __init__( self, - default=False, *, + default: T = False, *, allow_None=False, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -1069,7 +1069,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self,default=False,**params): + def __init__(self, default: T = False, **params): self._autotrigger_value = True self._autotrigger_reset_value = False self._mode = 'set-reset' @@ -1124,7 +1124,7 @@ def sig(self): _compute_length_of_default = __compute_length_of_default() -class Tuple(Parameter): +class Tuple(Parameter[T]): """A tuple Parameter (e.g. ('a',7.6,[3,5])) with a fixed tuple length.""" __slots__ = ['length'] @@ -1134,7 +1134,7 @@ class Tuple(Parameter): @typing.overload def __init__( self, - default=(0,0), *, length=None, + default: T = (0,0), *, length=None, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, allow_None=False, per_instance=True, allow_refs=False, nested_refs=False @@ -1142,7 +1142,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, length=Undefined, **params): + def __init__(self, default: T = Undefined, *, length=Undefined, **params): """ Initialize a tuple parameter with a fixed length (number of elements). The length is determined by the initial default @@ -1198,7 +1198,7 @@ def deserialize(cls, value): return tuple(value) # As JSON has no tuple representation -class NumericTuple(Tuple): +class NumericTuple(Tuple[T]): """A numeric tuple Parameter (e.g. (4.5,7.6,3)) with a fixed tuple length.""" def _validate_value(self, val, allow_None): @@ -1214,7 +1214,7 @@ def _validate_value(self, val, allow_None): ) -class XYCoordinates(NumericTuple): +class XYCoordinates(NumericTuple[T]): """A NumericTuple for an X,Y coordinate.""" _slot_defaults = dict(NumericTuple._slot_defaults, default=(0.0, 0.0)) @@ -1222,18 +1222,18 @@ class XYCoordinates(NumericTuple): @typing.overload def __init__( self, - default=(0.0, 0.0), *, length=None, + default: T = (0.0, 0.0), *, length=None, allow_None=False, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False ): ... - def __init__(self, default=Undefined, **params): + def __init__(self, default: T = Undefined, **params): super().__init__(default=default, length=2, **params) -class Range(NumericTuple): +class Range(NumericTuple[T]): """A numeric range with optional bounds and softbounds.""" __slots__ = ['bounds', 'inclusive_bounds', 'softbounds', 'step'] @@ -1246,7 +1246,7 @@ class Range(NumericTuple): @typing.overload def __init__( self, - default=None, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, length=None, + default: T = None, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, length=None, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, allow_None=False, per_instance=True, allow_refs=False, nested_refs=False @@ -1254,7 +1254,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, bounds=Undefined, softbounds=Undefined, + def __init__(self, default: T = Undefined, *, bounds=Undefined, softbounds=Undefined, inclusive_bounds=Undefined, step=Undefined, **params): self.bounds = bounds self.inclusive_bounds = inclusive_bounds @@ -1464,7 +1464,7 @@ def deserialize(cls, value): # Callable #----------------------------------------------------------------------------- -class Callable(Parameter): +class Callable(Parameter[T]): """ Parameter holding a value that is a callable object, such as a function. @@ -1477,7 +1477,7 @@ class Callable(Parameter): @typing.overload def __init__( self, - default=None, *, + default: T = None, *, allow_None=False, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -1485,7 +1485,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, **params): + def __init__(self, default: T = Undefined, **params): super().__init__(default=default, **params) self._validate(self.default) @@ -1811,7 +1811,7 @@ def _modified_slots_defaults(cls): return defaults -class Selector(SelectorBase, _SignatureSelector): +class Selector(SelectorBase[T], _SignatureSelector): """ Parameter whose value must be one object from a list of possible objects. @@ -1845,7 +1845,7 @@ class Selector(SelectorBase, _SignatureSelector): @typing.overload def __init__( self, - *, objects=[], default=None, instantiate=False, compute_default_fn=None, + *, objects=[], default: T = None, instantiate=False, compute_default_fn=None, check_on_set=None, allow_None=None, empty_default=False, doc=None, label=None, precedence=None, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -1855,7 +1855,7 @@ def __init__( # Selector is usually used to allow selection from a list of # existing objects, therefore instantiate is False by default. @_deprecate_positional_args - def __init__(self, *, objects=Undefined, default=Undefined, instantiate=Undefined, + def __init__(self, *, objects=Undefined, default: T = Undefined, instantiate=Undefined, compute_default_fn=Undefined, check_on_set=Undefined, allow_None=Undefined, empty_default=False, **params): @@ -1981,7 +1981,7 @@ def __init__(self, default=Undefined, *, objects=Undefined, **kwargs): empty_default=True, **kwargs) -class FileSelector(Selector): +class FileSelector(Selector[T]): """Given a path glob, allows one file to be selected from those matching.""" __slots__ = ['path'] @@ -1993,7 +1993,7 @@ class FileSelector(Selector): @typing.overload def __init__( self, - default=None, *, path="", objects=[], instantiate=False, compute_default_fn=None, + default: T =None, *, path="", objects=[], instantiate=False, compute_default_fn=None, check_on_set=None, allow_None=None, empty_default=False, doc=None, label=None, precedence=None, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -2001,7 +2001,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, path=Undefined, **kwargs): + def __init__(self, default: T = Undefined, *, path=Undefined, **kwargs): self.default = default self.path = path self.update(path=path) @@ -2032,7 +2032,7 @@ def get_range(self): return _abbreviate_paths(self.path,super().get_range()) -class ListSelector(Selector): +class ListSelector(Selector[T]): """ Variant of Selector where the value can be multiple objects from a list of possible objects. @@ -2041,7 +2041,7 @@ class ListSelector(Selector): @typing.overload def __init__( self, - default=None, *, objects=[], instantiate=False, compute_default_fn=None, + default: T =None, *, objects=[], instantiate=False, compute_default_fn=None, check_on_set=None, allow_None=None, empty_default=False, doc=None, label=None, precedence=None, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -2049,7 +2049,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, objects=Undefined, **kwargs): + def __init__(self, default: T = Undefined, *, objects=Undefined, **kwargs): super().__init__( objects=objects, default=default, empty_default=True, **kwargs) @@ -2090,7 +2090,7 @@ def _update_state(self): self._ensure_value_is_in_objects(o) -class MultiFileSelector(ListSelector): +class MultiFileSelector(ListSelector[T]): """Given a path glob, allows multiple files to be selected from the list of matches.""" __slots__ = ['path'] @@ -2102,7 +2102,7 @@ class MultiFileSelector(ListSelector): @typing.overload def __init__( self, - default=None, *, path="", objects=[], compute_default_fn=None, + default: T = None, *, path="", objects=[], compute_default_fn=None, check_on_set=None, allow_None=None, empty_default=False, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, @@ -2111,7 +2111,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, path=Undefined, **kwargs): + def __init__(self, default: T = Undefined, *, path=Undefined, **kwargs): self.default = default self.path = path self.update(path=path) @@ -2211,31 +2211,31 @@ class Dict(ClassSelector): @typing.overload def __init__( self, - default=None, *, is_instance=True, + default: T = None, *, is_instance=True, allow_None=False, doc=None, label=None, precedence=None, instantiate=True, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False ): ... - def __init__(self, default=Undefined, **params): + def __init__(self, default: T = Undefined, **params): super().__init__(default=default, class_=dict, **params) -class Array(ClassSelector): +class Array(ClassSelector[T]): """Parameter whose value is a numpy array.""" @typing.overload def __init__( self, - default=None, *, is_instance=True, + default: T = None, *, is_instance=True, allow_None=False, doc=None, label=None, precedence=None, instantiate=True, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False ): ... - def __init__(self, default=Undefined, **params): + def __init__(self, default: T = Undefined, **params): from numpy import ndarray super().__init__(default=default, class_=ndarray, **params) @@ -2259,7 +2259,7 @@ def deserialize(cls, value): return numpy.asarray(value) -class DataFrame(ClassSelector): +class DataFrame(ClassSelector[T]): """ Parameter whose value is a pandas DataFrame. @@ -2287,7 +2287,7 @@ class DataFrame(ClassSelector): @typing.overload def __init__( self, - default=None, *, rows=None, columns=None, ordered=None, is_instance=True, + default: T =None, *, rows=None, columns=None, ordered=None, is_instance=True, allow_None=False, doc=None, label=None, precedence=None, instantiate=True, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -2295,7 +2295,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, rows=Undefined, columns=Undefined, ordered=Undefined, **params): + def __init__(self, default: T = Undefined, *, rows=Undefined, columns=Undefined, ordered=Undefined, **params): from pandas import DataFrame as pdDFrame self.rows = rows self.columns = columns @@ -2385,7 +2385,7 @@ def deserialize(cls, value): return pandas.DataFrame(value) -class Series(ClassSelector): +class Series(ClassSelector[T]): """ Parameter whose value is a pandas Series. @@ -2403,7 +2403,7 @@ class Series(ClassSelector): @typing.overload def __init__( self, - default=None, *, rows=None, allow_None=False, is_instance=True, + default: T = None, *, rows=None, allow_None=False, is_instance=True, doc=None, label=None, precedence=None, instantiate=True, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -2411,7 +2411,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, rows=Undefined, allow_None=Undefined, **params): + def __init__(self, default: T = Undefined, *, rows=Undefined, allow_None=Undefined, **params): from pandas import Series as pdSeries self.rows = rows super().__init__(default=default, class_=pdSeries, allow_None=allow_None, @@ -2670,7 +2670,7 @@ def __call__(self,path="",**params): -class Path(Parameter): +class Path(Parameter[T]): """ Parameter that can be set to a string specifying the path of a file or folder. @@ -2708,7 +2708,7 @@ class Path(Parameter): @typing.overload def __init__( self, - default=None, *, search_paths=None, check_exists=True, + default: T = None, *, search_paths=None, check_exists=True, allow_None=False, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -2716,7 +2716,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, search_paths=Undefined, check_exists=Undefined, **params): + def __init__(self, default: T = Undefined, *, search_paths=Undefined, check_exists=Undefined, **params): if search_paths is Undefined: search_paths = [] @@ -2816,7 +2816,7 @@ def _resolve(self, path): # Color #----------------------------------------------------------------------------- -class Color(Parameter): +class Color(Parameter[T]): """ Color parameter defined as a hex RGB string with an optional # prefix or (optionally) as a CSS3 color name. @@ -2866,7 +2866,7 @@ class Color(Parameter): @typing.overload def __init__( self, - default=None, *, allow_named=True, + default: T = None, *, allow_named=True, allow_None=False, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -2874,7 +2874,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, allow_named=Undefined, **kwargs): + def __init__(self, default: T = Undefined, *, allow_named=Undefined, **kwargs): super().__init__(default=default, **kwargs) self.allow_named = allow_named self._validate(self.default) @@ -2912,7 +2912,7 @@ def _validate_allow_named(self, val, allow_named): # Bytes #----------------------------------------------------------------------------- -class Bytes(Parameter): +class Bytes(Parameter[T]): """ A Bytes Parameter, with a default value and optional regular expression (regex) matching. @@ -2931,7 +2931,7 @@ class Bytes(Parameter): @typing.overload def __init__( self, - default=b"", *, regex=None, allow_None=False, + default: T = b"", *, regex=None, allow_None=False, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -2939,7 +2939,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, default=Undefined, *, regex=Undefined, allow_None=Undefined, **kwargs): + def __init__(self, default: T = Undefined, *, regex=Undefined, allow_None=Undefined, **kwargs): super().__init__(default=default, **kwargs) self.regex = regex self._validate(self.default) From 694523812203d1d9304eda3599d85728a6b94751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Mon, 16 Dec 2024 14:57:58 +0100 Subject: [PATCH 05/11] Use T for Integer --- param/parameters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/param/parameters.py b/param/parameters.py index 62ac8891b..c896c6b0a 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -838,7 +838,7 @@ def __setstate__(self,state): -class Integer(Number[int]): +class Integer(Number[T]): """Numeric Parameter required to be an Integer""" _slot_defaults = dict(Number._slot_defaults, default=0) From 30f45acb3c5bbe803e4224a2add6142f1afcc7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Mon, 16 Dec 2024 16:19:13 +0100 Subject: [PATCH 06/11] Update __init__ for Integer --- param/parameters.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/param/parameters.py b/param/parameters.py index c896c6b0a..7b92f021b 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -15,6 +15,7 @@ parameter types (e.g. Number), and also imports the definition of Parameters and Parameterized classes. """ +from __future__ import annotations import collections import copy @@ -843,6 +844,19 @@ class Integer(Number[T]): _slot_defaults = dict(Number._slot_defaults, default=0) + @typing.overload + def __init__( + self, + default: T = 0, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None, + allow_None=False, doc=None, label=None, precedence=None, instantiate=False, + constant=False, readonly=False, pickle_default_value=True, per_instance=True, + allow_refs=False, nested_refs=False + ): + ... + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + def _validate_value(self, val, allow_None): if callable(val): return From 1d2ed5fb496a50b05df1eed7d866c7a91c1ec927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Mon, 16 Dec 2024 16:19:45 +0100 Subject: [PATCH 07/11] Add type_information to ClassSelector --- param/parameters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/param/parameters.py b/param/parameters.py index 7b92f021b..131d760a3 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -2165,7 +2165,7 @@ class ClassSelector(SelectorBase[T]): @typing.overload def __init__( self, - *, class_, default: T = None, instantiate=True, is_instance=True, + *, class_: tuple[type[T], ...] | type[T], default: T = None, instantiate=True, is_instance=True, allow_None=False, doc=None, label=None, precedence=None, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False @@ -2173,7 +2173,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, *, class_, default: T = Undefined, instantiate=Undefined, is_instance=Undefined, **params): + def __init__(self, *, class_: tuple[type[T]] | type[T], default: T = Undefined, instantiate=Undefined, is_instance=Undefined, **params): self.class_ = class_ self.is_instance = is_instance super().__init__(default=default,instantiate=instantiate,**params) From a2bd29b2144600bb32441064356a88252f488753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Mon, 16 Dec 2024 16:40:19 +0100 Subject: [PATCH 08/11] fix tests --- param/parameters.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/param/parameters.py b/param/parameters.py index 131d760a3..508159ac4 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -854,8 +854,8 @@ def __init__( ): ... - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, default: T = Undefined, *args, **kwargs): + super().__init__(default=default, *args, **kwargs) def _validate_value(self, val, allow_None): if callable(val): @@ -2173,7 +2173,7 @@ def __init__( ... @_deprecate_positional_args - def __init__(self, *, class_: tuple[type[T]] | type[T], default: T = Undefined, instantiate=Undefined, is_instance=Undefined, **params): + def __init__(self, *, class_: tuple[type[T], ...] | type[T], default: T = Undefined, instantiate=Undefined, is_instance=Undefined, **params): self.class_ = class_ self.is_instance = is_instance super().__init__(default=default,instantiate=instantiate,**params) From 8d72d1fd61767c23805fc74354c4a04dcbd97cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Tue, 17 Jun 2025 11:34:52 +0200 Subject: [PATCH 09/11] Dirty hack for setting non value --- param/parameters.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/param/parameters.py b/param/parameters.py index 508159ac4..2a4b8b8a6 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -877,6 +877,11 @@ def _validate_step(self, val, step): f"None or an integer value, not {type(step)}." ) + def __set__(self, obj, val: T | int) -> None: + if typing.TYPE_CHECKING: + val = typing.cast("T", val) + super().__set__(obj, val) + class Magnitude(Number[T]): """Numeric Parameter required to be in the range [0.0-1.0].""" @@ -1526,7 +1531,7 @@ class Action(Callable): # Composite #----------------------------------------------------------------------------- -class Composite(Parameter): +class Composite(Parameter[T]): """ A Parameter that is a composite of a set of other attributes of the class. From 6bd00461a93ac6ed9d8f3b5fc833ec38d665c02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Tue, 1 Jul 2025 17:26:21 +0200 Subject: [PATCH 10/11] Fix lint --- param/parameterized.py | 2 +- param/parameters.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/param/parameterized.py b/param/parameterized.py index e8b4dfbcc..246788761 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -27,7 +27,7 @@ from itertools import chain from operator import itemgetter, attrgetter from types import FunctionType, MethodType -from typing import Any, Union, Literal # When python 3.9 support is dropped replace Union with | +from typing import Any, Union, Literal, Generic, TypeVar # When python 3.9 support is dropped replace Union with | from contextlib import contextmanager CRITICAL = 50 diff --git a/param/parameters.py b/param/parameters.py index 38a40b7a7..66b1da965 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -849,7 +849,7 @@ def __setstate__(self,state): class Integer(Number[T]): - """Numeric Parameter required to be an Integer""" + """Numeric Parameter required to be an Integer.""" _slot_defaults = dict(Number._slot_defaults, default=0) From 205a9ad0da29c841380d25d41a095797f00d391e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Wed, 2 Jul 2025 10:16:08 +0200 Subject: [PATCH 11/11] Playing around --- param/parameterized.py | 4 +-- param/parameters.py | 61 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/param/parameterized.py b/param/parameterized.py index 246788761..465747d99 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -231,7 +231,7 @@ def _identity_hook(obj, val): return val -class _Undefined: +class _TUndefined: """ Dummy value to signal completely undefined values rather than simple None values. @@ -247,7 +247,7 @@ def __repr__(self): return '' -Undefined = _Undefined() +Undefined = _TUndefined() @contextmanager diff --git a/param/parameters.py b/param/parameters.py index 66b1da965..099232984 100644 --- a/param/parameters.py +++ b/param/parameters.py @@ -37,7 +37,7 @@ from .parameterized import ( Parameterized, Parameter, ParameterizedFunction, ParamOverrides, String, Undefined, get_logger, instance_descriptor, _dt_types, - _int_types, _identity_hook, T + _int_types, _identity_hook, T, _TUndefined ) from ._utils import ( ParamFutureWarning as _ParamFutureWarning, @@ -847,8 +847,12 @@ def __setstate__(self,state): super().__setstate__(state) +import typing as t +# int = t.TypeVar("_TInteger", bound="int") +# int = t.TypeVar("_TInteger", bound="int") +_TNone = t.Literal[None] -class Integer(Number[T]): +class Integer(Number[int]): """Numeric Parameter required to be an Integer.""" _slot_defaults = dict(Number._slot_defaults, default=0) @@ -856,16 +860,51 @@ class Integer(Number[T]): @typing.overload def __init__( self, - default: T = 0, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None, + default: int = 0, + *args, + allow_None: typing.Literal[False] = ..., + **kwargs + ) -> None: ... + + @typing.overload + def __init__( + self, + default: int | _TNone = 0, + *args, + allow_None: typing.Literal[True] = ..., + **kwargs + ) -> None: ... + + @typing.overload + def __init__( + self, + default: int | _TNone = None, + *args, + allow_None: typing.Literal[True] = True, + **kwargs + ) -> None: ... + + @typing.overload + def __init__( + self, + default: int = 0, *, bounds=None, softbounds=None, inclusive_bounds=(True,True), step=None, set_hook=None, allow_None=False, doc=None, label=None, precedence=None, instantiate=False, constant=False, readonly=False, pickle_default_value=True, per_instance=True, allow_refs=False, nested_refs=False ): ... - def __init__(self, default: T = Undefined, *args, **kwargs): + def __init__(self, default: int | _TNone | _TUndefined = Undefined, *args, allow_None = False, **kwargs): + if typing.TYPE_CHECKING: + # self.__allow_None = allow_None + if isinstance(default, _TUndefined): + default = 0 + super().__init__(default=default, *args, **kwargs) + # if typing.TYPE_CHECKING: + # self.__allow_None = allow_None + # def _validate_value(self, val, allow_None): if callable(val): return @@ -886,9 +925,17 @@ def _validate_step(self, val, step): f"None or an integer value, not {type(step)}." ) - def __set__(self, obj, val: T | int) -> None: - if typing.TYPE_CHECKING: - val = typing.cast("T", val) + def __set__(self, obj, val: int | None) -> None: + # if typing.TYPE_CHECKING: + # if val is None and not self.__allow_None: + # raise TypeError("None is not allowed") + # # else: + # # value = typing.cast("_TNone", value) + # if val is not None: + # if type(val) is not int: + # raise TypeError("Only int allowed") + # # else: + # # value = typing.cast("int", val) super().__set__(obj, val)