試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

PyGObjectで gobject.GObjectクラスを継承してGObjectプロパティを用いる(後半)

PyGObjectで gobject.GObjectクラスを継承してGObjectプロパティを用いる(前半)」の続き。

クラスとプロパティの初期化

gobject.GObjectクラスを継承したクラスの__init__()の中では必ずメンバ関数__gobject_init__()親クラスのgobject.GObject.__init__()を呼び出す。
このキーワード引数としてプロパティの値が初期化できるが、特に、オブジェクト初期化時にのみ書き込み可なプロパティを用いる場合*1、ここでキーワード引数として渡すという形でないと書き込みが行えない。__init__()の中であっても「PyGObject/PyGTKにおけるGObjectプロパティの操作について」に書いたような通常の方法ではプロパティに値を書き込むことはできない。
(2010/3/15)__gobject_init__()は古く、gobject.GObject.__init__()へのキーワード引数で初期化するのが好ましいということが分かった。

値の変更に関する通知

プロパティの値が変更されると「notify」シグナルが発生し、gobject.GObjectクラスのメンバ関数connect()で関連付けたハンドラが呼ばれる。これにより、プロパティの変更をオブジェクトの動作に反映させることができる。
「notify::[プロパティ名]」のシグナルを用いると、プロパティごとに変更を通知できる。

self.connect('notify::[プロパティ名]', [ハンドラ名])

このシグナルに関連付けたハンドラ側が受け取る引数は(「self」を除くと)2つで、1つ目がオブジェクト自身、2つ目がdo_get_property()などが受け取るのと同じプロパティ情報(メンバnameでプロパティ名が取得可)。

Pythonでオブジェクトのプロパティを使用する」の例と同じような内容となる。
[任意]ファイル名: pygobjectproptest.py

#! /usr/bin/python
# -*- coding: utf-8 -*-

import sys
try:
  import gobject
except:
  sys.exit(1)


class TestClass(gobject.GObject):
  __foo = 12345
  # プロパティ名の登録
  # タプルの要素は
  # http://www.pygtk.org/articles/subclassing-gobject/sub-classing-gobject-in-python.htm
  # http://www.mail-archive.com/pygtk@daa.com.au/msg06355.html
  # などを参照
  __gproperties__ = \
  {
    'a' : (gobject.TYPE_INT,          # 型
           'nick name of a',          # ニックネーム
           'description of a',        # 説明
           100,                       # 受け付ける最小値
           99999,                     # 受け付ける最大値
           100,                       # 初期値(上の範囲内におさめる)
                                      # 実際の初期値でなくてもよい
                                      # (コンストラクタ引数をすぐ入れる場合など)
           gobject.PARAM_READWRITE),  # アクセス制御
    'foo' : (gobject.TYPE_INT,
             'nick name of foo',
             'description of foo',
             __foo,
             __foo,
             __foo,
             gobject.PARAM_READABLE),
  }
  def __init__(self, default_a):
    # !!! 重要 !!!
    # gobject.GObject.__init__()にキーワード引数を渡して初期化が可能
    # [プロパティ名1]=[値1], [プロパティ名2]=[値2], ...
    gobject.GObject.__init__(self, a=default_a)
    # 常に読み取り専用のプロパティはset_data()で入れておくことにする
    # set_data(),get_data()は辞書のようにキー文字列で値を出し入れする
    self.set_data('foo', self.__foo)
    # 「notify::[プロパティ名]」は値が変更されるときのシグナル
    # connect()によりハンドラを呼び出せる
    self.connect('notify::a', self.__on_notify_a)
  def __on_notify_a(self, gobject, property):
    # aの値が正常に変更されたときに呼ばれる
    print '__on_notify_a(): property.name: %s' % property.name
  # 値やアクセス権のチェックが別のところで行われるため
  # アクセサの記述はシンプルにできる
  # getterはdo_get_property(),setterはdo_set_property()の名前で用意する
  def do_get_property(self, property):
    return self.get_data(property.name)
  def do_set_property(self, property, value):
    self.set_data(property.name, value)

class TestClass2(gobject.GObject):
  __gproperties__ = \
  {
    'b' : (gobject.TYPE_STRING,
           'nick name of b',
           'description of b',
           '',
           # オブジェクト初期化時のみ書き込み可にする
           gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY)
  }
  def __init__(self, default_b):
    # !!! 重要 !!!
    # オブジェクト生成時のみ書き込み可能にするプロパティはここで
    # gobject.GObject.__init__()へのキーワード引数で初期化する形で書き込まれる
    # クラスの__init__()内であってもプロパティに書き込みをすることはできない
    gobject.GObject.__init__(self, b=default_b)
  def do_get_property(self, property):
    return self.get_data(property.name)
  def do_set_property(self, property, value):
    self.set_data(property.name, value)


print '>>> obj = TestClass(999)'
obj = TestClass(999)
print 'obj.props.a:%d\n' % obj.props.a

# 受け付けない範囲を入れても反映されない
print '>>> obj.props.a = 10'
obj.props.a = 10
print 'obj.props.a:%d\n' % obj.props.a

# 正しい範囲の値を入れると反映される
print '>>> obj.props.a = 123'
obj.props.a = 123
print 'obj.props.a:%d\n' % obj.props.a

# 読み取り専用プロパティの参照と間違った書き込み
print 'obj.props.foo:%d' % obj.props.foo
print '>>> obj.props.foo = 10000'
try:
  obj.props.foo = 10000
except TypeError, (msg):
  print 'TypeError: %s\n' % msg


# オブジェクト生成時にのみ書き込みできるプロパティの例
print '>>> obj2 = TestClass("TEST TEST TEST")'
obj2 = TestClass2("TEST TEST TEST")
print 'obj2.props.b:%s' % obj2.props.b
print '>>> obj2.props.b = "abc"'
try:
  obj2.props.b = "abc"
except TypeError, (msg):
  print 'TypeError: %s' % msg

(2010/3/15)gobject.GObject.__init__()を用いるように修正
(2010/3/26)プロパティfooの説明を修正
下は実行例。基本的な動作は同じだが、メッセージの出方は異なる。

>>> obj = TestClass(999)
obj.props.a:999

>>> obj.props.a = 10
./pygobjectproptest.py:87: Warning: value "10" of type `gint' is invalid or out of range for property `a' of type `gint'
  obj.props.a = 10
obj.props.a:999

>>> obj.props.a = 123
__on_notify_a(): property.name: a
obj.props.a:123

obj.props.foo:12345
>>> obj.props.foo = 10000
TypeError: property 'foo' is not writable

>>> obj2 = TestClass("TEST TEST TEST")
obj2.props.b:TEST TEST TEST
>>> obj2.props.b = "abc"
TypeError: property 'b' can only be set in constructor

関連記事:

参考URL:

使用したバージョン:

  • Python 2.6.4
  • PyGObject 2.20.0
  • GLib 2.22.2

*1:アクセス許可を「gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY」にした場合