Python GTK+3 チュートリアル

22. オブジェクト

翻訳して勉強するGtkチュートリアル第22章 Objects です。


GObject は、GTK+、Pango、そしてGObjectをベースにした他のライブラリの、全てのオブジェクトタイプに共通の属性とメソッドを提供する基本的なタイプです。GObject.GObject クラスは、オブジェクトの構築と破棄のためのメソッド、プロパティのアクセスメソッド、シグナル・サポートのメソッドを提供します。

ここでは、PythonでのGObjectの実装について重要な点を紹介します。


22.1. GObject.GObject からの継承

ネイティブの GObject は GObject.GObject からアクセスできます。直接インスタンス化されることはほとんどなく、一般的には継承クラスを使用します。Gtk.Widget GObject.GObject の継承クラスです。継承クラスを作って、設定ダイアログのような新しいウィジェットを作るのも面白いかもしれません。

GObject.GObject を継承するには、以下の例のように、コンストラクタで GObject.GObject.init() を呼び出す必要があります (例えばクラスが Gtk.Button を継承している場合は、Gtk.Button.init() を呼び出す必要があります)。

from gi.repository import GObject

class MyObject(GObject.GObject):

    def __init__(self):
        GObject.GObject.__init__(self)

22.2. シグナル

各シグナルは、それを放出できる型と共に型システムに登録されます。その型のユーザは、シグナル放出時に呼び出される関数を登録すると、その型のインスタンス上のシグナルに接続されると言われています。また、ユーザは自分自身でシグナルを放出したり、シグナルに接続された関数の中からシグナルの放出を停止したりすることができます。

22.2.1. シグナルの受信

既出のメインループとシグナルを参照して下さい。

22.2.2. 新しいシグナルを作成

新しいシグナルは、辞書である GObject.GObject.gsignals に追加することで作成できます。

新しいシグナルが作成された時にメソッドハンドラを定義することもできます。シグナルが放出されるたびに呼び出されます。これは do_signal_name と呼ばれています。

class MyObject(GObject.GObject):
    __gsignals__ = {
        'my_signal': (GObject.SIGNAL_RUN_FIRST, None,
                      (int,))
    }

    def do_my_signal(self, arg):
        print("method handler for 'my_signal' called with argument", arg)

GObject.SIGNAL_RUN_FIRST は、このシグナルが最初の放出段階でオブジェクトのメソッドハンドラ(ここでは do_my_signal() )を呼び出すことを示しています。代替案として、GObject.SIGNAL_RUN_LAST (3番目のエミッションステージでメソッドハンドラが呼び出される)と GObject.SIGNAL_RUN_CLEANUP (最後のエミッションステージでメソッドハンドラが呼び出される)があります。

2番目の部分の None は、シグナルのリターンタイプを示し、通常は None です。

(int,) はシグナルの引数を示し、ここではシグナルは1つの引数のみを取ります。この引数の型のリストはカンマで終わらなければなりません。

シグナルは、GObject.GObject.emit() を使って放出できます。

my_obj.emit("my_signal", 42)    # シグナル "my_signal" を引数42で出力します

22.3. プロパティ

GObject の素晴らしい機能の一つは、オブジェクトのプロパティを取得・設定するための汎用的なメカニズムです。GObject.GObject から継承された各クラスは、新しいプロパティを定義することができます。それぞれのプロパティは、決して変わることのない型を持っています (例: str, float, int….)。例えば、ボタンのテキストを含む “label” プロパティがある Gtk.Button で使用されます。

22.3.1. 既存のプロパティを使用する

クラス GObject.GObject は、既存のプロパティを管理するためのいくつかの便利な関数、GObject.GObject.get_property() GObject.GObject.set_property() を提供しています。

また、プロパティの中にはゲッターやセッターと呼ばれる専用の関数を持っているものもあります。

ボタンのプロパティ “label” については、それらを取得したり設定したりするための2つの関数、Gtk.Button.get_label() Gtk.Button.set_label() があります。

22.3.2. 新しいプロパティを作成する

プロパティには名前と型が定義されます。Python自体が動的に型付けされていたとしても、一度定義されたプロパティの型を変更することはできません。プロパティは、GObject.Property を使って作成することができます。

from gi.repository import GObject

class MyObject(GObject.GObject):

    foo = GObject.Property(type=str, default='bar')
    property_float = GObject.Property(type=float)
    def __init__(self):
        GObject.GObject.__init__(self)

プロパティは読み取り専用にすることもできます。

そのためには、プロパティの定義にいくつかのフラグを追加して、読み書きのアクセスを制御することができます。

フラグは以下の3つがあります:

  • GObject.ParamFlags.READABLE (外部コードの読み取りアクセスのみ)
  • GObject.ParamFlags.WRITABLE (書き込みアクセスのみ)
  • GObject.ParamFlags.READWRITE (パブリック)
foo = GObject.Property(type=str, flags = GObject.ParamFlags.READABLE) # 書き込み不可
bar = GObject.Property(type=str, flags = GObject.ParamFlags.WRITABLE) # 読み込み不可

また、GObject.Property で装飾された新しいメソッドを使用して、新しい読み取り専用プロパティを定義することもできます。

from gi.repository import GObject

class MyObject(GObject.GObject):

    def __init__(self):
        GObject.GObject.__init__(self)

    @GObject.Property
    def readonly(self):
        return 'This is read-only.'

このプロパティを取得するには:

my_object = MyObject()
print(my_object.readonly)
print(my_object.get_property("readonly"))

GObject.Property のAPIは組み込みの property() と似ています。Pythonの property と同様の方法で property setter を作成することができます。

class AnotherObject(GObject.Object):
    value = 0

    @GObject.Property
    def prop(self):
        'Read only property.'
        return 1

    @GObject.Property(type=int)
    def propInt(self):
        'Read-write integer property.'
        return self.value

    @propInt.setter
    def propInt(self, value):
        self.value = value

より冗長な形式を使って数値の最小と最大を定義する方法もあります。

from gi.repository import GObject

class MyObject(GObject.GObject):

    __gproperties__ = {
        "int-prop": (int, # type
                     "integer prop", # nick
                     "A property that contains an integer", # blurb
                     1, # min
                     5, # max
                     2, # default
                     GObject.ParamFlags.READWRITE # flags
                    ),
    }

    def __init__(self):
        GObject.GObject.__init__(self)
        self.int_prop = 2

    def do_get_property(self, prop):
        if prop.name == 'int-prop':
            return self.int_prop
        else:
            raise AttributeError('unknown property %s' % prop.name)

    def do_set_property(self, prop, value):
        if prop.name == 'int-prop':
            self.int_prop = value
        else:
            raise AttributeError('unknown property %s' % prop.name)

プロパティは、辞書である GObject.GObject.gproperties で定義し、do_get_property と do_set_property で処理する必要があります。

22.3.3. プロパティを見張る

プロパティが変更されると、“notify::property-name” という名前のシグナルが放出されます。

my_object = MyObject()

def on_notify_foo(obj, gparamstring):
    print("foo changed")

my_object.connect("notify::foo", on_notify_foo)

my_object.set_property("foo", "bar")    # on_notify_foo が呼び出される

22.4. API

class Gobject.Gobject
get_property(property_name)
プロパティ値を取得します。
set_property(property_name, value)
property_name を値に設定します。
emit(signal_name, ...)
signal_nameのシグナルを出力します。シグナルの引数が伴わなければいけません。例えば、シグナルの型が (int,) であれば、それは下記のように放出されなければなりません。
self.emit(signal_name, 42)
freeze_notify()
このメソッドは、thaw_notify() メソッドが呼ばれるまで、すべての "notify::" シグナル (プロパティが変更されたときに発せられる) をフリーズします。

freeze_notify() を呼び出す際に with ステートメントを使用することを推奨します。そうすることで、thaw_notify() がブロックの最後に暗黙的に呼び出されることを確実にします。
with an_object.freeze_notify():
        # ここにフリーズ通知の時にやることを書く
        ...
thaw_notify()
freeze_notify() で解凍された "notify::" シグナルをすべて解凍します。

thaw_notify() を明示的に呼び出さず、 with ステートメントでfreeze_notify() と 一緒に使うことをお勧めします。
handler_block(handler_id)
handler_unblock() が呼び出されない限り、シグナルが発生している間は呼び出されません。したがって、シグナルハンドラを「ブロックする」ということは、一時的に非アクティブにすることを意味しており、シグナルハンドラが再びアクティブになるためには、以前にブロックされた回数と全く同じ回数だけブロックを解除しなければなりません。

handler_block() を with ステートメントと一緒に使用することをお勧めします。これによりブロックの最後に暗黙のうちに handler_unblock() を呼び出すことになります。
with an_object.handler_block(handler_id):
        # ここにブロックの時にやることを書く
        ...
handler_unblock(handler_id)
handler_block() の効果を取り消します。ブロックされたハンドラはシグナルの放出中にスキップされ、以前にブロックされた回数分だけブロックが解除されるまで呼び出されません。

handler_unblock() を明示的に呼び出さず、with ステートメントと一緒に handler_block() を使用することをお勧めします。
__gsignals__
継承されたクラスが新しいシグナルを定義できる辞書。

辞書の各要素は新しいシグナルです。キーはシグナル名、値はタプルで、形式は以下の通りです。
(GObject.SIGNAL_RUN_FIRST, None, (int,))
GObject.SIGNAL_RUN_FIRST は、GObject.SIGNAL_RUN_LAST または GObject.SIGNAL_RUN_CLEANUP で置き換えることができます。None はシグナルの戻り値の型です。(int,) はシグナルのパラメータのリストで、カンマで終わる必要があります。
__gproperties__
__gproperties__ 辞書は、オブジェクトのプロパティを定義するクラスプロパティです。これは新しいプロパティを定義するのにお勧めの方法ではありません。上に書いた方法の方がずっと簡潔です。このメソッドの利点は、数値の最小値や最大値など、より多くの設定でプロパティを定義できることです。

キーはプロパティの名前です。

値はプロパティを記述するタプルです。このタプルの要素数はその最初の要素に依存しますが、タプルは常に少なくとも次の項目を含みます。

  • 最初の要素はプロパティの型(例:int, float...)です。
  • 2番目の要素は、プロパティのニックネームで、プロパティの短い説明を含む文字列です。これは一般的に、グラフィカル・ユーザー・インターフェース・ビルダーである Glade のような強力なイントロスペクション機能(オブジェクトへの質問・調査の機能)を持つプログラムで使用されます。
  • 3番目はプロパティの説明または blurb(内容紹介の短文)で、これはプロパティの説明を長くした別の文字列です。Glade などでも使用されます。
  • 最後のもの(これは後述するように必ずしも4番目ではありません)は、プロパティのフラグです。GObject.PARAM_READABLE GObject.PARAM_WRITABLE GObject.PARAM_READWRITE です。
タプルの絶対的な長さは、プロパティタイプ(タプルの最初の要素)に依存します。したがって、次のような状況になります。

  • 型が bool または str の場合、4番目の要素がプロパティの既定値となります。
  • 型が int または float の場合、4番目の要素が最小値、5番目の要素が最大値、6番目の要素がデフォルト値となります。
  • 型がこれらのいずれかでない場合は、余分な要素はありません。
GObject.SIGNAL_RUN_FIRST
最初の排出段階でオブジェクト・メソッドハンドラを呼び出します。
GObject.SIGNAL_RUN_LAST
第3の排出段階でオブジェクト・メソッドハンドラを呼び出します。
GObject.SIGNAL_RUN_CLEANUP
最後の排出段階でオブジェクト・メソッドハンドラを呼び出します。
GObject.ParamFlags.READABLE
プロパティは読み込み可能です。
GObject.ParamFlags.WRITABLE
プロパティは書き込み可能です。
GObject.ParamFlags.READWRITE
プロパティは読み書き可能です。


関連記事