我有一个 Altair 图表,希望通过以下方式使其具有交互性。当我单击数据点时,我希望通过其 CLI 启动应用程序,并将数据点的属性作为启动命令的参数提供。我的理解是,使用 Altair 5.3.0 中引入的新
JupyterChart
类应该可以实现这一点 - 请参阅示例
此处
但是,按照该示例,当我尝试通过在观察者回调中调用
subprocess.Popen
来运行我的应用程序时,没有任何反应。我做错了什么?
这是一个 MWE:
import altair as alt
import pandas as pd
import subprocess
def on_click(change):
sel = change.new.value[0]
x_sel = df.iloc[sel]["x"]
p = subprocess.Popen(["echo", f"'{x_sel}'"])
df = pd.DataFrame({"x": [1,2], "y": [1,1]})
brush = alt.selection_point("brush")
jchart = alt.JupyterChart(
alt.Chart(df).mark_point(filled=True, size=100, stroke="black").encode(
x=alt.X("x:Q").scale(domain=[0,3]),
y=alt.Y("y:Q").scale(domain=[0,2]),
color=alt.Color("x:Q").legend(None)
)
)
jchart.selections.observe(on_click, ["brush"])
jchart
这是我的环境信息:
- python 3.11.9
- altair 5.3.0
- ipykernel 6.25.0 (required by VS Code)
- jupyter 1.0.0
- notebook 6.5.6
我尝试在 VS Code (1.91.1) 和 jupyter 笔记本中运行 MWE。在 VS Code 中,我的编辑器窗口冻结了:我无法将焦点切换到终端,并且无法在不立即丢失上下文菜单的情况下右键单击任何内容。在 Jupyter Notebook 中,我的窗口没有冻结,并且我没有在界面或控制台中看到任何错误消息,但我也没有看到任何输出。在这两种情况下,我希望看到 Popen 调用的结果(即“1”或“2”)打印在单元格输出中。
问题在于尝试在 Jupyter 的主线程中运行
subprocess.Popen
。Jupyter 本质上是单线程的,这意味着它一次只能做一件事。当调用
subprocess.Popen
时,它会阻止该线程,直到子进程完成,从而导致 Jupyter 冻结。
为了解决这个问题,需要在一个单独的线程或进程中运行的子进程。这里有几种方法可以做到这一点:
1. 使用
ipywidgets.Output
:
这种方法使用
ipywidgets.Output
小部件将子进程的输出捕获到一个单独的输出区域,这样就不会阻塞 Jupyter 的主线程。
import altair as alt
import pandas as pd
import subprocess
from ipywidgets import Output
from IPython.display import display
def on_click(change):
sel = change.new.value[0]
x_sel = df.iloc[sel]["x"]
# 使用 Output 小部件捕获子进程的输出
out = Output()
display(out)
with out:
p = subprocess.Popen(["echo", f"'{x_sel}'"], stdout=subprocess.PIPE)
output, _ = p.communicate()
print(output.decode())
df = pd.DataFrame({"x": [1,2], "y": [1,1]})
brush = alt.selection_point("brush")
jchart = alt.JupyterChart(
alt.Chart(df).mark_point(filled=True, size=100, stroke="black").encode(
x=alt.X("x:Q").scale(domain=[0,3]),
y=alt.Y("y:Q").scale(domain=[0,2]),
color=alt.Color("x:Q").legend(None)
)
)
jchart.selections.observe(on_click, ["brush"])
jchart
2. 使用线程:
这种方法使用 Python 的
threading
模块在一个单独的线程中运行子进程。
import altair as alt
import pandas as pd
import subprocess
import threading
def run_subprocess(x_sel):
subprocess.Popen(["echo", f"'{x_sel}'"])
def on_click(change):
sel = change.new.value[0]
x_sel = df.iloc[sel]["x"]
# 在一个单独的线程中运行子进程
thread = threading.Thread(target=run_subprocess, args=(x_sel,))
thread.start()
# ... rest of the code ...
这两种方法都可以解决 Jupyter 冻结的问题,并允许在单击数据点时运行的应用程序。选择哪种方法取决于的具体需求和偏好。
标签:python,subprocess,altair From: 78818074