You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* reactive-mutable: tweak problem/solution examples
* add section on python operations that create copies
* simplyify css to just what we need to override
* use render.code in the other examples, too
Copy file name to clipboardExpand all lines: docs/reactive-mutable.qmd
+72-8Lines changed: 72 additions & 8 deletions
Original file line number
Diff line number
Diff line change
@@ -73,6 +73,28 @@ b
73
73
74
74
The advantage to this approach is not eagerly creating defensive copies all the time, as we must in the "copy on assignment" approach. However, if you are performing more updates than assignments, this approach actually makes _more_ copies, plus it gives you more opportunities to slip up and forget not to mutate the object.
75
75
76
+
### Python operations that create copies
77
+
78
+
We've seen that `x + [value]` creates a new list object and that `x.copy()` creates a new list object.
79
+
There are some other common operations that create copies.
80
+
You can use these patterns to avoid mutating reactive values in place.
81
+
82
+
1.**List comprehensions**:
83
+
`[x for x in a]` creates a new list with the same elements as `a`.
84
+
This approach is particularly useful when you need to transform the elements of a list in some way, as in `[x*2 for x in a]`.
85
+
86
+
2.**Slicing**:
87
+
`a[:]` creates a new list with the same elements as `a`.
88
+
This is useful when you need to copy the entire list, or a subset of it.
89
+
90
+
3.**Star operator**:
91
+
`[*a, value]` creates a new list with the same elements as `a`, with the additional `value` appended after them.
92
+
This is an easy way to add a single element to the end or start of a list (`[value, *a]`).
93
+
94
+
4.**Double star operator**:
95
+
`{**a, key: value}` creates a new dictionary with the same key-value pairs as `a`, with the additional key-value pair `key: value` added.
96
+
This is an easy way to add a single key-value pair to a dictionary.
97
+
76
98
### Use immutable objects
77
99
78
100
The third way is to use a different data structure entirely. Instead of list, we will use tuple, which is immutable. Immutable objects do not provide any way to change their values "in place", even if we wanted to. Therefore, we can be confident that nothing we do to tuple variable `a` could ever affect tuple variable `b`.
@@ -112,6 +134,7 @@ The rest of this article demonstrates these problems, and their solutions, in th
112
134
113
135
This demo app demonstrates that when an object that is stored in a `reactive.value` is mutated, the change is not visible to the `reactive.value` and no reactive invalidation occurs. Below, the `add_value_to_list` effect retrieves the list stored in `user_provided_values` and appends an item to it.
114
136
137
+
::: {.callout-warning title="Problem: No reactive update" appearance="simple"}
115
138
```{shinylive-python}
116
139
#| standalone: true
117
140
#| components: [editor, viewer]
@@ -124,7 +147,7 @@ from shiny.express import input, render, ui
124
147
ui.input_numeric("x", "Enter a value to add to the list:", 1)
Each time the button is clicked, a new item is added to the list; but the ` reactive.value` has no way to know anything has changed. (Surprisingly, even adding `user_provided_values.set(values)` to the end of `add_value_to_list` will not help; the reactive value will see that the identity of the new object is the same as its existing object, and ignore the change.)
142
167
143
168
Switching to the "copy on update" technique fixes the problem. The app below is identical to the one above, except for the body of `add_value_to_list`. Click on the button a few times--the results now appear correctly.
144
169
170
+
::: {.callout-tip title="Solution: Copy on update" appearance="simple"}
145
171
```{shinylive-python}
146
172
#| standalone: true
147
173
#| components: [editor, viewer]
@@ -154,7 +180,7 @@ from shiny.express import input, render, ui
154
180
ui.input_numeric("x", "Enter a value to add to the list:", 1)
Let's further modify our example; now, we will output not just the values entered by the user, but also a parallel list of those values after being doubled. This example is the same as the last one, with the addition of the `@reactive.calc` called `doubled_values`, which is then included in the text output. Click the button a few times, and you'll see that something is amiss.
200
+
Let's further modify our example; now, we will output not just the values entered by the user, but also a parallel list of those values after being doubled. This example is the same as the last one, with the addition of the `@reactive.calc` called `doubled_values`, which is then included in the text output.
201
+
202
+
In the example below, if you click the button three times, you'd expect the user values to be `[1, 1, 1]` and the doubled values to be `[2, 2, 2]`.
203
+
Click the button below three times.
204
+
What values do you actually get?
173
205
206
+
::: {.callout-warning title="Problem: Mutating in place" appearance="simple"}
174
207
```{shinylive-python}
175
208
#| standalone: true
176
209
#| components: [editor, viewer]
@@ -183,9 +216,9 @@ from shiny.express import input, render, ui
183
216
ui.input_numeric("x", "Enter a value to add to the list:", 1)
# Stores all the values the user has submitted so far
191
224
user_provided_values = reactive.value([])
@@ -202,9 +235,16 @@ def doubled_values():
202
235
values[i] *= 2
203
236
return values
204
237
```
238
+
:::
205
239
206
-
This is because `doubled_values` does its doubling by modifying the values of the list in place, causing these changes to "leak" back into `user_provided_values`. We could fix this by having `doubled_values` call `user_provided_values().copy()`, or by using a list comprehension (since it creates a new list and leaves the old one alone).
240
+
By the third click, the user input that should be `[1, 1, 1]` is instead `[4, 2, 1]`!
241
+
This is because `doubled_values` does its doubling by modifying the values of the list in place, causing these changes to "leak" back into `user_provided_values`.
207
242
243
+
We could fix this by having `doubled_values` call `user_provided_values().copy()`.
244
+
Or we can use a list comprehension, which creates a new list in the process.
245
+
The second option is shown below.
246
+
247
+
::: {.callout-tip title="Solution: Copy with list comprehension" appearance="simple"}
208
248
```{shinylive-python}
209
249
#| standalone: true
210
250
#| components: [editor, viewer]
@@ -217,9 +257,9 @@ from shiny.express import input, render, ui
217
257
ui.input_numeric("x", "Enter a value to add to the list:", 1)
0 commit comments