인터랙티브 피규어와 비동기 프로그래밍 #

Matplotlib는 그림을 GUI 창에 삽입하여 풍부한 대화형 그림을 지원합니다. 데이터를 검사하기 위해 축을 이동하고 확대하는 기본 상호 작용은 Matplotlib에 '적용'됩니다. 이는 정교한 대화형 그래프를 구축하는 데 사용할 수 있는 전체 마우스 및 키보드 이벤트 처리 시스템에서 지원됩니다.

이 가이드는 GUI 이벤트 루프와 Matplotlib 통합이 작동하는 방식에 대한 낮은 수준의 세부 정보를 소개하기 위한 것입니다. Matplotlib 이벤트 API에 대한 보다 실용적인 소개는 이벤트 처리 시스템 , 대화형 자습서Matplotlib를 사용하는 대화형 응용 프로그램을 참조하십시오 .

이벤트 루프 #

기본적으로 모든 사용자 상호 작용(및 네트워킹)은 (OS를 통해) 사용자의 이벤트를 기다린 다음 이에 대해 조치를 취하는 무한 루프로 구현됩니다. 예를 들어, 최소 REPL(Read Evaluate Print Loop)은

exec_count = 0
while True:
    inp = input(f"[{exec_count}] > ")        # Read
    ret = eval(inp)                          # Evaluate
    print(ret)                               # Print
    exec_count += 1                          # Loop

여기에는 많은 세부 사항이 누락되어 있지만(예: 첫 번째 예외에서 종료됩니다!) 모든 터미널, GUI 및 서버 [ 1 ] 의 기반이 되는 이벤트 루프를 나타냅니다 . 일반적으로 읽기 단계는 일종의 I/O(사용자 입력 또는 네트워크)를 기다리는 반면 평가인쇄 는 입력을 해석한 다음 이에 대해 조치 를 취합니다.

실제로 우리는 I/O 루프 [ 2 ] 를 직접 구현하는 대신 특정 이벤트에 대한 응답으로 실행할 콜백을 등록하는 메커니즘을 제공하는 프레임워크와 상호 작용합니다 . 예를 들어 "사용자가 이 버튼을 클릭하면 이 기능을 실행하십시오." 또는 "사용자가 'z' 키를 누르면 이 다른 기능을 실행하십시오." 이를 통해 사용자 는 I/O 의 핵심 [ 3 ] 세부 사항 을 조사하지 않고도 반응형 이벤트 구동 프로그램을 작성할 수 있습니다 . 코어 이벤트 루프는 "메인 루프"라고도 하며 일반적으로 라이브러리에 따라 , 또는 와 같은 이름을 가진 메서드에 의해 _exec시작 run됩니다 start.

Signal모든 GUI 프레임워크(Qt, Wx, Gtk, tk, OSX 또는 웹)에는 사용자 상호 작용을 캡처하여 애플리케이션(예 : Qt의 프레임워크) 으로 다시 전달하는 몇 가지 방법이 Slot있지만 정확한 세부 정보는 툴킷에 따라 다릅니다. Matplotlib에는 툴킷 API를 사용하여 툴킷 UI 이벤트를 Matplotlib의 이벤트 처리 시스템 에 연결하는 우리가 지원하는 각 GUI 툴킷에 대한 백엔드 가 있습니다 . 그런 다음 를 사용하여 함수를 Matplotlib의 이벤트 처리 시스템에 연결할 수 있습니다. 이를 통해 데이터와 직접 상호 작용하고 GUI 툴킷에 구애받지 않는 사용자 인터페이스를 작성할 수 있습니다.FigureCanvasBase.mpl_connect

명령 프롬프트 통합 #

여태까지는 그런대로 잘됐다. REPL(IPython 터미널과 같은)이 있어 대화식으로 인터프리터에 코드를 보내고 결과를 다시 받을 수 있습니다. 또한 사용자 입력을 기다리는 이벤트 루프를 실행하고 입력이 발생할 때 실행할 기능을 등록할 수 있는 GUI 툴킷도 있습니다. 그러나 두 가지를 모두 수행하려면 문제가 있습니다. 프롬프트와 GUI 이벤트 루프는 둘 다 각자가 담당한다고 생각하는 무한 루프 입니다 ! 프롬프트와 GUI 창 모두 응답하려면 루프를 '시간 공유'할 수 있는 방법이 필요합니다.

  1. 대화형 창을 원할 때 GUI 메인 루프가 파이썬 프로세스를 차단하도록 하십시오.

  2. CLI 기본 루프가 Python 프로세스를 차단하고 간헐적으로 GUI 루프를 실행하도록 합니다.

  3. GUI에 파이썬을 완전히 내장(하지만 이것은 기본적으로 전체 애플리케이션을 작성하는 것입니다)

프롬프트 차단 #

pyplot.show

열려 있는 모든 Figure를 표시합니다.

pyplot.pause

간격 초 동안 GUI 이벤트 루프를 실행합니다 .

backend_bases.FigureCanvasBase.start_event_loop

차단 이벤트 루프를 시작합니다.

backend_bases.FigureCanvasBase.stop_event_loop

현재 차단 이벤트 루프를 중지합니다.

가장 간단한 "통합"은 '차단' 모드에서 GUI 이벤트 루프를 시작하고 CLI를 인수하는 것입니다. GUI 이벤트 루프가 실행되는 동안 프롬프트에 새 명령을 입력할 수 없습니다(터미널은 터미널에 입력된 문자를 반향할 수 있지만 GUI 이벤트 루프를 실행 중이기 때문에 Python 인터프리터로 전송되지 않습니다). 그림 창은 반응할 것입니다. 이벤트 루프가 중지되면(여전히 열려 있는 Figure 창은 응답하지 않음) 프롬프트를 다시 사용할 수 있습니다. 이벤트 루프를 다시 시작하면 열려 있는 그림이 다시 응답하게 됩니다(대기 중인 사용자 상호 작용을 처리함).

열려 있는 모든 그림이 닫힐 때까지 이벤트 루프를 시작하려면 다음을 사용하십시오 pyplot.show.

pyplot.show(block=True)

고정된 시간(초 단위) 동안 이벤트 루프를 시작하려면 를 사용 pyplot.pause하십시오.

사용하지 않는 경우 및 pyplot를 통해 이벤트 루프를 시작하고 중지할 수 있습니다 . 그러나 사용하지 않는 대부분의 컨텍스트에서는 대형 GUI 응용 프로그램에 Matplotlib를 내장하고 있으며 응용 프로그램에 대해 GUI 이벤트 루프가 이미 실행 중이어야 합니다.FigureCanvasBase.start_event_loopFigureCanvasBase.stop_event_looppyplot

프롬프트에서 벗어나 이 기술은 사용자 상호 작용을 위해 일시 ​​중지하거나 추가 데이터에 대한 폴링 사이에 그림을 표시하는 스크립트를 작성하려는 경우 매우 유용할 수 있습니다. 자세한 내용은 스크립트 및 함수 를 참조하십시오.

입력 후크 통합 #

차단 모드에서 GUI 이벤트 루프를 실행하거나 UI 이벤트를 명시적으로 처리하는 것이 유용하지만 더 잘할 수 있습니다! 우리는 사용 가능한 프롬프트 및 대화형 그림 창 을 가질 수 있기를 정말로 원합니다 .

대화식 프롬프트의 '입력 후크' 기능을 사용하여 이를 수행할 수 있습니다. 이 훅은 사용자가 입력하기를 기다릴 때 프롬프트에 의해 호출됩니다(빠른 타이피스트의 경우에도 프롬프트는 대부분 인간이 생각하고 손가락을 움직일 때까지 기다립니다). 세부 사항은 프롬프트마다 다르지만 논리는 대략

  1. 키보드 입력 대기 시작

  2. GUI 이벤트 루프 시작

  3. 사용자가 키를 누르는 즉시 GUI 이벤트 루프를 종료하고 키를 처리합니다.

  4. 반복하다

이는 대화형 GUI 창과 대화형 프롬프트가 동시에 있는 듯한 착각을 불러일으킵니다. 대부분의 경우 GUI 이벤트 루프가 실행되지만 사용자가 입력을 시작하자마자 프롬프트가 다시 이어집니다.

이 시분할 기술은 파이썬이 유휴 상태이고 사용자 입력을 기다리는 동안에만 이벤트 루프가 실행되도록 허용합니다. 장기 실행 코드 중에 GUI가 응답하도록 하려면 위에서 설명한 대로 GUI 이벤트 큐를 주기적으로 플러시해야 합니다 . 이 경우 프로세스를 차단하는 것은 REPL이 아니라 귀하의 코드이므로 "시간 공유"를 수동으로 처리해야 합니다. 반대로 매우 느린 도형 그리기는 그리기가 끝날 때까지 프롬프트를 차단합니다.

전체 임베딩 #

다른 방향으로 이동하여 풍부한 기본 애플리케이션에 그림(및 Python 인터프리터 )을 완전히 포함시키는 것도 가능합니다. Matplotlib는 GUI 응용 프로그램에 직접 포함할 수 있는 각 툴킷에 대한 클래스를 제공합니다(이는 내장 창을 구현하는 방법입니다!). 자세한 내용 은 그래픽 사용자 인터페이스에 Matplotlib 포함 을 참조하십시오.

스크립트 및 기능 #

backend_bases.FigureCanvasBase.flush_events

그림에 대한 GUI 이벤트를 플러시합니다.

backend_bases.FigureCanvasBase.draw_idle

컨트롤이 GUI 이벤트 루프로 반환되면 위젯 다시 그리기를 요청합니다.

figure.Figure.ginput

그림과 상호 작용하기 위해 호출을 차단합니다.

pyplot.ginput

그림과 상호 작용하기 위해 호출을 차단합니다.

pyplot.show

열려 있는 모든 Figure를 표시합니다.

pyplot.pause

간격 초 동안 GUI 이벤트 루프를 실행합니다 .

스크립트에서 대화형 그림을 사용하는 몇 가지 사용 사례가 있습니다.

  • 스크립트를 조정하기 위해 사용자 입력 캡처

  • 장기 실행 스크립트가 진행됨에 따라 진행률 업데이트

  • 데이터 소스에서 스트리밍 업데이트

차단 기능 #

Axes에서만 포인트를 수집해야 하는 경우 사용할 수 Figure.ginput있거나 보다 일반적으로 도구 blocking_input의 도구가 이벤트 루프의 시작 및 중지를 처리합니다. 그러나 사용자 지정 이벤트 처리를 작성했거나 사용 중인 경우 위에서widgets 설명한 방법을 사용하여 GUI 이벤트 루프를 수동으로 실행해야 합니다 .

프롬프트 차단에 설명된 방법을 사용 하여 GUI 이벤트 루프 실행을 일시 중단할 수도 있습니다. 루프가 종료되면 코드가 다시 시작됩니다. 일반적으로 사용하려는 모든 위치 에서 대화형 그림의 추가 이점과 함께 대신 사용할 time.sleep수 있습니다 .pyplot.pause

예를 들어 데이터를 폴링하려면 다음과 같은 것을 사용할 수 있습니다.

fig, ax = plt.subplots()
ln, = ax.plot([], [])

while True:
    x, y = get_new_data()
    ln.set_data(x, y)
    plt.pause(1)

새 데이터를 폴링하고 1Hz에서 수치를 업데이트합니다.

명시적으로 이벤트 루프 # 회전

backend_bases.FigureCanvasBase.flush_events

그림에 대한 GUI 이벤트를 플러시합니다.

backend_bases.FigureCanvasBase.draw_idle

컨트롤이 GUI 이벤트 루프로 반환되면 위젯 다시 그리기를 요청합니다.

보류 중인 UI 이벤트(마우스 클릭, 버튼 누르기 또는 그리기)가 있는 창이 열려 있는 경우 를 호출하여 해당 이벤트를 명시적으로 처리할 수 있습니다 FigureCanvasBase.flush_events. 이것은 현재 대기 중인 모든 UI 이벤트가 처리될 때까지 GUI 이벤트 루프를 실행합니다. 정확한 동작은 백엔드에 따라 다르지만 일반적으로 모든 그림의 이벤트가 처리되고 처리 대기 중인 이벤트만 처리됩니다(처리 중에 추가된 이벤트 아님).

예를 들어

import time
import matplotlib.pyplot as plt
import numpy as np
plt.ion()

fig, ax = plt.subplots()
th = np.linspace(0, 2*np.pi, 512)
ax.set_ylim(-1.5, 1.5)

ln, = ax.plot(th, np.sin(th))

def slow_loop(N, ln):
    for j in range(N):
        time.sleep(.1)  # to simulate some work
        ln.figure.canvas.flush_events()

slow_loop(100, ln)

이것은 약간 느린 것처럼 느껴지지만(우리는 100ms마다 사용자 입력을 처리하는 반면 20-30ms는 "반응"이라고 느끼는 것입니다) 반응할 것입니다.

플롯을 변경하고 다시 렌더링하려면 호출 draw_idle하여 캔버스를 다시 그려달라고 요청해야 합니다. 이 방법은 draw_soon 과 유사하게 생각할 수 있습니다 asyncio.loop.call_soon.

위의 예에 다음과 같이 추가할 수 있습니다.

def slow_loop(N, ln):
    for j in range(N):
        time.sleep(.1)  # to simulate some work
        if j % 10:
            ln.set_ydata(np.sin(((j // 10) % 5 * th)))
            ln.figure.canvas.draw_idle()

        ln.figure.canvas.flush_events()

slow_loop(100, ln)

더 자주 호출 FigureCanvasBase.flush_events할수록 그림의 반응이 더 좋아지지만 시각화에 더 많은 리소스를 사용하고 계산에 더 적은 비용을 들이게 됩니다.

오래된 아티스트 #

아티스트(Matplotlib 1.5부터)는 아티스트의 내부 상태가 마지막으로 렌더링된 이후 변경된 경우 오래된 속성을 가 집니다. True기본적으로 부실 상태는 그리기 트리의 아티스트 부모에게 전파됩니다. 예를 Line2D 들어 인스턴스의 색상이 변경되면 이를 포함하는 AxesFigure에도 "부실"로 표시됩니다. 따라서 fig.stale그림의 아티스트가 수정되어 화면에 표시된 것과 동기화되지 않은 경우 보고합니다. draw_idle이는 그림의 다시 렌더링을 예약하기 위해 호출해야 하는지 여부를 결정하는 데 사용하기 위한 것 입니다.

각 아티스트에는 Artist.stale_callback서명이 있는 콜백을 보유하는 속성이 있습니다.

def callback(self: Artist, val: bool) -> None:
   ...

기본적으로 오래된 상태를 아티스트의 부모에게 전달하는 기능으로 설정됩니다. 특정 아티스트의 전파를 억제하려면 이 속성을 없음으로 설정하십시오.

Figure인스턴스에는 아티스트가 포함되어 있지 않으며 기본 콜백은 None입니다. 당신이 전화를 걸고 pyplot.ion있지 않다면 IPython우리는 콜백을 설치하여 낡은 것이 draw_idle될 때마다 호출할 것입니다. 우리는 후크를 사용하여 사용자의 입력을 실행한 후 사용자에게 프롬프트를 반환하기 전에 오래된 그림을 호출 Figure합니다 . 사용하지 않는 경우 callback 속성을 사용하여 그림이 오래되었을 때 알림을 받을 수 있습니다.IPython'post_execute'draw_idlepyplotFigure.stale_callback

유휴 무승부 #

backend_bases.FigureCanvasBase.draw

를 렌더링합니다 Figure.

backend_bases.FigureCanvasBase.draw_idle

컨트롤이 GUI 이벤트 루프로 반환되면 위젯 다시 그리기를 요청합니다.

backend_bases.FigureCanvasBase.flush_events

그림에 대한 GUI 이벤트를 플러시합니다.

거의 모든 경우에 backend_bases.FigureCanvasBase.draw_idleover 를 사용하는 것이 좋습니다 backend_bases.FigureCanvasBase.draw. draw그림의 렌더링을 강제하는 반면 draw_idle다음에 GUI 창이 화면을 다시 칠할 때 렌더링을 예약합니다. 이렇게 하면 화면에 표시될 픽셀만 렌더링하여 성능이 향상됩니다. 화면이 가능한 빨리 업데이트되도록 하려면 다음을 수행하십시오.

fig.canvas.draw_idle()
fig.canvas.flush_events()

스레딩 #

대부분의 GUI 프레임워크는 화면에 대한 모든 업데이트와 메인 이벤트 루프가 메인 스레드에서 실행되도록 요구합니다. 이로 인해 플롯의 주기적인 업데이트를 백그라운드 스레드로 푸시할 수 없습니다. 역방향으로 보이지만 일반적으로 계산을 백그라운드 스레드로 푸시하고 주 스레드에서 그림을 주기적으로 업데이트하는 것이 더 쉽습니다.

일반적으로 Matplotlib는 스레드로부터 안전하지 않습니다. 한 스레드에서 개체 를 업데이트 Artist하고 다른 스레드에서 그리려는 경우 중요 섹션을 잠그고 있는지 확인해야 합니다.

Eventloop 통합 메커니즘 #

CPython / readline #

PyOS_InputHookPython C API는 실행될 함수를 등록하기 위한 후크를 제공합니다 ("파이썬의 인터프리터 프롬프트가 유휴 상태가 되고 터미널에서 사용자 입력을 기다리려고 할 때 함수가 호출됩니다."). 이 후크는 두 번째 이벤트 루프(GUI 이벤트 루프)를 Python 입력 프롬프트 루프와 통합하는 데 사용할 수 있습니다. 후크 함수는 일반적으로 GUI 이벤트 큐에서 보류 중인 모든 이벤트를 소진하거나 짧은 고정 시간 동안 메인 루프를 실행하거나 stdin에서 키를 누를 때까지 이벤트 루프를 실행합니다.

PyOS_InputHookMatplotlib는 현재 Matplotlib이 사용되는 다양한 방법으로 인해 어떠한 관리도 수행하지 않습니다 . 이 관리는 다운스트림 라이브러리(사용자 코드 또는 셸)에 맡겨집니다. 대화형 그림은 Matplotlib가 '대화형 모드'인 경우에도 해당 항목 PyOS_InputHook이 등록되지 않은 경우 바닐라 파이썬 repl에서 작동하지 않을 수 있습니다.

입력 후크 및 이를 설치하기 위한 헬퍼는 일반적으로 GUI 툴킷용 Python 바인딩에 포함되어 있으며 가져오기 시 등록될 수 있습니다. IPython은 또한 Matplotlib가 지원하는 모든 GUI 프레임워크에 대한 입력 후크 기능을 제공하며 NET을 통해 설치할 수 있습니다 %matplotlib. 이것은 Matplotlib와 프롬프트를 통합하는 권장 방법입니다.

IPython/prompt_toolkit #

IPython >= 5.0에서 IPython은 CPython의 readline 기반 프롬프트 사용에서 기반 프롬프트로 변경되었습니다 prompt_toolkit. 메서드 를 통해 prompt_toolkit 입력되는 동일한 개념적 입력 후크가 있습니다. 입력 후크 의 소스는 에 있습니다.prompt_toolkitIPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook()prompt_toolkitIPython.terminal.pt_inputhooks

각주