クラスメンバーがコピーされるタイミング
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
聞いてたとおり、メタクラス?関連は、奥が深いなぁ。