自作クラスのJupyter上での出力を制御する
Jupyter notebook (lab) 上で画像などが表示されるオブジェクトの作り方を勉強したのでメモ。
やり方
_repr_**_
という関数を定義してあげることで実現できる。
現在対応している表示方法は以下。
Format REPL Notebook Qt Console _repr_pretty_
yes yes yes _repr_svg_
no yes yes _repr_png_
no yes yes _repr_jpeg_
no yes yes _repr_html_
no yes no _repr_javascript_
no yes no _repr_markdown_
no yes no _repr_latex_
no yes no _repr_mimebundle_
o ? ?
上記の関数を定義することでJupyter上での出力を制御できる。
簡単な例を下記に示す。
モジュールのインポート
import io
import sys
from dataclasses import dataclass
from typing import Union
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
from PIL.PngImagePlugin import PngImageFile
import IPython
from IPython.display import display_png, display_pretty
# display関数は v5.4およびv6.1以降は自動でインポートされるらしい。
# https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#IPython.display.display
# from IPython.display import display
import py3Dmol
from rdkit import Chem
from rdkit.Chem import Draw
バージョン
print(sys.version)
3.11.6 | packaged by conda-forge | (main, Oct 3 2023, 10:37:07) [Clang 15.0.7 ]
print(IPython.__version__)
8.24.0
SMILES: TypeAlias = str
@dataclass # 勝手にreprを綺麗にしてくれる
class Chemical:
"""化合物を表現するオブジェクト。SMILESとMolを入力にとることができる。
Parameters
----------
_chemical : Union[SMILES, Chem.Mol]
`str` or `rdkit.Chem.rdchem.Mol`
canonicalize : bool, optional
canonicalize SMILES or not, by default False
Raises
------
TypeError
文字列でもrdkit.Chem.rdchem.Molオブジェクトでもない場合にエラーを起こす。
AttributeError
molオブジェクト・smilesオブジェクトはimmutableになっているため、代入しようとするエラーを起こす。
"""
_chemical: Union[SMILES, Chem.Mol]
canonicalize: bool = False
def __post_init__(self) -> None:
if isinstance(self._chemical, str):
self.__mol = Chem.MolFromSmiles(self._chemical)
self.__smiles = (
Chem.MolToSmiles(self.__mol)
if self.canonicalize
else self._chemical
)
elif isinstance(self._chemical, Chem.Mol):
self.__mol = self._chemical
self.__smiles = Chem.MolToSmiles(self._chemical)
else:
raise TypeError("`rdkit.Chem.rdchem.Mol` or `str`")
@property
def mol(self) -> Chem.Mol:
"""rdkit.Chem.rdchem.Mol"""
return self.__mol
@mol.setter
def mol(self):
raise AttributeError
@property
def smiles(self) -> SMILES:
"""SMILES (str)"""
return self.__smiles
@smiles.setter
def smiles(self):
raise AttributeError
# 画像を表示するのに使う
def _repr_png_(self) -> bytes:
image_png: PngImageFile = Draw.MolToImage(self.mol)
# bytes型に変換しなくてはならない
# やり方参考: https://stackoverflow.com/questions/33101935/convert-pil-image-to-byte-array
with io.BytesIO() as bytes_io:
image_png.save(bytes_io, format="png")
return bytes_io.getvalue()
# HTMLを表示するのに使う
def _repr_html_(self) -> str:
# 参考: https://note.yu9824.com/howto/py3dmol-write-html/
view = py3Dmol.view(width="100%") # viewオブジェクトの生成
view.addModel(
Chem.MolToMolBlock(self.mol), "sdf", {"keepH": True}
) # viewオブジェクトにはMolBlock形式かPDB形式で分子を渡す必要があるため、`Chem.MolToMolBlock`関数でMolBlock形式に変換
view.setStyle(
{"stick": {"radius": 0.25}, "sphere": {"scale": 0.35}}
) # 分子の表示スタイルを設定
return view._make_html()
上記のように定義してあげることで画像、テキスト、HTMLの3つの出力に対応したChemical
クラスが定義できた。
# ベンゼン
chemical = Chemical("c1ccccc1")
chemical
3Dmol.js failed to load for some reason. Please check your browser console for error messages.
勝手に表示されるこの動作は display
関数が非明示的に呼び出されていると考えれば良い。
# _repr_html_が優先して呼び出された
display(chemical)
3Dmol.js failed to load for some reason. Please check your browser console for error messages.
私の環境ではHTMLが優先されるようだ。
reprの優先度は変更できる?
→ できない。
一般に、オブジェクトが表示されるときには利用可能なフォーマッタがすべて呼び出されます。どのフォーマッタを表示するかは UI が決めることです。あるフォーマッタは、他のどのフォーマッタが使えるかによって出力を変えるべきではありません。
つまり、どのreprが呼ばれるかはフロント側が決めるべき、という思想らしい。
jupyterにおける優先順位
特定のreprを呼び出したい場合は?
他のrepr
を呼び出したい場合は、明示的にそれ専用のdisplay
関数を使用するのが一番簡単そう。
# _repr_png_を明示的に呼び出す
display_png(chemical)
display_pretty(chemical)
Chemical(_chemical='c1ccccc1', canonicalize=False)
もしくは、display
関数を使って、表示したいフォーマットや表示したくないフォーマットを指定すれば良い。
display(chemical, exclude=("text/html", "image/png"))
Chemical(_chemical='c1ccccc1', canonicalize=False)
display(chemical, include=("text/plain",))
Chemical(_chemical='c1ccccc1', canonicalize=False)
include
, exclude
に指定できる一覧 (っぽいもの)
- text/plain
- text/html
- text/markdown
- text/latex
- application/json
- application/javascript
- application/pdf
- image/png
- image/jpeg
- image/svg+xml
ちなみにどの _repr_*_
も定義されていない場合は組み込みの __repr__
が呼ばれるらしい。
class NoRepr:
def __init__(self) -> None: ...
def __repr__(self) -> None:
return "call `__repr__`"
NoRepr()
call `__repr__`