1
0

More than 1 year has passed since last update.

bokeh: ColumnDataSourceは複数のドキュメントで使用できない

Posted at

環境

  • Python3.10.2
  • bokeh 2.4.2

起きたこと

ColumnDataSourceインスタンスを参照しているFigureを、複数のHTMLファイルに出力しようとすると、RuntimeError: Models must be owned by only a single document, ColumnDataSource(id='1002', ...) is already in a docというエラーが発生しました。

foo.py
from bokeh.plotting import figure, output_file, save
from bokeh.models import ColumnDataSource

source=ColumnDataSource({"x":[1,2],"y":[1,4]})
print(f"{source=}")
p1 = figure()
print(f"{p1=}")
p1.line(source=source, x="x", y="y")
output_file(filename="p1.html")
save(p1)

p2 = figure()
print(f"{p2=}")
p2.line(source=source, x="x", y="y")
output_file(filename="p2.html")
save(p2)
$ python foo.py
source=ColumnDataSource(id='1002', ...)
p1=Figure(id='1003', ...)
p2=Figure(id='1184', ...)
Traceback (most recent call last):
  File "/home/vagrant/Documents/study/python/foo.py", line 16, in <module>
    save(p2)
  File "/home/vagrant/.pyenv/versions/3.10.2/lib/python3.10/site-packages/bokeh/io/saving.py", line 98, in save
    _save_helper(obj, filename, resources, title, template, theme)
  File "/home/vagrant/.pyenv/versions/3.10.2/lib/python3.10/site-packages/bokeh/io/saving.py", line 164, in _save_helper
    html = file_html(obj, resources, title=title, template=template or FILE, theme=theme)
  File "/home/vagrant/.pyenv/versions/3.10.2/lib/python3.10/site-packages/bokeh/embed/standalone.py", line 345, in file_html
    with OutputDocumentFor(models_seq, apply_theme=theme, always_new=_always_new) as doc:
  File "/home/vagrant/.pyenv/versions/3.10.2/lib/python3.10/contextlib.py", line 135, in __enter__
    return next(self.gen)
  File "/home/vagrant/.pyenv/versions/3.10.2/lib/python3.10/site-packages/bokeh/embed/util.py", line 153, in OutputDocumentFor
    doc.add_root(model)
  File "/home/vagrant/.pyenv/versions/3.10.2/lib/python3.10/site-packages/bokeh/document/document.py", line 327, in add_root
    with self.models.freeze():
  File "/home/vagrant/.pyenv/versions/3.10.2/lib/python3.10/contextlib.py", line 142, in __exit__
    next(self.gen)
  File "/home/vagrant/.pyenv/versions/3.10.2/lib/python3.10/site-packages/bokeh/document/models.py", line 135, in freeze
    self._pop_freeze()
  File "/home/vagrant/.pyenv/versions/3.10.2/lib/python3.10/site-packages/bokeh/document/models.py", line 276, in _pop_freeze
    self.recompute()
  File "/home/vagrant/.pyenv/versions/3.10.2/lib/python3.10/site-packages/bokeh/document/models.py", line 224, in recompute
    ma._attach_document(document)
  File "/home/vagrant/.pyenv/versions/3.10.2/lib/python3.10/site-packages/bokeh/model/model.py", line 580, in _attach_document
    raise RuntimeError(f"Models must be owned by only a single document, {self!r} is already in a doc")
RuntimeError: Models must be owned by only a single document, ColumnDataSource(id='1002', ...) is already in a doc

p1.htmlは出力されましたが、p2.htmlは出力されませんでした。

原因

以下のサイトによると、ColumnDataSourceは複数のドキュメントで利用できないようです。

The problem here is that your multiple calls to components produces multiple documents, and multiple documents cannot share a ColumnDataSource.

以下のコードのように、ColumnDataSourceを1つのドキュメントからのみ参照するようにすれば、エラーは発生しません。

from bokeh.plotting import figure, output_file, save
from bokeh.models import ColumnDataSource

source1=ColumnDataSource({"x":[1,2],"y":[1,4]})
p1 = figure()
p1.line(source=source1, x="x", y="y")
output_file(filename="p1.html")
save(p1)

source2=ColumnDataSource({"x":[1,2],"y":[1,4]})
p2 = figure()
p2.line(source=source2, x="x", y="y")
output_file(filename="p2.html")
save(p2)

補足:ColumnDataSource以外でもエラーは発生する

Models must be owned by only a single documentというエラーメッセージは、ColumnDataSourceだけでなくModelでも同様のエラーが発生します。

たとえば1個のButtonインスタンスを、複数のドキュメントに出力しようとすると、RuntimeError: Models must be owned by only a single document, Button(id='1002', ...) is already in a docというエラーが発生します。

from bokeh.plotting import figure, output_file, save
from bokeh.models import Button
from bokeh.layouts import column

button = Button(label="Foo")
print(f"{button=}")

p1 = figure()
print(f"{p1=}")
output_file(filename="p1.html")
save(column([p1, button]))

p2 = figure()
print(f"{p2=}")
output_file(filename="p2.html")
save(column([p2, button]))

補足:IPythonやJupyter Notebookなどインタプリタでは、このエラーに遭遇する確率が高い

1つのColumnDataSourceインスタンスを複数のドキュメントで利用することはあまりないので、通常上記のエラーは見ることはないかと思います。
しかし、IPythonやJupyter Notebookなどのインタプリタでは、動作確認目的で1つのColumnDataSourceインスタンスを複数のドキュメントで利用することがあるので、上記のエラーに遭遇するかもしれません。

In [1]: from bokeh.plotting import figure, show
   ...: from bokeh.models import ColumnDataSource

In [2]: source=ColumnDataSource({"x":[1,2],"y":[1,4]})

In [3]: p1 = figure()
   ...: p1.line(source=source, x="x", y="y")
   ...: show(p1)

In [4]: p1 = figure()
   ...: p1.line(source=source, x="x", y="y", line_color="red")
   ...: show(p1)

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)

...

RuntimeError: Models must be owned by only a single document, UnionRenderers(id='1048', ...) is already in a doc

参考サイト

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0