クラスメンバーがコピーされるタイミング

榛名山麓のうどん屋さん。うまかった!

2010/2/17追記
2年放置していた はてなdiaryをみてたら、ひどいのがあったので追記。。(汗
pythonでは、コンストラクタ内ではなく、method定義の外側にメンバー定義をすると、インスタンスではなくて、クラスのメンバーになるので、下記では駄目ですねぇ。
ホントは、メンバー宣言をコンストラクタ内に書くのは、実はまだちょっと慣れません。。とはいえ、最近はほとんどpythonでコード書いてなかったり。。


メンバーを保護する感じのクラスを作ってみました。
privateっぽくメンバー(name, job)を隠蔽して、jobのみ編集可能、name, job以外のメンバー追加は不可にするというものです。
DBを隠蔽して、変更すべきでないカラムのデータを変更できないようにして、カプセル化しようと思ったのがきっかけ。

# -*- coding: utf-8 -*-

class Foo(object):

    __attr = dict(name=None, job=None)

    def __init__(self, name=None):
        if (name is not None): self.name = name

    def __getattribute__(self, name):
        try:
            if (name == '_Foo__attr'):
                return object.__getattribute__(self,
                                               '_Foo__attr')

            _tmp = object.__getattribute__(self,
                                           '_Foo__attr')
            if (_tmp.has_key(name)):
                return _tmp[name]
        except:
            raise

    def __setattr__(self, name, value):
        try:
            if (name == 'name' and self.name is not None):
                raise "cannot change 'name'"

            if (self.__attr.has_key(name)):
                self.__attr[name] = value
            else:
                raise ("no attribute [%s]" % name)
        except:
            raise

で、一見うまく動くのですが、実際には、name, jobは、classオブジェクトのメンバーを変更してしまっているようです。


foo = Foo('tom')
print foo.name, foo.job
foo.job = 'cat'
print foo.name, foo.job

foo.hoge = 'fuga'  # exception (計画通り)
foo.name = 'jerry' # exception (計画通り)

bar = Foo('jerry')  # exception (なぜ??)

buzz = Foo()
print buzz.name, buzz.job # tom cat となる!

__setattr__()で、selfのメンバーを変更するようにしている(つもり)なのですが、何らかの変更による(裏側での)メンバーコピーの前に直接__setattr__()を呼び出すと、コピーされずに直接classオブジェクトのメンバーを変更するみたい。ううむ。

結局、__init__()で最初に、

import copy
_tmp = copy.copy(object.__getattribute__(self, _Foo__attr))
object.__setattr__(self, _Foo__attr, _tmp)

みたいな処理を入れました。

折角なので、この辺を全部abstractなクラスにしようとチャレンジしてみたのはこちら。
もちっといい方法ないのかな。

エラー処理その他はちゃんと見てません。abstract()は、本家サイトのTipsから拝借。

# -*- coding: utf-8 -*-

def abstract():
    import inspect
    caller = inspect.getouterframes(inspect.currentframe())[1][3]
    raise NotImplementedError(caller + ' must be implemented in subclass')

class AbstractManager(object):  # abstruct class
    """
    Object Manager

    Databese wrapp class
    """
    __version__ = 1.0

    # private members.

    __attributes = {}

    # constractor / destractor
    def __init__(self):
        """
        create Some Object

        copy "__attributes" to local instance.
        """
        # need to set copy:
        #  because, fooking __setattr__, __getattribute__
        import copy
        _name = object.__getattribute__(self, '__name__')
        if _name is None:
            raise AttributeError("Need to set __name__")
        _name = "_%s__attributes" % _name
        _tmp = copy.copy(object.__getattribute__(self, _name))
        object.__setattr__(self, _name, _tmp)

    # interfaces
    def save(self): abstract()

    def delete(self): abstract()

    # private functions
    def __getattribute__(self, name):
        try:
            _name = object.__getattribute__(self, '__name__')
            if _name is None:
                raise AttributeError("Need to set __name__")
            _name = "_%s__attributes" % _name

            if (name == '_AbstractManager__attributes'):
                return object.__getattribute__(self, _name)

            _tmp = object.__getattribute__(self, _name)
            if (_tmp.has_key(name)):
                return _tmp[name]
        except:
            raise AttributeError("Need to set __name__ of the Class.")

        return object.__getattribute__(self, name)

    def __setattr__(self, name, value):
        try:
            if (self.__attributes.has_key(name)):
                self.__attributes[name] = value
            else:
                raise KeyError('No attribute named "%s".' % name)
        except:
            raise

############################################################

if __name__ == "__main__":

    #### test class
    class testClass(AbstractManager):

        __name__     = 'testClass'
        __version__  = 1.0

        __attributes = dict(id_item = 0,
                            url     = None)

        def __init__(self, id_item):
            AbstractManager.__init__(self)
            self.id_item = id_item

        def __getattribute__(self, name):
            return AbstractManager.__getattribute__(self, name)

        def __setattr__(self, name, value):
            # specific codes.
            if (name == 'id_item' and self.id_item > 0):
                raise ItemsObjectError('Changing "id_item" is not permmitted.')

            return AbstractManager.__setattr__(self, name, value)


    #### test codes

    x = testClass(id_item = 21)
    print "id  [%s]" % x.id_item
    print "url [%s]" % x.url

    x.url = 'http://example.com/'
    print "after url [%s]" % x.url

    # throw exception
    try:
        x.id_item = 55
        print "set 55 [%d]" % x.id_item
        print "Error: need to raise exception here."
    except:
        pass


聞いてたとおり、メタクラス?関連は、奥が深いなぁ。