param 2.4.0a0__tar.gz → 2.4.0rc0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {param-2.4.0a0 → param-2.4.0rc0}/PKG-INFO +1 -1
  2. {param-2.4.0a0 → param-2.4.0rc0}/param/_version.py +2 -2
  3. {param-2.4.0a0 → param-2.4.0rc0}/param/depends.py +28 -23
  4. {param-2.4.0a0 → param-2.4.0rc0}/param/parameterized.py +115 -68
  5. {param-2.4.0a0 → param-2.4.0rc0}/param/parameters.py +69 -24
  6. {param-2.4.0a0 → param-2.4.0rc0}/tests/testparameterizedobject.py +21 -0
  7. {param-2.4.0a0 → param-2.4.0rc0}/.gitignore +0 -0
  8. {param-2.4.0a0 → param-2.4.0rc0}/LICENSE.txt +0 -0
  9. {param-2.4.0a0 → param-2.4.0rc0}/README.md +0 -0
  10. {param-2.4.0a0 → param-2.4.0rc0}/numbergen/__init__.py +0 -0
  11. {param-2.4.0a0 → param-2.4.0rc0}/param/__init__.py +0 -0
  12. {param-2.4.0a0 → param-2.4.0rc0}/param/_utils.py +0 -0
  13. {param-2.4.0a0 → param-2.4.0rc0}/param/display.py +0 -0
  14. {param-2.4.0a0 → param-2.4.0rc0}/param/ipython.py +0 -0
  15. {param-2.4.0a0 → param-2.4.0rc0}/param/py.typed +0 -0
  16. {param-2.4.0a0 → param-2.4.0rc0}/param/reactive.py +0 -0
  17. {param-2.4.0a0 → param-2.4.0rc0}/param/serializer.py +0 -0
  18. {param-2.4.0a0 → param-2.4.0rc0}/param/version.py +0 -0
  19. {param-2.4.0a0 → param-2.4.0rc0}/pyproject.toml +0 -0
  20. {param-2.4.0a0 → param-2.4.0rc0}/tests/__init__.py +0 -0
  21. {param-2.4.0a0 → param-2.4.0rc0}/tests/conftest.py +0 -0
  22. {param-2.4.0a0 → param-2.4.0rc0}/tests/testaddparameter.py +0 -0
  23. {param-2.4.0a0 → param-2.4.0rc0}/tests/testbind.py +0 -0
  24. {param-2.4.0a0 → param-2.4.0rc0}/tests/testbooleanparam.py +0 -0
  25. {param-2.4.0a0 → param-2.4.0rc0}/tests/testbytesparam.py +0 -0
  26. {param-2.4.0a0 → param-2.4.0rc0}/tests/testcalendardateparam.py +0 -0
  27. {param-2.4.0a0 → param-2.4.0rc0}/tests/testcalendardaterangeparam.py +0 -0
  28. {param-2.4.0a0 → param-2.4.0rc0}/tests/testcallable.py +0 -0
  29. {param-2.4.0a0 → param-2.4.0rc0}/tests/testclassselector.py +0 -0
  30. {param-2.4.0a0 → param-2.4.0rc0}/tests/testcolorparameter.py +0 -0
  31. {param-2.4.0a0 → param-2.4.0rc0}/tests/testcomparator.py +0 -0
  32. {param-2.4.0a0 → param-2.4.0rc0}/tests/testcompositeparams.py +0 -0
  33. {param-2.4.0a0 → param-2.4.0rc0}/tests/testcustomparam.py +0 -0
  34. {param-2.4.0a0 → param-2.4.0rc0}/tests/testdateparam.py +0 -0
  35. {param-2.4.0a0 → param-2.4.0rc0}/tests/testdaterangeparam.py +0 -0
  36. {param-2.4.0a0 → param-2.4.0rc0}/tests/testdefaultfactory.py +0 -0
  37. {param-2.4.0a0 → param-2.4.0rc0}/tests/testdefaults.py +0 -0
  38. {param-2.4.0a0 → param-2.4.0rc0}/tests/testdeprecations.py +0 -0
  39. {param-2.4.0a0 → param-2.4.0rc0}/tests/testdynamicparams.py +0 -0
  40. {param-2.4.0a0 → param-2.4.0rc0}/tests/testfiledeserialization.py +0 -0
  41. {param-2.4.0a0 → param-2.4.0rc0}/tests/testfileselector.py +0 -0
  42. {param-2.4.0a0 → param-2.4.0rc0}/tests/testimports.py +0 -0
  43. {param-2.4.0a0 → param-2.4.0rc0}/tests/testipythonmagic.py +0 -0
  44. {param-2.4.0a0 → param-2.4.0rc0}/tests/testjsonserialization.py +0 -0
  45. {param-2.4.0a0 → param-2.4.0rc0}/tests/testlist.py +0 -0
  46. {param-2.4.0a0 → param-2.4.0rc0}/tests/testlistselector.py +0 -0
  47. {param-2.4.0a0 → param-2.4.0rc0}/tests/testmultifileselector.py +0 -0
  48. {param-2.4.0a0 → param-2.4.0rc0}/tests/testnumbergen.py +0 -0
  49. {param-2.4.0a0 → param-2.4.0rc0}/tests/testnumberparameter.py +0 -0
  50. {param-2.4.0a0 → param-2.4.0rc0}/tests/testnumpy.py +0 -0
  51. {param-2.4.0a0 → param-2.4.0rc0}/tests/testobjectselector.py +0 -0
  52. {param-2.4.0a0 → param-2.4.0rc0}/tests/testpandas.py +0 -0
  53. {param-2.4.0a0 → param-2.4.0rc0}/tests/testparamdepends.py +0 -0
  54. {param-2.4.0a0 → param-2.4.0rc0}/tests/testparameter.py +0 -0
  55. {param-2.4.0a0 → param-2.4.0rc0}/tests/testparameterizedrepr.py +0 -0
  56. {param-2.4.0a0 → param-2.4.0rc0}/tests/testparamoutput.py +0 -0
  57. {param-2.4.0a0 → param-2.4.0rc0}/tests/testparamunion.py +0 -0
  58. {param-2.4.0a0 → param-2.4.0rc0}/tests/testpathparam.py +0 -0
  59. {param-2.4.0a0 → param-2.4.0rc0}/tests/testpickle.py +0 -0
  60. {param-2.4.0a0 → param-2.4.0rc0}/tests/testrangeparameter.py +0 -0
  61. {param-2.4.0a0 → param-2.4.0rc0}/tests/testreactive.py +0 -0
  62. {param-2.4.0a0 → param-2.4.0rc0}/tests/testrefs.py +0 -0
  63. {param-2.4.0a0 → param-2.4.0rc0}/tests/testreprhtml.py +0 -0
  64. {param-2.4.0a0 → param-2.4.0rc0}/tests/testselector.py +0 -0
  65. {param-2.4.0a0 → param-2.4.0rc0}/tests/testsignatures.py +0 -0
  66. {param-2.4.0a0 → param-2.4.0rc0}/tests/teststringparam.py +0 -0
  67. {param-2.4.0a0 → param-2.4.0rc0}/tests/testtimedependent.py +0 -0
  68. {param-2.4.0a0 → param-2.4.0rc0}/tests/testtupleparam.py +0 -0
  69. {param-2.4.0a0 → param-2.4.0rc0}/tests/testutils.py +0 -0
  70. {param-2.4.0a0 → param-2.4.0rc0}/tests/testversion.py +0 -0
  71. {param-2.4.0a0 → param-2.4.0rc0}/tests/testwatch.py +0 -0
  72. {param-2.4.0a0 → param-2.4.0rc0}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: param
3
- Version: 2.4.0a0
3
+ Version: 2.4.0rc0
4
4
  Summary: Declarative parameters for robust Python classes and a rich API for reactive programming
5
5
  Project-URL: Homepage, https://param.holoviz.org/
6
6
  Project-URL: Tracker, https://github.com/holoviz/param/issues
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.4.0a0'
22
- __version_tuple__ = version_tuple = (2, 4, 0, 'a0')
21
+ __version__ = version = '2.4.0rc0'
22
+ __version_tuple__ = version_tuple = (2, 4, 0, 'rc0')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -15,11 +15,12 @@ if t.TYPE_CHECKING:
15
15
  from collections.abc import AsyncGenerator, Callable, Generator
16
16
 
17
17
  _Y = t.TypeVar("_Y")
18
- _S = t.TypeVar("_S")
19
18
  _T = t.TypeVar("_T")
20
19
 
21
20
  _P = t.ParamSpec("_P")
21
+ _FullP = t.ParamSpec("_FullP")
22
22
  _R = t.TypeVar("_R", covariant=True)
23
+ _S = t.TypeVar("_S")
23
24
  Dependency = Parameter | str
24
25
 
25
26
  class DependencyInfo(t.TypedDict):
@@ -28,6 +29,10 @@ class DependencyInfo(t.TypedDict):
28
29
  watch: bool
29
30
  on_init: bool
30
31
 
32
+ class _DepsFn(t.Protocol[_FullP, _R]):
33
+ _dinfo: DependencyInfo
34
+ def __call__(self, *args: _FullP.args, **kwargs: _FullP.kwargs) -> _R: ...
35
+
31
36
  class DependsFunc(t.Protocol[_P, _R]):
32
37
  _dinfo: DependencyInfo
33
38
  def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R: ...
@@ -35,25 +40,25 @@ class DependsFunc(t.Protocol[_P, _R]):
35
40
 
36
41
  @t.overload
37
42
  def depends(
38
- func: Callable[_P, _R], /, *dependencies: Dependency, watch: bool = False, on_init: bool = False, **kw: Dependency
43
+ func: Callable[t.Concatenate[_S, _P], _R], /, *dependencies: Dependency, watch: bool = False, on_init: bool = False, **kw: Dependency
39
44
  ) -> DependsFunc[_P, _R]:
40
45
  ...
41
46
 
42
47
  @t.overload
43
48
  def depends(
44
49
  *dependencies: str, watch: bool = False, on_init: bool = False
45
- ) -> Callable[[Callable[_P, _R]], DependsFunc[_P, _R]]:
50
+ ) -> Callable[[Callable[t.Concatenate[_S, _P], _R]], DependsFunc[_P, _R]]:
46
51
  ...
47
52
 
48
53
  @t.overload
49
54
  def depends(
50
55
  *dependencies: Parameter, watch: bool = False, on_init: bool = False, **kw: Parameter
51
- ) -> Callable[[Callable[_P, _R]], DependsFunc[_P, _R]]:
56
+ ) -> Callable[[Callable[t.Concatenate[_S, _P], _R]], DependsFunc[_P, _R]]:
52
57
  ...
53
58
 
54
59
  def depends(
55
- *dependencies: Dependency | Callable[_P, _R], watch: bool = False, on_init: bool = False, **kw: Dependency
56
- ) -> DependsFunc[_P, _R] | Callable[[Callable[_P, _R]], DependsFunc[_P, _R]]:
60
+ *dependencies: Dependency | Callable[t.Concatenate[_S, _P], _R], watch: bool = False, on_init: bool = False, **kw: Dependency
61
+ ) -> DependsFunc[_P, _R] | Callable[[Callable[t.Concatenate[_S, _P], _R]], DependsFunc[_P, _R]]:
57
62
  """
58
63
  Annotates a function or :class:`Parameterized` method to express its dependencies.
59
64
 
@@ -74,51 +79,51 @@ def depends(
74
79
 
75
80
  """
76
81
  if dependencies and callable(dependencies[0]) and not isinstance(dependencies[0], (str, Parameter)):
77
- func = t.cast("Callable[_P, _R]", dependencies[0])
82
+ func = t.cast("Callable[t.Concatenate[_S, _P], _R]", dependencies[0])
78
83
  deps = t.cast("tuple[Dependency, ...]", dependencies[1:])
79
- return _depends_impl(func, *deps, watch=watch, on_init=on_init, **kw)
84
+ return t.cast("DependsFunc[_P, _R]", _depends_impl(func, *deps, watch=watch, on_init=on_init, **kw))
80
85
 
81
86
  deps = t.cast("tuple[Dependency, ...]", dependencies)
82
87
 
83
- def _decorator(func: Callable[_P, _R]) -> DependsFunc[_P, _R]:
84
- return _depends_impl(func, *deps, watch=watch, on_init=on_init, **kw)
88
+ def _decorator(func: Callable[t.Concatenate[_S, _P], _R]) -> DependsFunc[_P, _R]:
89
+ return t.cast("DependsFunc[_P, _R]", _depends_impl(func, *deps, watch=watch, on_init=on_init, **kw))
85
90
 
86
91
  return _decorator
87
92
 
88
93
 
89
94
  def _depends_impl(
90
- func: Callable[_P, _R], /, *dependencies: Dependency, watch: bool = False, on_init: bool = False, **kw: Dependency
91
- ) -> DependsFunc[_P, _R]:
95
+ func: Callable[_FullP, _R], /, *dependencies: Dependency, watch: bool = False, on_init: bool = False, **kw: Dependency
96
+ ) -> _DepsFn[_FullP, _R]:
92
97
  dependencies, kw = (
93
98
  tuple(transform_reference(arg) for arg in dependencies),
94
99
  {key: transform_reference(arg) for key, arg in kw.items()}
95
100
  )
96
101
 
97
102
  if inspect.isgeneratorfunction(func):
98
- func_gen = t.cast("Callable[_P, Generator[t.Any, t.Any, t.Any]]", func)
103
+ func_gen = t.cast("Callable[_FullP, Generator[t.Any, t.Any, t.Any]]", func)
99
104
  @wraps(func)
100
105
  def _depends_gen(*args, **kw):
101
106
  for val in func_gen(*args, **kw):
102
107
  yield val
103
- _depends = t.cast("Callable[_P, _R]", _depends_gen)
108
+ _depends = t.cast("Callable[_FullP, _R]", _depends_gen)
104
109
  elif inspect.isasyncgenfunction(func):
105
- func_agen = t.cast("Callable[_P, AsyncGenerator[t.Any, t.Any]]", func)
110
+ func_agen = t.cast("Callable[_FullP, AsyncGenerator[t.Any, t.Any]]", func)
106
111
  @wraps(func)
107
112
  async def _depends_async_gen(*args, **kw):
108
113
  async for val in func_agen(*args, **kw):
109
114
  yield val
110
- _depends = t.cast("Callable[_P, _R]", _depends_async_gen)
115
+ _depends = t.cast("Callable[_FullP, _R]", _depends_async_gen)
111
116
  elif iscoroutinefunction(func):
112
- F = t.cast("Callable[_P, t.Awaitable[_R]]", func)
117
+ F = t.cast("Callable[_FullP, t.Awaitable[_R]]", func)
113
118
  @wraps(func)
114
119
  async def _depends_coro(*args, **kw):
115
120
  return await F(*args, **kw)
116
- _depends = t.cast("Callable[_P, _R]", _depends_coro)
121
+ _depends = t.cast("Callable[_FullP, _R]", _depends_coro)
117
122
  else:
118
123
  @wraps(func)
119
124
  def _depends_sync(*args, **kw):
120
125
  return func(*args, **kw)
121
- _depends = t.cast("Callable[_P, _R]", _depends_sync)
126
+ _depends = t.cast("Callable[_FullP, _R]", _depends_sync)
122
127
 
123
128
  deps = list(dependencies)+list(kw.values())
124
129
  string_specs = False
@@ -155,7 +160,7 @@ def _depends_impl(
155
160
  _dinfo.update({'dependencies': dependencies,
156
161
  'kw': kw, 'watch': watch, 'on_init': on_init})
157
162
 
158
- typed_depends = t.cast("DependsFunc[_P, _R]", _depends)
163
+ typed_depends = t.cast("_DepsFn[_FullP, _R]", _depends)
159
164
  typed_depends._dinfo = _dinfo
160
165
 
161
166
  if string_specs or not watch:
@@ -196,7 +201,7 @@ def _depends_impl(
196
201
  def cb_gen(*events: Event):
197
202
  args = _resolve_args()
198
203
  dep_kwargs = _resolve_kwargs()
199
- func_gen = t.cast("Callable[_P, Generator[t.Any, t.Any, t.Any]]", func)
204
+ func_gen = t.cast("Callable[_FullP, Generator[t.Any, t.Any, t.Any]]", func)
200
205
  for val in func_gen(*args, **dep_kwargs):
201
206
  yield val
202
207
  cb = cb_gen
@@ -204,7 +209,7 @@ def _depends_impl(
204
209
  async def cb_async_gen(*events: Event):
205
210
  args = _resolve_args()
206
211
  dep_kwargs = _resolve_kwargs()
207
- func_agen = t.cast("Callable[_P, AsyncGenerator[t.Any, t.Any]]", func)
212
+ func_agen = t.cast("Callable[_FullP, AsyncGenerator[t.Any, t.Any]]", func)
208
213
  async for val in func_agen(*args, **dep_kwargs):
209
214
  yield val
210
215
  cb = cb_async_gen
@@ -212,7 +217,7 @@ def _depends_impl(
212
217
  async def cb_coro(*events: Event):
213
218
  args = _resolve_args()
214
219
  dep_kwargs = _resolve_kwargs()
215
- func_coro = t.cast("Callable[_P, t.Awaitable[t.Any]]", func)
220
+ func_coro = t.cast("Callable[_FullP, t.Awaitable[t.Any]]", func)
216
221
  await func_coro(*args, **dep_kwargs)
217
222
  cb = cb_coro
218
223
  else:
@@ -59,7 +59,6 @@ if t.TYPE_CHECKING:
59
59
  class _StringInitKwargs(_ParameterKwargs, total=False):
60
60
  regex: str | re.Pattern[str] | None
61
61
 
62
-
63
62
  _T = t.TypeVar("_T")
64
63
  _P = t.ParamSpec("_P")
65
64
  _R = t.TypeVar("_R", covariant=True)
@@ -84,6 +83,8 @@ from ._utils import (
84
83
  gen_types,
85
84
  )
86
85
 
86
+ _IS_PYPY = sys.implementation.name == "pypy"
87
+
87
88
  CRITICAL = 50
88
89
  ERROR = 40
89
90
  WARNING = 30
@@ -1189,6 +1190,7 @@ class ParameterMetaclass(type):
1189
1190
 
1190
1191
 
1191
1192
  _UDPATE_PARAMETER_SIGNATURE = _in_ipython() or (os.getenv("PARAM_PARAMETER_SIGNATURE", "false").lower() in ("1" , "true"))
1193
+ _PARAMETER_CACHE_ATTRS = ('instantiate', 'constant', 'default_factory')
1192
1194
 
1193
1195
 
1194
1196
  class _ParameterBase(metaclass=ParameterMetaclass):
@@ -1853,6 +1855,8 @@ class Parameter(_ParameterBase, t.Generic[_T]):
1853
1855
  pass
1854
1856
 
1855
1857
  super().__setattr__(attribute, value)
1858
+ if is_slot and attribute in _PARAMETER_CACHE_ATTRS:
1859
+ self._invalidate_init_cache()
1856
1860
  if has_watcher and old is not NotImplemented:
1857
1861
  self._trigger_event(attribute, old, value)
1858
1862
 
@@ -1867,6 +1871,20 @@ class Parameter(_ParameterBase, t.Generic[_T]):
1867
1871
  if not self.owner.param._BATCH_WATCH:
1868
1872
  self.owner.param._batch_call_watchers()
1869
1873
 
1874
+ def _invalidate_init_cache(self):
1875
+ try:
1876
+ owner = object.__getattribute__(self, 'owner')
1877
+ except AttributeError:
1878
+ return
1879
+ if owner is None:
1880
+ return
1881
+ private = owner._param__private
1882
+ if not hasattr(private, 'params_to_deepcopy'):
1883
+ return
1884
+ private.params_to_deepcopy = None
1885
+ private.params_to_ref = None
1886
+ private.params_with_default_factory = None
1887
+
1870
1888
  def __getattribute__(self, key: str) -> t.Any:
1871
1889
  """
1872
1890
  Allow slot values to be Undefined in an "unbound" parameter, i.e. one
@@ -2011,6 +2029,9 @@ class Parameter(_ParameterBase, t.Generic[_T]):
2011
2029
  obj._param__private.values[name] = val
2012
2030
  self._post_setter(obj, val)
2013
2031
 
2032
+ if obj is None:
2033
+ self._invalidate_init_cache()
2034
+
2014
2035
  if obj is not None:
2015
2036
  if not hasattr(obj, '_param__private') or not getattr(obj._param__private, 'initialized', False):
2016
2037
  return
@@ -2105,6 +2126,17 @@ class Parameter(_ParameterBase, t.Generic[_T]):
2105
2126
  """
2106
2127
  return {slot: getattr(self, slot) for slot in getattr(self.__class__, "_all_slots_", ())}
2107
2128
 
2129
+ def __copy__(self) -> Parameter:
2130
+ cls = self.__class__
2131
+ duplicate = cls.__new__(cls)
2132
+ if _IS_PYPY:
2133
+ # Workaround for PyPy segfaults (https://github.com/pypy/pypy/issues/5400)
2134
+ duplicate.__setstate__(self.__getstate__())
2135
+ else:
2136
+ for slot in cls._all_slots_:
2137
+ object.__setattr__(duplicate, slot, getattr(self, slot))
2138
+ return duplicate
2139
+
2108
2140
  def __setstate__(self, state: dict[str, t.Any]):
2109
2141
  # set values of __slots__ (instead of in non-existent __dict__)
2110
2142
  for k, v in state.items():
@@ -2561,21 +2593,17 @@ class Parameters:
2561
2593
  defined as parameters.
2562
2594
  """
2563
2595
  self = self_.self
2564
- ## Deepcopy all 'instantiate=True' parameters
2565
- params_to_deepcopy = {}
2566
- params_to_ref = {}
2567
2596
  objects = self_._cls_parameters
2568
- for pname, p in objects.items():
2569
- if pname == 'name' or p.default_factory:
2570
- continue
2571
- if p.instantiate:
2572
- params_to_deepcopy[pname] = p
2573
- elif p.constant:
2574
- params_to_ref[pname] = p
2597
+ private = self_.cls._param__private
2598
+ params_to_deepcopy = private.params_to_deepcopy or []
2599
+ params_to_ref = private.params_to_ref or []
2600
+
2601
+ if not params and not params_to_deepcopy and not params_to_ref:
2602
+ return {}, {}
2575
2603
 
2576
- for p in params_to_deepcopy.values():
2604
+ for p in params_to_deepcopy:
2577
2605
  self_._instantiate_param(p)
2578
- for p in params_to_ref.values():
2606
+ for p in params_to_ref:
2579
2607
  self_._instantiate_param(p, deepcopy=False)
2580
2608
 
2581
2609
  ## keyword arg setting
@@ -3108,8 +3136,22 @@ class Parameters:
3108
3136
  and parameters are rarely added (and cannot be deleted).
3109
3137
  """
3110
3138
  cls = self_.cls
3111
- pdict = cls._param__private.params
3139
+ private = cls._param__private
3140
+ pdict = private.params
3112
3141
  if pdict:
3142
+ if private.params_to_deepcopy is None or private.params_to_ref is None or private.params_with_default_factory is None:
3143
+ private.params_to_deepcopy = []
3144
+ private.params_to_ref = []
3145
+ private.params_with_default_factory = []
3146
+ for pname, pobj in pdict.items():
3147
+ if pname == 'name':
3148
+ continue
3149
+ if pobj.default_factory is not None:
3150
+ private.params_with_default_factory.append((pname, pobj))
3151
+ elif pobj.instantiate:
3152
+ private.params_to_deepcopy.append(pobj)
3153
+ elif pobj.constant:
3154
+ private.params_to_ref.append(pobj)
3113
3155
  return pdict
3114
3156
 
3115
3157
  paramdict = {}
@@ -3118,12 +3160,28 @@ class Parameters:
3118
3160
  if isinstance(val, Parameter):
3119
3161
  paramdict[name] = val
3120
3162
 
3163
+ params_to_deepcopy: list[Parameter] = []
3164
+ params_to_ref: list[Parameter] = []
3165
+ params_with_default_factory: list[tuple[str, Parameter]] = []
3166
+ for pname, pobj in paramdict.items():
3167
+ if pname == 'name':
3168
+ continue
3169
+ if pobj.default_factory is not None:
3170
+ params_with_default_factory.append((pname, pobj))
3171
+ elif pobj.instantiate:
3172
+ params_to_deepcopy.append(pobj)
3173
+ elif pobj.constant:
3174
+ params_to_ref.append(pobj)
3175
+
3121
3176
  # We only want the cache to be visible to the cls on which
3122
3177
  # params() is called, so we mangle the name ourselves at
3123
3178
  # runtime (if we were to mangle it now, it would be
3124
3179
  # _Parameterized.__params for all classes).
3125
3180
  # cls._param__private.params[f'_{cls.__name__}__params'] = paramdict
3126
- cls._param__private.params = paramdict
3181
+ private.params = paramdict
3182
+ private.params_to_deepcopy = params_to_deepcopy
3183
+ private.params_to_ref = params_to_ref
3184
+ private.params_with_default_factory = params_with_default_factory
3127
3185
  return paramdict
3128
3186
 
3129
3187
  def objects(self_, instance: t.Literal[True, False, 'existing'] = True) -> dict[str, Parameter]:
@@ -3331,14 +3389,14 @@ class Parameters:
3331
3389
  """
3332
3390
  self_or_cls = self_.self_or_cls
3333
3391
  self_or_cls._Dynamic_time_fn = time_fn # type: ignore[union-attr, ty:invalid-assignment] # pyright: ignore[reportAttributeAccessIssue]
3334
- param_ns: Parameters = self_or_cls.param
3392
+ param_objs = self_.objects('existing')
3335
3393
 
3336
- for n, p in param_ns.objects('existing').items():
3394
+ for n, p in param_objs.items():
3337
3395
  if not hasattr(p, '_value_is_dynamic'):
3338
3396
  continue
3339
3397
  is_dynamic = p._value_is_dynamic(None, self_._cls) if self_.self is None else p._value_is_dynamic(self_.self)
3340
3398
  if is_dynamic:
3341
- g = param_ns.get_value_generator(n)
3399
+ g = self_.get_value_generator(n, param_objs)
3342
3400
  g._Dynamic_time_fn = time_fn
3343
3401
 
3344
3402
  if sublistattr:
@@ -3647,12 +3705,12 @@ class Parameters:
3647
3705
  >>> p.param.values(onlychanged=True)
3648
3706
  {'a': 10}
3649
3707
  """
3650
- self_or_cls = self_.self_or_cls
3651
- param_ns = self_or_cls.param
3708
+ param_objs = self_.objects('existing') # type: ignore[union-attr, ty:unresolved-attribute]
3709
+
3652
3710
  vals = []
3653
- for name, val in param_ns.objects('existing').items(): # type: ignore[union-attr, ty:unresolved-attribute,]
3654
- value = param_ns.get_value_generator(name) # type: ignore[union-attr, ty:unresolved-attribute]
3655
- if name == 'name' and onlychanged and _is_auto_name(self_.cls.__name__, value):
3711
+ for name, val in param_objs.items():
3712
+ value = self_.get_value_generator(name, param_objs)
3713
+ if name == 'name' and onlychanged and isinstance(value, str) and _is_auto_name(self_.cls.__name__, value):
3656
3714
  continue
3657
3715
  if not onlychanged or not Comparator.is_equal(value, val.default):
3658
3716
  vals.append((name, value))
@@ -3671,25 +3729,15 @@ class Parameters:
3671
3729
  If name is not dynamic, its current value is returned
3672
3730
  (i.e. equivalent to ``getattr(name)``).
3673
3731
  """
3674
- cls_or_slf = self_.self_or_cls
3675
- param_ns = cls_or_slf.param
3676
- param_obj = param_ns.objects('existing').get(name) # type: ignore[union-attr, ty:unresolved-attribute]
3677
-
3732
+ param_obj = self_.objects('existing').get(name) # type: ignore[union-attr, ty:unresolved-attribute]
3678
3733
  if not param_obj:
3679
- return getattr(cls_or_slf, name)
3680
-
3681
- cls, slf = None, None
3682
- if isinstance(cls_or_slf,type):
3683
- cls = cls_or_slf
3684
- else:
3685
- slf = cls_or_slf
3686
-
3687
- if not hasattr(param_obj,'_force'):
3688
- return param_obj.__get__(slf, cls)
3734
+ return getattr(self_.self_or_cls, name)
3735
+ elif not hasattr(param_obj, '_force'):
3736
+ return param_obj.__get__(self_.self, self_.cls)
3689
3737
  else:
3690
- return param_obj._force(slf, cls)
3738
+ return param_obj._force(self_.self, self_.cls)
3691
3739
 
3692
- def get_value_generator(self_, name: str) -> t.Any:
3740
+ def get_value_generator(self_, name: str, parameters: dict[str, Parameter] | None = None) -> t.Any:
3693
3741
  """
3694
3742
  Retrieve the value or value-generating object of a named parameter.
3695
3743
 
@@ -3732,15 +3780,17 @@ class Parameters:
3732
3780
  <UniformRandom UniformRandom ...>
3733
3781
  """
3734
3782
  cls_or_slf = self_.self_or_cls
3735
- param_ns = cls_or_slf.param
3736
- param_obj = param_ns.objects('existing').get(name) # type: ignore[union-attr, ty:unresolved-attribute]
3783
+ if parameters and name in parameters:
3784
+ param_obj: Parameter | None = parameters[name]
3785
+ else:
3786
+ param_obj = self_.objects('existing').get(name) # type: ignore[union-attr, ty:unresolved-attribute]
3737
3787
 
3738
3788
  if not param_obj:
3739
- value = getattr(cls_or_slf,name)
3789
+ value = getattr(cls_or_slf, name)
3740
3790
 
3741
3791
  # CompositeParameter detected by being a Parameter and having 'attribs'
3742
3792
  elif hasattr(param_obj, 'attribs'):
3743
- value = [param_ns.get_value_generator(a) for a in param_obj.attribs] # type: ignore[union-attr, ty:unresolved-attribute]
3793
+ value = [self_.get_value_generator(a) for a in param_obj.attribs] # type: ignore[union-attr, ty:unresolved-attribute]
3744
3794
 
3745
3795
  # not a Dynamic Parameter
3746
3796
  elif not hasattr(param_obj, '_value_is_dynamic'):
@@ -3748,10 +3798,9 @@ class Parameters:
3748
3798
 
3749
3799
  # Dynamic Parameter...
3750
3800
  else:
3751
- # TODO: is this always an instance?
3752
- if isinstance(cls_or_slf, Parameterized) and name in cls_or_slf._param__private.values:
3801
+ if self_.self is not None and name in self_.self._param__private.values:
3753
3802
  # dealing with object and it's been set on this object
3754
- value = cls_or_slf._param__private.values[name]
3803
+ value = self_.self._param__private.values[name]
3755
3804
  elif not callable(param_obj.default):
3756
3805
  value = getattr(cls_or_slf, name)
3757
3806
  else:
@@ -3796,17 +3845,16 @@ class Parameters:
3796
3845
  -0.7312715117751976
3797
3846
  """
3798
3847
  cls_or_slf = self_.self_or_cls
3799
- param_ns = cls_or_slf.param
3800
- param_obj = param_ns.objects('existing').get(name) # type: ignore[union-attr, ty:unresolved-attribute]
3848
+ param_obj = self_.objects('existing').get(name) # type: ignore[union-attr, ty:unresolved-attribute]
3801
3849
 
3802
3850
  if not param_obj:
3803
3851
  value = getattr(cls_or_slf,name)
3804
3852
  elif hasattr(param_obj,'attribs'):
3805
- value = [param_ns.inspect_value(a) for a in param_obj.attribs] # type: ignore[union-attr, ty:unresolved-attribute]
3853
+ value = [self_.inspect_value(a) for a in param_obj.attribs] # type: ignore[union-attr, ty:unresolved-attribute]
3806
3854
  elif not hasattr(param_obj,'_inspect'):
3807
- value = getattr(cls_or_slf,name)
3855
+ value = getattr(cls_or_slf, name)
3808
3856
  else:
3809
- if isinstance(cls_or_slf,type):
3857
+ if isinstance(cls_or_slf, type):
3810
3858
  value = param_obj._inspect(None, cls_or_slf)
3811
3859
  else:
3812
3860
  value = param_obj._inspect(cls_or_slf, None)
@@ -5474,6 +5522,9 @@ class _ClassPrivate:
5474
5522
  'disable_instance_params',
5475
5523
  'renamed',
5476
5524
  'params',
5525
+ 'params_to_deepcopy',
5526
+ 'params_to_ref',
5527
+ 'params_with_default_factory',
5477
5528
  'initialized',
5478
5529
  'signature',
5479
5530
  'explicit_no_refs',
@@ -5483,6 +5534,9 @@ class _ClassPrivate:
5483
5534
  disable_instance_params: bool
5484
5535
  renamed: bool
5485
5536
  params: dict[str, Parameter]
5537
+ params_to_deepcopy: list[Parameter] | None
5538
+ params_to_ref: list[Parameter] | None
5539
+ params_with_default_factory: list[tuple[str, Parameter]] | None
5486
5540
  initialized: bool
5487
5541
  signature: inspect.Signature | None
5488
5542
  explicit_no_refs: list[str]
@@ -5506,6 +5560,9 @@ class _ClassPrivate:
5506
5560
  self.disable_instance_params = disable_instance_params
5507
5561
  self.renamed = renamed
5508
5562
  self.params = {} if params is None else params
5563
+ self.params_to_deepcopy = None
5564
+ self.params_to_ref = None
5565
+ self.params_with_default_factory = None
5509
5566
  self.initialized = False
5510
5567
  self.signature = None
5511
5568
  self.explicit_no_refs = [] if explicit_no_refs is None else explicit_no_refs
@@ -5617,15 +5674,7 @@ class _NS:
5617
5674
  def __get__(self, obj: C | None, objtype: type[C]) -> Parameters:
5618
5675
  if obj is None:
5619
5676
  return objtype._param__parameters
5620
- objdict = getattr(obj, "__dict__", None)
5621
- ns = objdict.get("_param__parameters") if objdict is not None else None
5622
- if ns is None:
5623
- ns = Parameters(objtype, self=obj)
5624
- if objdict is not None:
5625
- objdict["_param__parameters"] = ns
5626
- else:
5627
- setattr(obj, "_param__parameters", ns)
5628
- return ns
5677
+ return Parameters(objtype, self=obj)
5629
5678
 
5630
5679
 
5631
5680
  class _PrivateNS:
@@ -5834,21 +5883,19 @@ class Parameterized(metaclass=ParameterizedMetaclass):
5834
5883
 
5835
5884
  # Find parameters with default_factory through the class
5836
5885
  # parameters to avoid making a copy.
5837
- params_with_default_factory = [
5838
- pname
5839
- for pname, pobj in self.param._cls_parameters.items()
5840
- if pname not in params
5841
- and pobj.default_factory is not None
5842
- ]
5886
+ params_with_default_factory = type(self)._param__private.params_with_default_factory or []
5843
5887
  # Set from default_factory once initialized so instance parameters
5844
5888
  # are copied.
5845
5889
  if params_with_default_factory:
5846
- for pname in params_with_default_factory:
5847
- pobj = self.param[pname]
5890
+ for pname, pobj in params_with_default_factory:
5891
+ if pname in params:
5892
+ continue
5848
5893
  dfactory = pobj.default_factory
5849
5894
  if dfactory is None:
5850
5895
  continue
5851
5896
  elif isinstance(dfactory, DefaultFactory):
5897
+ # DefaultFactory receives instance-level Parameter context.
5898
+ pobj = self.param[pname]
5852
5899
  default_val = dfactory(cls=type(self), self=self, parameter=pobj)
5853
5900
  else:
5854
5901
  default_val = dfactory()
@@ -67,6 +67,7 @@ if t.TYPE_CHECKING:
67
67
  from .parameterized import _ParameterKwargs
68
68
 
69
69
  LT = t.TypeVar("LT")
70
+ CT = t.TypeVar("CT")
70
71
 
71
72
  AT = t.TypeVar("AT", np.ndarray, np.ndarray | None)
72
73
  DF = t.TypeVar("DF", pd.DataFrame, pd.DataFrame | None)
@@ -1638,11 +1639,11 @@ class NumericTuple(Tuple[_T]):
1638
1639
 
1639
1640
  @t.overload
1640
1641
  def __init__(
1641
- self: NumericTuple[tuple[float, ...] | None],
1642
+ self: NumericTuple[tuple[float, ...]],
1642
1643
  default: tuple[float, ...] = (0.0, 0.0),
1643
1644
  *,
1644
1645
  length: int | None = None,
1645
- allow_None: t.Literal[True] = True,
1646
+ allow_None: t.Literal[False] = False,
1646
1647
  doc: str | None = None,
1647
1648
  label: str | None = None,
1648
1649
  precedence: float | None = None,
@@ -1661,10 +1662,21 @@ class NumericTuple(Tuple[_T]):
1661
1662
  @t.overload
1662
1663
  def __init__(
1663
1664
  self: NumericTuple[tuple[float, ...] | None],
1664
- default: None = None,
1665
+ default: tuple[float, ...] | None = (0.0, 0.0),
1665
1666
  *,
1666
1667
  length: int | None = None,
1667
- allow_None: t.Literal[False] = False,
1668
+ allow_None: t.Literal[True] = True,
1669
+ **kwargs: Unpack[_ParameterKwargs]
1670
+ ) -> None:
1671
+ ...
1672
+
1673
+ @t.overload
1674
+ def __init__(
1675
+ self: NumericTuple[tuple[float, ...] | None],
1676
+ default: None,
1677
+ *,
1678
+ length: int | None = None,
1679
+ allow_None: bool = False,
1668
1680
  **kwargs: Unpack[_ParameterKwargs]
1669
1681
  ) -> None:
1670
1682
  ...
@@ -2586,7 +2598,7 @@ class Selector(SelectorBase, _SignatureSelector[_T]):
2586
2598
  object.__setattr__(self, 'check_on_set', check_on_set)
2587
2599
 
2588
2600
  instantiate = params.pop("instantiate", Undefined)
2589
- params["instantiate"] = False if instantiate is Undefined else instantiate
2601
+ params["instantiate"] = False if instantiate is Undefined else instantiate # pyrefly: ignore[bad-typed-dict-key]
2590
2602
  super().__init__(default=default, **params)
2591
2603
  # Required as Parameter sets allow_None=True if default is None
2592
2604
  if allow_None is Undefined:
@@ -2915,10 +2927,10 @@ class ClassSelector(SelectorBase[_T]):
2915
2927
 
2916
2928
  @t.overload
2917
2929
  def __init__(
2918
- self,
2930
+ self: ClassSelector[CT],
2919
2931
  *,
2920
- default: _T,
2921
- class_: type[_T] | tuple[type[_T], ...],
2932
+ default: CT,
2933
+ class_: type[CT],
2922
2934
  is_instance: t.Literal[True] = True,
2923
2935
  allow_None: t.Literal[False] = False,
2924
2936
  doc: str | None = None,
@@ -2938,10 +2950,10 @@ class ClassSelector(SelectorBase[_T]):
2938
2950
 
2939
2951
  @t.overload
2940
2952
  def __init__(
2941
- self,
2953
+ self: ClassSelector[CT | None],
2942
2954
  *,
2943
2955
  default: None = None,
2944
- class_: type[_T] | tuple[type[_T], ...],
2956
+ class_: type[CT],
2945
2957
  is_instance: t.Literal[True] = True,
2946
2958
  allow_None: t.Literal[False] = False,
2947
2959
  **kwargs: Unpack[_ParameterKwargs]
@@ -2950,10 +2962,10 @@ class ClassSelector(SelectorBase[_T]):
2950
2962
 
2951
2963
  @t.overload
2952
2964
  def __init__(
2953
- self,
2965
+ self: ClassSelector[CT | None],
2954
2966
  *,
2955
- default: _T | None = None,
2956
- class_: type[_T] | tuple[type[_T], ...],
2967
+ default: CT | None = None,
2968
+ class_: type[CT],
2957
2969
  is_instance: t.Literal[True] = True,
2958
2970
  allow_None: t.Literal[True] = True,
2959
2971
  **kwargs: Unpack[_ParameterKwargs]
@@ -2962,10 +2974,32 @@ class ClassSelector(SelectorBase[_T]):
2962
2974
 
2963
2975
  @t.overload
2964
2976
  def __init__(
2965
- self,
2977
+ self: ClassSelector[t.Any],
2966
2978
  *,
2967
- default: type[_T],
2968
- class_: type[_T] | tuple[type[_T], ...],
2979
+ default: t.Any = None,
2980
+ class_: tuple[type, ...],
2981
+ is_instance: t.Literal[True] = True,
2982
+ allow_None: t.Literal[False] = False,
2983
+ **kwargs: Unpack[_ParameterKwargs]
2984
+ ) -> None: ...
2985
+
2986
+ @t.overload
2987
+ def __init__(
2988
+ self: ClassSelector[t.Any],
2989
+ *,
2990
+ default: t.Any = None,
2991
+ class_: tuple[type, ...],
2992
+ is_instance: t.Literal[True] = True,
2993
+ allow_None: t.Literal[True] = True,
2994
+ **kwargs: Unpack[_ParameterKwargs]
2995
+ ) -> None: ...
2996
+
2997
+ @t.overload
2998
+ def __init__(
2999
+ self: ClassSelector[type[CT]],
3000
+ *,
3001
+ default: type[CT],
3002
+ class_: type[CT],
2969
3003
  is_instance: t.Literal[False],
2970
3004
  allow_None: t.Literal[False] = False,
2971
3005
  **kwargs: Unpack[_ParameterKwargs]
@@ -2974,10 +3008,10 @@ class ClassSelector(SelectorBase[_T]):
2974
3008
 
2975
3009
  @t.overload
2976
3010
  def __init__(
2977
- self,
3011
+ self: ClassSelector[type[CT] | None],
2978
3012
  *,
2979
3013
  default: None = None,
2980
- class_: type[_T] | tuple[type[_T], ...],
3014
+ class_: type[CT] | tuple[type[CT], ...],
2981
3015
  is_instance: t.Literal[False],
2982
3016
  allow_None: t.Literal[False] = False,
2983
3017
  **kwargs: Unpack[_ParameterKwargs]
@@ -2986,16 +3020,27 @@ class ClassSelector(SelectorBase[_T]):
2986
3020
 
2987
3021
  @t.overload
2988
3022
  def __init__(
2989
- self,
3023
+ self: ClassSelector[type[CT] | None],
2990
3024
  *,
2991
- default: type[_T] | None = None,
2992
- class_: type[_T] | tuple[type[_T], ...],
3025
+ default: type[CT] | None = None,
3026
+ class_: type[CT] | tuple[type[CT], ...],
2993
3027
  is_instance: t.Literal[False],
2994
3028
  allow_None: t.Literal[True] = True,
2995
3029
  **kwargs: Unpack[_ParameterKwargs]
2996
3030
  ) -> None:
2997
3031
  ...
2998
3032
 
3033
+ @t.overload
3034
+ def __init__(
3035
+ self: ClassSelector[t.Any],
3036
+ *,
3037
+ default: t.Any = None,
3038
+ class_: tuple[type, ...],
3039
+ is_instance: t.Literal[False] = False,
3040
+ allow_None: t.Literal[False] = False,
3041
+ **kwargs: Unpack[_ParameterKwargs]
3042
+ ) -> None: ...
3043
+
2999
3044
  def __init__(
3000
3045
  self, *,
3001
3046
  class_: type | tuple[type, ...] = t.cast("type | tuple[type, ...]", Undefined), # pyrefly: ignore[bad-argument-type]
@@ -3158,7 +3203,7 @@ class Array(ClassSelector["AT"]):
3158
3203
  import numpy
3159
3204
  super().__init__( # type: ignore[misc, call-overload]
3160
3205
  default=default, # type: ignore[arg-type]
3161
- class_=numpy.ndarray,
3206
+ class_=numpy.ndarray, # type: ignore[arg-type]
3162
3207
  is_instance=True,
3163
3208
  allow_None=allow_None, # type: ignore[arg-type]
3164
3209
  **params, # type: ignore[arg-type]
@@ -3471,7 +3516,7 @@ class List(Parameter[_T]):
3471
3516
  self: List[list[LT]],
3472
3517
  default: list[LT] = [],
3473
3518
  *,
3474
- item_type: type[LT] | tuple[type[LT], ...] | None = None,
3519
+ item_type: type[LT] | tuple[type[LT], ...] = (),
3475
3520
  bounds: tuple[int, int | None] | None = (0, None),
3476
3521
  is_instance: bool = True,
3477
3522
  allow_None: t.Literal[False] = False,
@@ -3495,7 +3540,7 @@ class List(Parameter[_T]):
3495
3540
  self: List[list[LT] | None],
3496
3541
  default: list[LT] | None = None,
3497
3542
  *,
3498
- item_type: type[LT] | tuple[type[LT], ...] | None = None,
3543
+ item_type: type[LT] | tuple[type[LT], ...] = (),
3499
3544
  allow_None: t.Literal[True] = True,
3500
3545
  **kwargs: Unpack[_ParameterKwargs]
3501
3546
  ) -> None:
@@ -2,8 +2,10 @@
2
2
  import abc
3
3
  import inspect
4
4
  import re
5
+ import sys
5
6
  import unittest
6
7
  import warnings
8
+ import weakref
7
9
 
8
10
  import param
9
11
  import numbergen
@@ -1981,3 +1983,22 @@ def test_abc_basic_checks():
1981
1983
  assert gc.l == [10]
1982
1984
  gc.x += 1
1983
1985
  assert gc.l == [10, 11]
1986
+
1987
+
1988
+ @pytest.mark.skipif(sys.implementation.name == "pypy", reason='Works differently on PyPy')
1989
+ def test_no_param_namespace_cycle():
1990
+ # Accessing .param on an instance must not create a reference cycle.
1991
+ # A cycle (obj -> obj.__dict__['_param__parameters'] -> Parameters.self -> obj)
1992
+ # would prevent CPython's reference-counting from immediately freeing the
1993
+ # object, breaking weakref-based cleanup used by libraries like HoloViews.
1994
+ class P(param.Parameterized):
1995
+ x = param.Number(1)
1996
+
1997
+ freed = []
1998
+ obj = P()
1999
+ ref = weakref.ref(obj, lambda _: freed.append(True)) # noqa: F841
2000
+
2001
+ _ = obj.param.values() # access .param to trigger any caching
2002
+
2003
+ del obj
2004
+ assert freed, "Parameterized instance not freed immediately — likely a reference cycle via .param"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes