-
-
Notifications
You must be signed in to change notification settings - Fork 82
Description
Summary
When assigning values to Parameters, Param automatically resolves ref-like objects (e.g. Parameters, reactive expressions, async generators). Sometimes, however, users need to store these ref-like values themselves — for forwarding, serialization, inspection, or deferred evaluation — rather than immediately resolving them.
This issue proposes adding a minimal wrapper, raw, that marks a value to be stored as-is. Alternative names considered: constant, const, or literal.
Problem
Param’s assignment model is designed for convenience — ref-like values are automatically resolved when assigned to a Parameter.
However, there are valid use cases where the reference object itself should be stored, not the resolved value:
- Keeping a
Parameteror reactive object to be connected later. - Holding an expression or generator for deferred computation.
- Serializing configurations that intentionally contain unresolved refs.
- Preserving symbolic or lazy evaluation constructs for inspection.
Proposed API
import param
# Store a ref-like value *as itself*, skipping resolution:
obj.some_param = param.Raw(ref_like)
# or the convenience function:
obj.some_param = param.raw(ref_like)Alternative names under consideration:
param.const(value)param.constant(value)param.literal(value)
All would convey the same semantics: assign without resolving.
Semantics
-
raw(x)is a transient assignment wrapper. -
During assignment, if a value is an instance of
Raw, Param:- Unwraps it (
x = x.value), and - Skips any resolution logic that would normally occur.
- Unwraps it (
-
The stored value becomes the underlying ref-like object, not the wrapper.
-
Reading the value simply returns whatever was stored.
Examples
Keep a reactive or param reference
class C(param.Parameterized):
value = param.Parameter()
ref_like = another_obj.param.some_param
c = C()
c.value = param.raw(ref_like)
assert c.value is ref_likeKeep a reactive expression or async generator
expr = rx.map(source, lambda v: v * 2)
c.value = param.raw(expr) # store expression, not resolved value
async def stream(): yield 1
c.value = param.raw(stream()) # store generator, not consumedContainers are stored verbatim
c.value = param.raw([rx.expr1, param.Parameter(), async_gen])Implementation sketch
class Raw:
__slots__ = ("value",)
def __init__(self, value): self.value = value
def __repr__(self): return f"Raw({self.value!r})"
def raw(value):
return Raw(value)In Parameter.__set__ (or equivalent):
if isinstance(new, Raw):
new = new.value
skip_ref_resolution = True
if not skip_ref_resolution:
new = maybe_resolve(new) # existing logicTyping (optional)
from typing import Generic, TypeVar
T = TypeVar("T")
class Raw(Generic[T]):
value: TBackward compatibility
- Fully backward compatible: no change unless users explicitly use
Raw. - The wrapper is transient and does not appear in serialized state.
- Stored value is the inner object.