Python:続・アクセサの生成

以前書いてみたアクセサをpropertyを使って改良してみる。

propertyというのはnew-styleクラスに対してのみ使える、メソッドで

python code
  1. class C(object):
  2.     def __init__(self): self.__x = None
  3.     def getx(self): return self.__x
  4.     def setx(self, value): self.__x = value
  5.     def delx(self): del self.__x
  6.     x = property(getx, setx, delx, "I'm the 'x' property.")
  7.  

こんな感じで使う。

Rubyを使った人なら分かると思うけど、プロパティ名でのアクセス=アクセサ呼び出しになるのは結構便利。
ActiveRecordなんかでは、特に便利だと感じる。あ、このプロパティ、もういっそエンコードされた値を返したい、と思ったときにそのクラスにプロパティ名と同じメソッドを定義するだけでいいし。

前につくったアクセサは「get_hoge()」や「set_hoge()」といった感じで呼び出されたけど、これにpropertyを使うことによってRubyと同じような属性アクセスが可能になる。

とりあえずこんな感じ。

accessor.py

python code
  1. #!/usr/bin/python
  2. # vim: fileencoding=utf-8
  3. from itertools import *
  4.  
  5. def property_accessor(cls, *names):
  6.   _property(cls, names)
  7.  
  8. def property_reader(cls, *names):
  9.   _property(cls, names, True)
  10.  
  11. def property_writer(cls, *names):
  12.   _property(cls, names, False, True)
  13.  
  14. def _property(cls, names, error_setter = False, error_getter = False):
  15.   for attr_name, doc in _name_and_docs(names):
  16.     real_name = _real_name(cls, attr_name)
  17.     setter = _add_setter(cls, attr_name, real_name, error_setter)
  18.     getter = _add_getter(cls, attr_name, real_name, error_getter)
  19.     deleter = _add_deleter(cls, attr_name, real_name)
  20.     setattr(cls, attr_name, property(getter, setter, deleter, doc))
  21.  
  22. def _name_and_docs(name_or_tuples):
  23.   return imap(_name_and_doc ,name_or_tuples)
  24.  
  25. def _name_and_doc(name_or_tupple):
  26.   if isinstance(name_or_tupple, ("".__class__, u"".__class__)) :
  27.     return (name_or_tupple, "")
  28.   else:
  29.     return name_or_tupple
  30.  
  31. def _real_name(cls, name) :
  32.   return "_%s__%s"%(cls.__name__, name)
  33.  
  34. def _add_method(cls, verb, name, method_to_add):
  35.   method_name = "%s_%s" % (verb, name)
  36.   method = cls.__dict__.get(method_name)
  37.   if not method :
  38.     method = method_to_add
  39.     setattr(cls, method_name, method)
  40.   return method
  41.  
  42. def _add_setter(cls, name, real_name, error = False) :
  43.   if error:
  44.     def setter(self, v):
  45.       raise AttributeError, "class %s: %s is a read-only attribute." % (cls.__name__, name)
  46.   else:
  47.     setter = lambda self, v : setattr(self, real_name, v)
  48.   return _add_method(cls, "set", name, setter)
  49.  
  50. def _add_getter(cls, name, real_name, error = False) :
  51.   if error:
  52.     def getter(self):
  53.       raise AttributeError, "class %s: %s is a write-only attribute." % (cls.__name__, name)
  54.   else:
  55.     getter = lambda self : getattr(self, real_name)
  56.   return _add_method(cls, "get", name, getter)
  57.  
  58. def _add_deleter(cls, name, real_name) :
  59.   deleter = lambda self : delattr(self, real_name)
  60.   return _add_method(cls, "delete", name, deleter)
  61.  
  62. class AccessorType(type):
  63.   def __new__(cls, class_name, class_bases, classdict):
  64.     cls = type.__new__(cls, class_name, class_bases, classdict)
  65.     list = ["__accessor__", "__reader__", "__writer__"]
  66.     methods = imap(lambda n: eval("property_%s"%n.strip("_")), list)
  67.     for accessor_type, method in izip(list, methods):
  68.         if classdict.has_key(accessor_type):
  69.           method(cls, *classdict[accessor_type])
  70.     return cls
  71.  
  72. class Accessor(object):
  73.   __metaclass__ = AccessorType
  74.   def __init__(self, *args) :
  75.     list = ["__accessor__", "__reader__", "__writer__"]
  76.     for accessor_type in list:
  77.       if not self.__class__.__dict__.has_key(accessor_type) : continue
  78.       for name in self.__class__.__dict__.get(accessor_type):
  79.         attr_name, doc = _name_and_doc(name)
  80.         if not self.__dict__.get(attr_name):
  81.           exec("self._%s__%s = None" % (self.__class__.__name__, attr_name))
  82.  

使い方は

python code
  1. #!/usr/bin/python
  2. # vim: fenc=utf-8
  3. import accessor
  4.  
  5. class Test(accessor.Accessor) :
  6.   __accessor__ = [("x", "This is x value"), "y", "z"]
  7.   __reader__ = ["r"]
  8.  
  9.   def __init__(self):
  10.     accessor.Accessor.__init__(self)
  11.  
  12.   def get_y(self) :
  13.     return "new y value"
  14.  
  15. obj = Test()
  16. obj.x = "hoge"
  17.  
  18. print obj.x
  19. # => hoge
  20.  
  21. print Test.x.__doc__
  22. # => This is x value
  23.  
  24. print obj.y
  25. # => new y value
  26.  
  27. obj.r = "value"
  28. # => AttributeError: class Test: r is a read-only attribute.
  29.  

こんな感じ。

get_*とかset_*を定義すれば、独自のアクセサを定義できる。
属性は全てprivate(__*)として保存されているので、self.__xみたいな感じでクラス内ではアクセスできる。

これやっててはまったのはココ。

python code
  1. exec("self._%s__%s = None" % (self.__class__.__name__, attr_name))
  2.  

いやself.__dict__[attr_name] = Noneでいいんだけど、ふとexecを使うと予想外なことがおこったのであえて記事にのっけてみた。

python code
  1. exec("self.__%s = None" % (attr_name))
  2.  

なんとこれじゃだめなのだ!!ふつうPythonではself.__x = hogeとすると自動的に内部で self.__dict__["_classname__x"] = "hoge"に変換される。

でもexecでやるとどうやらこの過程をぶっ飛ばしてしまうらしい。要はprivateになってくれない。なので自分で変換してやることで解決する。

ところでこういうアクセサ生成メタクラスってネットにいっぱい転がってるんだけど、そろそろ標準で組み込まれたりしないんだろうかねえ。

Related posts:

07.27.08/12am

No comments yet

trackback uri
  • ajax-loading
  • ajax-loading
  • ajax-loading

Leave a Comment

You can use these tags: <code>, <i>, <em>, <strong>, <a>

About

Author:yuin(http://inforno.net/)

文学部文化学科卒という生粋の文系趣味プログラマ。

主にRuby、Javascript、PHP、JAVA,Python,C,Scala,Schemeなどを使っています。今はPythonな感じかもしれない。今後作曲活動なども復活するかもしれない。

Pages