Python traits で型強制 + traitsui でカンタン GUI 作成
Python の Canopy ディストリビューションで有名な Enthought.inc が作っている traits, traitsui というモジュールが結構便利なのだが、日本語の情報がないのでメモ。
概要
traits
は Python のクラスプロパティに特定の型を強制できるモジュールtraitsui
はtraits
の定義に従って、wxPython
,PyQt
,Pyside
の GUI を簡単にデザインできるモジュール
インストール
pip install traits traitsui
この記事の例ではPyQt を使うので入ってなければ入れる (pip では入らない)。Windows なら MSIインストーラがあるので楽。他OSならソースから build するか、各パッケージ管理で。
traits
Defining Traits: Initialization and Validation — Traits 4 User Manual
ほぼそのままですが。
import traits.api # class定義の際は HasTraits を継承させる class Person(traits.api.HasTraits): # traits を使うプロパティはクラス変数とし、許可される型を traits.api から設定 # 文字列 (str) のみ許可 name = traits.api.Str # 整数のみ許可 age = traits.api.Int # 'M', 'F' もしくは 'X'のみ許可 sex = traits.api.Enum('M', 'F', 'X') # インスタンス初期化 p = Person(name='John', age=22, sex='M') # Str 型が設定されたプロパティを書き換え。str では上書きできるが、別の型を入れるとエラー p.name = 'Mike' # NG! p.name = 0 # TraitError: The 'name' trait of a Person instance must be a string, but a value of 0 <type 'int'> was specified. # Int 型が設定されたプロパティを書き換え。int では上書きられるが、別の型を入れるとエラー p.age = 24 # NG! p.age = 2.0 # TraitError: The 'age' trait of a Person instance must be an integer (int or long), but a value of 2.0 <type 'float'> was specified. # Enum 型が設定されたプロパティを書き換え。初期化の際に許可した値以外はエラー # NG! p.sex = 'B' # TraitError: The 'sex' trait of a Person instance must be 'M' or 'F' or 'X', but a value of 'B' <type 'str'> was specified.
という感じで、クラス定義の際に設定しておけば、煩雑な入力値チェックを自分で書く必要がなくなる。より複雑な条件を設定したい場合は自分でチェック関数を書くこともできる。
さらに、ある変数の変更を検知する Handler や 読み取り専用のProperty (cache可) を定義したりもできる。
class Person2(Person): # Personを継承 # name + age を表示名にしてみる disp = traits.api.Property # _xxx_changed は プロパティ xxx の変更時に自動的に実行 def _name_changed(self, value): print('updated with ' + value) # _get_xxx は 自動的にプロパティ xxx の getter になる def _get_disp(self): return '{0} ({1})'.format(self.name, self.age) # 初期化やプロパティ設定でname を変更すると Person._name_changed が実行される p = Person2(name='John', age=22, sex='M') # updated with John p.name = 'Mike' # updated with Mike # disp を読み取ると Person._get_disp が実行される p.disp # Mike (22) # disp は上書きできない p.disp = 'overwrite' # TraitError: The 'disp' trait of a Person2 instance is 'read only'.
traitsui
traits
で定義したプロパティの型に応じて、適切な GUI を表示してくれる。さきほどの Person2
クラスで .configure_traits
メソッドを実行すると、自動でレイアウトされた GUI がポップアップしてくる。各フィールドはクラスで定義した型に応じてテキスト/プルダウンとして表示される。
p.configure_traits()
traitsui
を使うと、この GUI のレイアウトを変えられる。表示方法は HasTraits
を継承したクラスの traits_view
プロパティで設定する。
表示のレイアウト変更 + ラベル付与 + 性別をラジオボタン選択 + 表示名を読み取り専用にしてみる。どういった設定ができるかは膨大なので 下記ドキュメントを参照。
Introduction to Trait Editor Factories — TraitsUI 4 User Manual
from traitsui.api import View, VGroup, HGroup, Item class Person3(Person2): # VGroupは縦方向のレイアウト # HGroupは横方向のレイアウト traits_view = View(VGroup( HGroup(Item('name', label='氏名')), HGroup(Item('age', label='年齢'), Item('sex', label='性別', style='custom')), HGroup(Item('disp',label='表示名', style='readonly')))) p = Person3(name='John', age=22, sex='M') p.configure_traits()
このGUIからユーザがプロパティを変更した場合、traits
で行った定義に従って入力値のチェックや Handler の実行なんかが自動的に行われる。たとえば Int
で定義された年齢フィールドに文字列を入力しようとすると、エラーとなり入力が許可されない。
使い方 (PyQtへの埋め込み)
traits
, traitsui
でデザインした GUI は PyQt のウィンドウ / ダイアログに組み込むことができる。ので PyQt 上で 細かい GUI のレイアウトやイベント制御の処理を書かなくてすむようになる。
import sys # おまじない import sip sip.setapi('QString', 2) sip.setapi('QVariant', 2) import PyQt4.QtCore as QtCore import PyQt4.QtGui as QtGui class AppForm(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QMainWindow.__init__(self, parent) # ウィンドウタイトル / サイズを指定 self.setWindowTitle('Person Config') self.resize(400, 200) # メインの widget, layout を作成 self.main_frame = QtGui.QWidget() self.main_layout = QtGui.QVBoxLayout() p = Person3(name='John', age=22, sex='M') # Person3 の画面 widget ( GUI のレイアウト) を取得 control = p.edit_traits(parent=self, kind='subpanel').control # layout に widget 追加 self.main_layout.addWidget(control) # メインの領域に layout 追加 self.main_frame.setLayout(self.main_layout) self.setCentralWidget(self.main_frame) if __name__ == "__main__": app = QtGui.QApplication(sys.argv) form = AppForm() form.show() sys.exit(app.exec_())
まとめ
ちょっとした GUI のついたツールが作りたいとき、traits
, traitsui
を使うとラクだし、設計もシンプルにできる。