Skip to content

Commit

Permalink
Use sentinel value for inheriting parameter slots (#605)
Browse files Browse the repository at this point in the history
Co-authored-by: maximlt <[email protected]>
  • Loading branch information
jbednar and maximlt authored Apr 12, 2023
1 parent 7ece88c commit 23fc4de
Show file tree
Hide file tree
Showing 11 changed files with 530 additions and 217 deletions.
172 changes: 124 additions & 48 deletions examples/user_guide/Parameters.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"import param\n",
"from param import Parameter, Parameterized\n",
"\n",
"p = Parameter(42, doc=\"The answer\", constant=True)\n",
"p = Parameter(default=42, doc=\"The answer\", constant=True)\n",
"p.default"
]
},
Expand Down Expand Up @@ -87,8 +87,8 @@
"source": [
"class A(Parameterized):\n",
" question = Parameter(\"What is it?\", doc=\"The question\")\n",
" answer = Parameter(2, constant=True, doc=\"The answer\")\n",
" ultimate_answer = Parameter(42, readonly=True, doc=\"The real answer\")\n",
" answer = Parameter(default=2, constant=True, doc=\"The answer\")\n",
" ultimate_answer = Parameter(default=42, readonly=True, doc=\"The real answer\")\n",
"\n",
"a = A(question=\"How is it?\", answer=\"6\")"
]
Expand Down Expand Up @@ -166,15 +166,24 @@
"metadata": {},
"outputs": [],
"source": [
"b=A()\n",
"b = A()\n",
"b.answer"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If accessing the attribute always gives us a value whether on the instance or the class, what happened to the `Parameter` objects? They are stored on the Parameterized instance or class, and are accessible via a special `param` accessor object at either the instance or class levels:"
"If accessing the attribute always gives us a value whether on the instance or the class, what happened to the `Parameter` objects? They are stored on the Parameterized instance or class, and are accessible via a special `param` accessor object at either the instance or class levels, via attribute or key:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"a.param['question']"
]
},
{
Expand Down Expand Up @@ -227,8 +236,8 @@
"outputs": [],
"source": [
"with param.exceptions_summarized():\n",
" a.question=True\n",
" a.answer=5"
" a.question = True\n",
" a.answer = 5"
]
},
{
Expand All @@ -254,7 +263,7 @@
"outputs": [],
"source": [
"with param.edit_constant(a):\n",
" a.answer=30\n",
" a.answer = 30\n",
"a.answer"
]
},
Expand All @@ -269,9 +278,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Parameter inheritance and instantiation\n",
"## Parameter inheritance\n",
"\n",
"Much of the parameter metadata is there to help you control whether and how the parameter value is instantiated on Parameterized objects as they are created or new Parameterized subclasses as they are defined. Depending on how you want to use that Parameter and what values it might take, controlling instantiation can be very important when mutable values are involved. First, let's look at the default behavior, which is appropriate for immutable attributes:"
"`Parameter` objects and their metadata are inherited in a hierarchy of `Parameterized` objects. Let's see how that works:"
]
},
{
Expand All @@ -280,8 +289,13 @@
"metadata": {},
"outputs": [],
"source": [
"class A(Parameterized):\n",
" question = Parameter(\"What is it?\", doc=\"The question\")\n",
" answer = Parameter(default=2, constant=True, doc=\"The answer\")\n",
" ultimate_answer = Parameter(default=42, readonly=True, doc=\"The real answer\")\n",
"\n",
"class B(A):\n",
" ultimate_answer = Parameter(84, readonly=True)\n",
" ultimate_answer = Parameter(default=84)\n",
"\n",
"b = B()\n",
"b.question"
Expand All @@ -293,7 +307,7 @@
"metadata": {},
"outputs": [],
"source": [
"A.question=\"How are you?\""
"A.question = \"How are you?\""
]
},
{
Expand All @@ -309,7 +323,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Here you can see that B inherits the `question` parameter from A, and as long as `question` has not been set explicitly on `b`, `b.question` will report the value from where that Parameter was defined, i.e. A in this case. If `question` is subsequently set on `b`, `b.question` will no longer be affected by the value in `A`:"
"Here you can see that B inherits `question` from A, and as long as `question` has not been set explicitly on `b`, `b.question` will report the value from where that Parameter was defined, i.e. A in this case. If `question` is subsequently set on `b`, `b.question` will no longer be affected by the value in `A`:"
]
},
{
Expand All @@ -318,16 +332,76 @@
"metadata": {},
"outputs": [],
"source": [
"b.question=\"Why?\"\n",
"A.question=\"Who?\"\n",
"b.question = \"Why?\"\n",
"A.question = \"Who?\"\n",
"b.question"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As you can see, parameters not specified in B are still fully usable in it, if they were declared in a superclass. Metadata associated with that parameter is also inherited if not explicitly overidden in `B`. E.g. `help(b)` or `help(B)` will list all parameters:"
"As you can see, parameters not specified in B are still fully usable in it, if they were declared in a superclass. **Metadata associated with that parameter is also inherited**, if not explicitly overidden in `B`.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"b.param.ultimate_answer.constant"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"b.param.ultimate_answer.readonly"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"b.ultimate_answer"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"b.param.ultimate_answer.default"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"b.param.ultimate_answer.doc"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Looking at the metadata values of `ultimate_answer` on `b` or `B` you can see that:\n",
"\n",
"- All the default metadata values like `constant`, `allow_none`, ..., were inherited from the base `Parameter` object provided by Param\n",
"- The `read_only` and `doc` metadata values were inherited from `A`\n",
"- The `default` metadata value of `ultimate_answer` in `B` overrode the value provided in `A`.\n",
"\n",
"Parameter inheritance like this lets you (a) use a parameter in many subclasses without having to define it more than once, and (b) control the value of that parameter conveniently across the entire set of subclasses and instances, as long as that attribute has not been set on those objects already. Using inheritance in this way is a very convenient mechanism for setting default values and other \"global\" parameters, whether before a program starts executing or during it.\n",
"\n",
"`help(b)` or `help(B)` will list all parameters:"
]
},
{
Expand All @@ -341,9 +415,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Parameter inheritance like this lets you (a) use a parameter in many subclasses without having to define it more than once, and (b) control the value of that parameter conveniently across the entire set of subclasses and instances, as long as that attribute has not been set on those objects already. Using inheritance in this way is a very convenient mechanism for setting default values and other \"global\" parameters, whether before a program starts executing or during it.\n",
"## Parameter value instantiation\n",
"\n",
"However, what happens if the value (unlike Python strings) is mutable? Things get a lot more complex:"
"So much of the parameter metadata is there to help you control whether and how the parameter value is instantiated on Parameterized objects as they are created or new Parameterized subclasses as they are defined. Depending on how you want to use that Parameter and what values it might take, controlling instantiation can be very important when mutable values are involved. While the default behavior shown above is appropriate for **immutable attributes**, what happens if the value (unlike Python strings) is mutable? Things get a lot more complex."
]
},
{
Expand All @@ -352,11 +426,11 @@
"metadata": {},
"outputs": [],
"source": [
"s = [1,2,3]\n",
"s = [1, 2, 3]\n",
"\n",
"class C(Parameterized):\n",
" s1 = param.Parameter(s, doc=\"A sequence\")\n",
" s2 = param.Parameter(s, doc=\"Another sequence\")\n",
" s1 = Parameter(s, doc=\"A sequence\")\n",
" s2 = Parameter(s, doc=\"Another sequence\")\n",
"\n",
"c = C()"
]
Expand All @@ -365,7 +439,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Here, both parameters `s1` and `s2` effectively point to the same underlying sequence `s`:"
"Here, both parameters `s1` and `s2`, on both `A` and `B` effectively point to the same underlying sequence `s`:"
]
},
{
Expand All @@ -383,7 +457,7 @@
"metadata": {},
"outputs": [],
"source": [
"s[1]*=5"
"s[1] *= 5"
]
},
{
Expand All @@ -410,7 +484,7 @@
"metadata": {},
"outputs": [],
"source": [
"c.s1[2]='a'"
"c.s1[2] = 'a'"
]
},
{
Expand All @@ -435,7 +509,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"As you can see, there is only one actual sequence here, and `s`, `s1`, and `s2` all point to it. In some cases such behavior is desirable, e.g. if the mutable object is a specific global list (e.g. a set of search paths) with a unique identity and all of the parameters are meant to point to that specific item. In other cases, it's the contents of the mutable item that are important, and no sharing of contents is intended. Luckily, Param supports that case as well, if you provide `instantiate=True`:"
"As you can see, there is only one actual sequence here, and `s`, `s1`, and `s2` all point to it. In some cases such behavior is desirable, e.g. if the mutable object is a specific global list (e.g. a set of search paths) with a unique identity and all of the parameters are meant to point to that specific item. In other cases, it's the contents of the mutable item that are important, and no sharing of contents is intended. Luckily, Param supports that case as well, if you provide `instantiate=True` (default is `False`):"
]
},
{
Expand All @@ -447,8 +521,8 @@
"s = [1,2,3]\n",
"\n",
"class D(Parameterized):\n",
" s1 = Parameter(s, doc=\"A sequence\", instantiate=True)\n",
" s2 = Parameter(s, doc=\"Another sequence\", instantiate=True)\n",
" s1 = Parameter(default=s, doc=\"A sequence\", instantiate=True)\n",
" s2 = Parameter(default=s, doc=\"Another sequence\", instantiate=True)\n",
"\n",
"d = D()"
]
Expand All @@ -475,7 +549,7 @@
"metadata": {},
"outputs": [],
"source": [
"s*=2"
"s *= 2"
]
},
{
Expand All @@ -502,7 +576,7 @@
"metadata": {},
"outputs": [],
"source": [
"d.s1[2]='a'"
"d.s1[2] = 'a'"
]
},
{
Expand All @@ -525,9 +599,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Parameter metadata inheritance and instantiation\n",
"## Parameter object instantiation\n",
"\n",
"`instantiate` controls how parameter _values_ behave, but similar issues arise for Parameter _objects_, which offer similar control via the `per_instance` metadata declaration. `per_instance` (True by default) provides a logically distinct Parameter object for every Parameterized instance, allowing each such instance to have different metadata for that parameter. For example, we can set the label separately for each instance without clobbering each other:"
"`instantiate` controls how parameter _values_ behave, but similar issues arise for Parameter _objects_, which offer similar control via the `per_instance` metadata declaration. `per_instance` (`True` by default) provides a logically distinct Parameter object for every Parameterized instance, allowing each such instance to have different metadata for that parameter. For example, we can set the label separately for each instance without clobbering each other:"
]
},
{
Expand All @@ -538,8 +612,8 @@
"source": [
"d1 = D()\n",
"d2 = D()\n",
"d1.param.s1.label=\"sequence 1\"\n",
"d2.param.s1.label=\"Sequence 1\"\n",
"d1.param.s1.label = \"sequence 1\"\n",
"d2.param.s1.label = \"(sequence 1)\"\n",
"d2.param.s1.label"
]
},
Expand All @@ -566,11 +640,11 @@
"outputs": [],
"source": [
"class E(Parameterized):\n",
" a = param.Parameter(3.14, label=\"pi\", per_instance=False)\n",
" a = Parameter(default=3.14, label=\"pi\", per_instance=False)\n",
"\n",
"e1 = E()\n",
"e2 = E()\n",
"e2.param.a.label=\"Pie\"\n",
"e2.param.a.label = \"Pie\"\n",
"e1.param.a.label"
]
},
Expand All @@ -592,10 +666,10 @@
"outputs": [],
"source": [
"class S(param.Parameterized):\n",
" l = param.Parameter([1,2,3], instantiate=True)\n",
" l = Parameter(default=[1,2,3], instantiate=True)\n",
"\n",
"ss = [S() for i in range(10)]\n",
"ss[0].l[2]=5\n",
"ss[0].l[2] = 5\n",
"ss[1].l"
]
},
Expand All @@ -617,7 +691,7 @@
"with param.shared_parameters():\n",
" ps = [S() for i in range(10)]\n",
" \n",
"ps[0].l[2]=5\n",
"ps[0].l[2] = 5\n",
"ps[1].l"
]
},
Expand Down Expand Up @@ -650,13 +724,13 @@
"import param\n",
"\n",
"class Q(param.Parameterized):\n",
" a = param.Number(39, bounds=(0,50))\n",
" b = param.String(\"str\")\n",
" a = param.Number(default=39, bounds=(0,50))\n",
" b = param.String(default=\"str\")\n",
"\n",
"class P(Q):\n",
" c = param.ClassSelector(Q, Q())\n",
" e = param.ClassSelector(param.Parameterized, param.Parameterized())\n",
" f = param.Range((0,1))\n",
" c = param.ClassSelector(default=Q(), class_=Q)\n",
" e = param.ClassSelector(default=param.Parameterized(), class_=param.Parameterized)\n",
" f = param.Range(default=(0,1))\n",
"\n",
"p = P(f=(2,3), c=P(f=(42,43)), name=\"demo\")"
]
Expand Down Expand Up @@ -745,14 +819,16 @@
" \"\"\"Integer Parameter that must be even\"\"\"\n",
"\n",
" def _validate_value(self, val, allow_None):\n",
" super(EvenInteger, self)._validate_value(val, allow_None)\n",
" super()._validate_value(val, allow_None)\n",
" if not isinstance(val, numbers.Number):\n",
" raise ValueError(\"EvenInteger parameter %r must be a number, \"\n",
" \"not %r.\" % (self.name, val))\n",
" raise ValueError(\n",
" f\"EvenInteger parameter {self.name!r} must be a number, not {val!r}.\"\n",
" )\n",
" \n",
" if not (val % 2 == 0):\n",
" raise ValueError(\"EvenInteger parameter %r must be even, \"\n",
" \"not %r.\" % (self.name, val))\n",
" raise ValueError(\n",
" f\"EvenInteger parameter {self.name!r} must be even, not {val!r}.\"\n",
" )\n",
"\n",
"class P(param.Parameterized):\n",
" n = param.Number()\n",
Expand Down
Loading

0 comments on commit 23fc4de

Please sign in to comment.