以前書いてみたアクセサをpropertyを使って改良してみる。
propertyというのはnew-styleクラスに対してのみ使える、メソッドで
- class C(object):
- def __init__(self): self.__x = None
- def getx(self): return self.__x
- def setx(self, value): self.__x = value
- def delx(self): del self.__x
- x = property(getx, setx, delx, "I'm the 'x' property.")
こんな感じで使う。
Rubyを使った人なら分かると思うけど、プロパティ名でのアクセス=アクセサ呼び出しになるのは結構便利。
ActiveRecordなんかでは、特に便利だと感じる。あ、このプロパティ、もういっそエンコードされた値を返したい、と思ったときにそのクラスにプロパティ名と同じメソッドを定義するだけでいいし。
前につくったアクセサは「get_hoge()」や「set_hoge()」といった感じで呼び出されたけど、これにpropertyを使うことによってRubyと同じような属性アクセスが可能になる。
とりあえずこんな感じ。
accessor.py
- #!/usr/bin/python
- # vim: fileencoding=utf-8
- from itertools import *
- def property_accessor(cls, *names):
- _property(cls, names)
- def property_reader(cls, *names):
- _property(cls, names, True)
- def property_writer(cls, *names):
- _property(cls, names, False, True)
- def _property(cls, names, error_setter = False, error_getter = False):
- for attr_name, doc in _name_and_docs(names):
- real_name = _real_name(cls, attr_name)
- setter = _add_setter(cls, attr_name, real_name, error_setter)
- getter = _add_getter(cls, attr_name, real_name, error_getter)
- deleter = _add_deleter(cls, attr_name, real_name)
- setattr(cls, attr_name, property(getter, setter, deleter, doc))
- def _name_and_docs(name_or_tuples):
- return imap(_name_and_doc ,name_or_tuples)
- def _name_and_doc(name_or_tupple):
- if isinstance(name_or_tupple, ("".__class__, u"".__class__)) :
- return (name_or_tupple, "")
- else:
- return name_or_tupple
- def _real_name(cls, name) :
- return "_%s__%s"%(cls.__name__, name)
- def _add_method(cls, verb, name, method_to_add):
- method_name = "%s_%s" % (verb, name)
- method = cls.__dict__.get(method_name)
- if not method :
- method = method_to_add
- setattr(cls, method_name, method)
- return method
- def _add_setter(cls, name, real_name, error = False) :
- if error:
- def setter(self, v):
- raise AttributeError, "class %s: %s is a read-only attribute." % (cls.__name__, name)
- else:
- setter = lambda self, v : setattr(self, real_name, v)
- return _add_method(cls, "set", name, setter)
- def _add_getter(cls, name, real_name, error = False) :
- if error:
- def getter(self):
- raise AttributeError, "class %s: %s is a write-only attribute." % (cls.__name__, name)
- else:
- getter = lambda self : getattr(self, real_name)
- return _add_method(cls, "get", name, getter)
- def _add_deleter(cls, name, real_name) :
- deleter = lambda self : delattr(self, real_name)
- return _add_method(cls, "delete", name, deleter)
- class AccessorType(type):
- def __new__(cls, class_name, class_bases, classdict):
- cls = type.__new__(cls, class_name, class_bases, classdict)
- list = ["__accessor__", "__reader__", "__writer__"]
- methods = imap(lambda n: eval("property_%s"%n.strip("_")), list)
- for accessor_type, method in izip(list, methods):
- if classdict.has_key(accessor_type):
- method(cls, *classdict[accessor_type])
- return cls
- class Accessor(object):
- __metaclass__ = AccessorType
- def __init__(self, *args) :
- list = ["__accessor__", "__reader__", "__writer__"]
- for accessor_type in list:
- if not self.__class__.__dict__.has_key(accessor_type) : continue
- for name in self.__class__.__dict__.get(accessor_type):
- attr_name, doc = _name_and_doc(name)
- if not self.__dict__.get(attr_name):
- exec("self._%s__%s = None" % (self.__class__.__name__, attr_name))
使い方は
- #!/usr/bin/python
- # vim: fenc=utf-8
- import accessor
- class Test(accessor.Accessor) :
- __accessor__ = [("x", "This is x value"), "y", "z"]
- __reader__ = ["r"]
- def __init__(self):
- accessor.Accessor.__init__(self)
- def get_y(self) :
- return "new y value"
- obj = Test()
- obj.x = "hoge"
- print obj.x
- # => hoge
- print Test.x.__doc__
- # => This is x value
- print obj.y
- # => new y value
- obj.r = "value"
- # => AttributeError: class Test: r is a read-only attribute.
こんな感じ。
get_*とかset_*を定義すれば、独自のアクセサを定義できる。
属性は全てprivate(__*)として保存されているので、self.__xみたいな感じでクラス内ではアクセスできる。
これやっててはまったのはココ。
- exec("self._%s__%s = None" % (self.__class__.__name__, attr_name))
いやself.__dict__[attr_name] = Noneでいいんだけど、ふとexecを使うと予想外なことがおこったのであえて記事にのっけてみた。
- exec("self.__%s = None" % (attr_name))
なんとこれじゃだめなのだ!!ふつうPythonではself.__x = hogeとすると自動的に内部で self.__dict__["_classname__x"] = "hoge"に変換される。
でもexecでやるとどうやらこの過程をぶっ飛ばしてしまうらしい。要はprivateになってくれない。なので自分で変換してやることで解決する。
ところでこういうアクセサ生成メタクラスってネットにいっぱい転がってるんだけど、そろそろ標準で組み込まれたりしないんだろうかねえ。
No comments yet
trackback uriLeave a Comment