Reactive values

Published by onesixx on

https://shiny.posit.co/py/docs/reactive-foundations.html#values

reactive.value는 (input 값과 마찬가지로)  reactive dependency입니다(즉, reactive functions를 invalidate하는 데 사용할 수 있습니다).

하지만, input값과 달리, 특별한 입력 컨트롤이 없어도 되므로, 프로그래밍을 통해 값을 업데이트할 수 있습니다. 따라서 주로 앱에서 state를 유지하는 데 유용합니다.

EX. 사용자가 슬라이더를 움직인 값의 기록을 (reactive.value를 통해) 추적하는 예,
reactive.value인 rct_vals가 초기화될 때, 초기 값(여기서는 빈 list [])을 받습니다.
그런 다음 @reactive.effect는 슬라이더 값이 변경될 때마다 슬라이더 값을 추가합니다.

여기서, @reactive.effect에서 값을 가져오고 설정하기 때문에 무한 루프를 방지하기 위해,
@reactive.event가 필요합니다.

''' reactive value example '''
from shiny import App, Inputs, Outputs, Session, ui, reactive, render

app_ui = ui.div(
    ui.input_slider("x", "Slider value",
        min=0, max=100, value=10),
    ui.output_ui("out"),
)
def server(input: Inputs, output: Outputs, session: Session):
    rct_vals = reactive.value([])

    # Track the history of the slider
    @reactive.effect
    @reactive.event(input.x)
    def _():
        rct_vals.set([input.x()] + rct_vals())     # 1

    @render.ui
    def out():
        return [ui.p(x) for x in rct_vals()]       # 2

app = App(app_ui, server)

Maintaining state

Reactive values 은 App서 state를 유지하는 데 자주 사용된다.
위 예에서 슬라이더의 히스토리를 추적하는 데 사용하고 있지만,
그 외에도 여러 가지 용도로 사용할 수 있습니다(방문한 페이지/탭, 플롯에서 클릭한 지점 등).

주의가 필요!!

mutable objects(변경 가능한 객체 예: list, dict 등)를 reactive values 로 사용할 때는 주의하세요.
object in-place 수정하면, Shiny는 객체가 변경된 것을 알지 못하며
객체에 의존하는 모든 반응형 함수를 무효화하지 않습니다.
https://shiny.posit.co/py/docs/reactive-mutable.html

Mutable objects 문제점

python객체는 mutability(가변성)를 가지고 있다.

  • Mutable objects : lists, dicts
  • immutable objects : numbers, strings, bools, tuples …

==> 대부분의 object들은 Mutable objects 이고,
즉, 프로그램의 한 부분에서 object를 수정하면 프로그램의 다른 부분에서 (예기치 않게) object가 달라질 수 있습니다. (modified in place). 따라서 Mutable objects의 사용함에 있어 주의가 필요하다.

특히 Mutable objects가 shiny의 reactivity와 문제를 일으키는 이유는
reactive.value에 저장된 mutable object나 reactive.calc에서 return된 object를 수정하면
해당 값의 다른 consumer도 값이 변경됩니다.
이로 인해 두 가지 문제가 발생할 수 있습니다.
첫째, 변경된 값은 예상치 못한 값일 수 있습니다.
둘째, 예상된 값의 변경이고, 심지어 원하는 값으로 잘 변경되더라도, downstream reactive objects가 다시 실행되도록 트리거되지 않습니다. 즉 b는 변했지만, shiny가 변한것을 인지하지 못한다.

a = 1
b = a

a += 1
b
==> 1  (a가 수정되도, b에 영향이 없다.) 
a = [1, 2]
b = a

a.append(3)
b
==> [1, 2, 3]   ([1, 2]가 아니라)

해결책

1. 새로운 list 객체를 생성.

copy on assignment (할당 시 복사)

새로운 context에서 사용할 때마다,
객체를 copy하여 애초에 두 변수가 동일한 객체를 가리키지 않도록 하는 것

a = [1, 2]
b = a.copy()

a.append(3)
b
==> [1, 2]

copy on update x + [value]

기존 목록을 변경하는 x.append(value)대신에
원래 목록 a는 변경하지 않고 원하는 결과를 가진 새 목록 개체를 생성하는 a + [value] 방법을 활용

a = [1, 2]
b = a

a = a + [3]
b
==> [1, 2]

이 접근법의 장점은 ‘할당 시 복사’ 접근법에서처럼 항상 방어적인 복사본을 열심히 만들지 않아도 된다는 것입니다. 그러나 할당보다 더 많은 업데이트를 수행하는 경우 이 접근 방식은 실제로 더 많은 복사본을 만들 뿐만 아니라 실수로 개체를 변경하는 것을 잊어버릴 가능성이 더 커집니다.

https://shiny.posit.co/py/docs/reactive-mutable.html#python-operations-that-create-copies

2. 연산자를 활용

List comprehensions [x for x in a]

a와 동일한 element를 갖는 새로운 List를 생성한다.
[x*2 for x in a]와 같이, List의 element를 변형해야 할 때, 유용.

Slicing a[:]

a와 동일한 element를 갖는 새로운 List를 생성한다
전체 list 또는 list의 subset을 copy할때 유용.

Star operator [*a, value]

a와 동일한 element를 갖는 새로운 List를 생성하고, 그 뒤에 추가적인 값을 append한다.
list([value, *a])목록의 끝이나 시작에 단일 요소를 추가하는 쉬운 방법입니다.

Double star operator{**a, key: value}

a와 동일한 key-value 쌍을 가진 새로운 dictionary을 생성하고, key: value값이 추가됩니다.
이는 하나의 키-값 쌍을 사전에 추가하는 쉬운 방법입니다.

3. immutable objects 활용

완전히 다른 데이터 구조를 사용하는 것입니다.
List 대신 불변인 Tuple을 사용합니다.
Immutable objects는 우리가 원하더라도 값을 “in place” 변경할 수 있는 방법을 제공하지 않습니다.
따라서 tuple 변수 a에 대한 어떠한 작업도 tuple 변수 b에 영향을 줄 수 없다고 확신할 수 있습니다

a = (1, 2)
b = a

a = (*a, 3)  # alternatively, a = a + (3,)
b
==> (1, 2)

이 접근법의 장점은 ‘할당 시 복사’ 접근법에서처럼 항상 방어적인 복사본을 열심히 만들지 않아도 된다는 것입니다.
그러나 할당보다 더 많은 업데이트를 수행하는 경우, 이 접근 방식은 실제로 더 많은 복사본을 만들 뿐만 아니라 실수로 개체를 변경하는 것을 잊어버릴 가능성이 더 커집니다.

Categories: shiny

onesixx

Blog Owner

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x