初めてのPython(26章前編)

属性検索順制御

スーパークラスとして、ビルドインオブジェクト/objectを指定したクラスのインスタンスでは、
属性の検索が幅優先で行われる。
それだと困る(深さ優先にして欲しい)場合は、優先したい属性をクラスの中で明示的に指定する。

[kobakoba0723@fedora13-intel64 ~]$ cat new-style.py 
#!/usr/bin/env python

class A(object):
  def __init__(self):
    self.attr = 1

class B(A):
  pass

class C(A):
  def __init__(self):
    self.attr = 2

class D(B, C):
  def __init__(self):
    A.__init__(self)

class E(B, C):
  def __init__(self):
    C.__init__(self)

if __name__ == '__main__':
  x = D()
  y = E()
  
  print x.attr, y.attr

[kobakoba0723@fedora13-intel64 ~]$ python new-style.py 
1 2
[kobakoba0723@fedora13-intel64 ~]$ 

属性として変数を使ったけど、メソッドの場合も同じことようにすれば検索順を制御できる。

__slots__

クラス属性としてインスタンス属性として使いたい変数名のシーケンスを渡すと、
その変数名以外のインスタンスを作れなく出来る。

[kobakoba0723@fedora13-intel64 ~]$ python
Python 2.6.4 (r264:75706, Apr  1 2010, 02:55:51) 
[GCC 4.4.3 20100226 (Red Hat 4.4.3-8)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class A(object):
...   __slots__ = ['hoge', 'piyo']
... 
>>> x = A()
>>> x.hoge ='hoge'
>>> x.spam ='spam'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'spam'
>>> dir(x)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'hoge', 'piyo']
>>> 

ん?、piyoっていうインスタンス属性が作られている。。。

>>> x.piyo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: piyo
>>> x.piyo = 'piyo'
>>> 

しかも属性エラーが出た。変数としては存在する(?)のに、アクセスできない??
Pythonのドキュメントの__slots__の説明によると予め記憶領域だけ確保しているみたい。

__slots__を使うと、__dict__が作られないから、省メモリ化が必要なアプリとかでは重宝するみたい。
ただ単に属性を勝手に追加させたくないだけなら、__setattr__でも実装できるよなぁ、と思ってたらそういう目的だったのね。

省メモリ化はしたいけど、属性が必要になった時には追加できるようにするには、
__slots__ を”宣言する”時に __dict__を追加すれば良い。

>>> class A(object):
...   __slots__ = ['hoge', 'piyo', '__dict__']
... 
>>> x = A()
>>> dir(x)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'hoge', 'piyo']
>>> x.hoge = 'hoge'
>>> x.spam = 'spam'
>>> x.spam
'spam'
>>> dir(x)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'hoge', 'piyo', 'spam']
>>> 

わざわざ宣言するを""で囲ったのは、ドキュメントを斜め読みして、後でx.__slots__.append('__dict__')ってやって、
代入文を書いたら”AttributeError: 'A' object has no attribute 'spam'”ってエラーを出されちゃったから。
あと、append後に新しいインスタンスを作っても、__slots__にない変数名に対して代入文を書くとエラーが出る。
クラスを宣言する時にちゃんと__dict__を追加しておかないと駄目みたい。

プロパティ

ある属性に対して、アクセス/値の代入/削除/ドキュメント文字列の呼び出しが行われた時の動作を指定できる。
built-in関数のpropertyを使用して行う。property関数は、次のようにして使う。

property([fget[, fset[, fdel[, doc]]]])

Pythonのドキュメントにあるコードを持ってきて動かしたら、
アクセス/代入/削除でことごとく無限ループになってエラー終了。
無限ループになったので、__dict__を使って書き直すと今度はうまく動いた。
あとx.dataを指定してドキュメント文字列を表示しようとするとKeyErrorが出てしまった。
う〜ん、よくわかんない。とりあえずなんか動いた感じ。
なんで動かないのか判んない、だからこれで問題ないって保証も出来ない。

[kobakoba0723@fedora13-intel64 ~]$ cat property.py 
#!/usr/bin/env python

class A(object):
  def __init__(self):
    self.data = None
  
  def getdata(self):
    print 'get data',
#    return self.data
    return self.__dict__['data']
  def setdata(self, value):
    print 'set %s to data' % value
#    self.data = value
    self.__dict__['data'] = value
  def deldata(self):
    print 'delete data'
#    del self.data
    del self.__dict__['data']
  data = property(getdata, setdata, deldata, "property for data")

if __name__ == '__main__':
  x = A()
  x.data = 10
  print x.data
  del x.data
#  help(x.data)
  help(x)
  x.value = 20
  print x.value

[kobakoba0723@fedora13-intel64 ~]$ python property.py 
set None to data
set 10 to data
get data 10
delete data
Help on A in module __main__ object:

class A(__builtin__.object)
 |  Methods defined here:
 |  
 |  __init__(self)
 |  
 |  deldata(self)
 |  
 |  getdata(self)
 |  
 |  setdata(self, value)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  data
 |      property for data
(END) 
20
[kobakoba0723@fedora13-intel64 ~]$ 

参考にしたページ:
Python v2.6.2 ドキュメント データモデル
やっとむでぽん (python)__slots__ *1
Python v2.6.2 ドキュメント 組込み関数

*1:引用の中で[]を使うとちゃんと表示できないんだなぁ