作者: 機械伯爵
日時: 2009/11/19(23:39)
3-2-2.クラス定義(class definition)

3-2-2-0.クラス定義の概要

 クラス(class 種類)とは、第一義的にはオブジェクトのテンプレートですが、
同時に「クラスオブジェクト(class object)」というオブジェクトでもあります。
 クラスオブジェクトとしての振る舞いは「クラスオブジェクト」項に譲るとし
て、この「クラス定義」の項ではオブジェクト(インスタンス)を定義するもの
としてのクラスという視点を中心に話を進めます。

※クラス定義をテンプレートとして生成されたオブジェクトを、インスタンス
(instance 事例)といいます。

 クラス定義は、通常のメソッドや属性の設定の他に、継承のルール、メソッド
フックによる演算子の多重定義(overload)、あるはメタクラスプロトコル
(metaclass protocol)など様々な要素を含みます。
 それぞれについて、全て一通り触れることになりますが、この項は定義につい
ての話題なので、その他詳細については別項に譲ることにします。



3-2-2-1.基本型

 オブジェクトのテンプレートとしてのクラスの定義は、基本的に以下のように
定義されます。

	class クラス名:
		定義文1
		定義文2
		...

 クラスの定義も、関数定義と同様、インデントされたブロックの中に記述され
ます。
 定義文では、主に属性やメソッドの設定を行います。
 なお、メソッドはクラス内に定義された関数オブジェクトで、メソッド特有の
ルールはありますが属性の一種なので、属性のルールに全て従います

※注意:メソッドは「クラス内『に』定義された関数」であり、「クラス内『で』
定義された関数」だけではありません。クラス定義の外で定義された関数をメソ
ッドとして代入することも可能です。詳細は「メソッド」の項を参照してくださ
い。

 一番単純なクラス定義は、以下のようになります。

>>> class C:
...   pass
...
>>> c = C()
>>>

 cはクラスCのインスタンスで、内容的には何もありません。
 クラス名を関数のように呼び出すことにより、インスタンスが作られます。
 この関数(として呼び出されたクラス)を、以後C++言語などに倣ってコンスト
ラクタ(constructor)と呼びます。
 コンストラクタは、基本的にはインスタンスを生成し、それを初期化して返し
ます。
 初期化については、後に述べる__init__特殊メソッドによって規定できます。

※実はコンストラクタの動作をコントロールする__new__という特殊メソッドも
あるのですが、非常に複雑な動きをするものなので、Pythonのオブジェクトモデ
ルに十分慣れてからの使用をお勧めします。__new__特殊メソッドについては
「クラスオブジェクト」及び第二部の「オブジェクト指向プログラミング」で詳
しく説明します。



3-2-2-2. 属性(attribute)

 属性は、クラス及びインスタンスに属する変数です。
 クラス、インスタンスに共通する属性は、クラス定義で単純に代入することに
より、設定できます。

>>> class C:
...   x = 100
...
>>> c = C()
>>> c.x
100
>>> C.x
100
>>>

 属性はインスタンス及びクラスから、ドット演算子('.')を介して呼び出され
ます。

	オブジェクト.属性

 クラス属性に値が直接代入された場合、クラス属性を参照するインスタンス属
性も変化します。

>>> C.x = 200
>>> C.x
200
>>> c.x
200
>>>

 しかし、インスタンス属性に直接値が代入されると、その時点でインスタンス
独自の(クラス属性を参照しない)インスタンス属性が生成されます。
 これを遮蔽(shadow)あるいは上書き(override)といいます。
 遮蔽は、通常のOOP言語では上位クラスと下位クラスの間で起こりますが、イ
ンスタンスが独自の属性を持つことの出きるPythonでは、クラス−インスタンス
間でも遮蔽が起きます。

>>> C.x
200
>>> c.x
200
>>> c.x = 300
>>> C.x
200
>>> c.x
300
>>> del c.x
>>> c.x
200
>>>

 上記の通り、インスタンス属性を消去(delete)してやると、インスタンス属性
のみが消去され、再びクラス属性を参照するようになります。
 クラス属性にない属性を、インスタンス独自に設定することも可能です。

>>> class C:
...   x = 100
...
>>> c = C()
>>> c.y = 200
>>> c.y
200
>>>

 インスタンスの変更は、クラスに一切影響を及ぼしません。

※インスタンスからクラス自身を参照して、クラスの属性を変更することは可能
です。詳細は「オブジェクト」の項で説明します。

 逆にクラスの変更は、全インスタンスに影響を及ぼしますが、インスタンス独
自に設定した属性には変化を及ぼしません。

>>> class C:
...   x = 100
...
>>> c0 = C()
>>> c1 = C()
>>> c1.x = 200
>>> del C.x
>>> c0.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'x'
>>> c1.x
200
>>>

 基本的にクラスとインスタンスの、属性についての関係はシンプルで、インス
タンス独自の属性がなければクラスを参照する、というものです。
 インスタンス属性からクラスを経由して参照しても、オブジェクトの振舞は
「一つの例外を除き」全く変わりません。
 その例外とは、クラス定義内で定義された関数及び、クラス属性として代入さ
れた関数オブジェクトです。
 インスタンスがクラスを経由して関数を参照すると、関数は「メソッド」とし
て働きます。

※ここが微妙なのですが、クラス属性として関数を参照すると、普通の関数扱い
になります。

 メソッドの詳細は次項「メソッド」で詳しく解説します。
 なお「メソッド以外の属性」は「データ属性(data attribute)」と呼ばれてい
ます。



3-2-2-3.メソッド(method)

3-2-2-3-0.メソッドの概要

 メソッドの扱いはPythonのオブジェクトモデルの中でも異色の存在ですので、
若干定義の範囲を越えた解説を交えながら話を進めます(全般的な情報について
は「カスタムオブジェクト」の「インスタンス」の項を参照してください)
 メソッドは、クラス属性として設定された関数オブジェクトを「インスタンス
から呼び出した」ものです。
 メソッドの最大の特徴は、第1引数にインスタンス自身を受け取ることです
(この引数の仮引数名は慣例的に'self'となっていますが、強制ではありません)
 簡単な例を見てみましょう。

>>> class C:
...   def x(self):
...     return self
...
>>> c = C()
>>> C.x
<function x at 0xca4f8>
>>> c.x
<bound method C.x of <__main__.C object at 0xc7a50>>
>>> c.x()
<__main__.C object at 0xc7a50>
>>> c
<__main__.C object at 0xc7a50>
>>>

 c.xがC.xを参照しているにもかかわらず、片やメソッド(正確には「束縛され
たメソッド bound method」)、片や関数(function)と表示されています。
 そして、第1引数をそのまま戻すc.x()の結果は、c自体が戻されている(つま
りc自身が暗黙的に代入されている)ことが分かります。
 同じことをCから行ってみましょう。

>>> C.x(c)
<__main__.C object at 0xc7a50>
>>>

 C.xのほうは、設定通り1引数を必要とします。
 C.xの引数を省略したり、あるいはc.xに余分な引数を渡して実行すると、エラ
ーになります。
 なお、C経由で実行した場合のこの書式は単なる裏技ではなく、「継承」の項
で使用します。



3-2-2-3-1.メソッドのスコープ

 Pythonのスコープ(範囲 scope = 識別子の有効範囲)と名前空間(name space)
のルールは、Pythonの文法の中でも直感的に判りづらいところなので、別項で詳
しく説明しますが、ここではメソッド定義に関して最低限知っておかなければな
らないことを話しておきます。
 まず、関数の場合はその周囲の識別子を直接参照することができますが、メソ
ッドはクラス内やインスタンス内に設定されている識別子を直接参照することは
できません。(【】内は、あとから書き加えたコメントです)


>>> def f():
...   print(v)		【外部のvを参照】
...
>>> class C:
...   x = 100		【クラス属性にxをセット】
...   def call_x(self):
...     print(x)	【外部のxを参照】
...   def call_y(self):
...     print(y)	【外部のyを参照】
...
>>> c = C()
>>> c.y = 200		【インスタンス属性としてyをセット】
>>> v = 300		【実行環境レベルでvをセット】
>>> f()
300			【参照可能】
>>> c.call_x()		【クラス属性を参照→失敗】
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in call_x
NameError: global name 'x' is not defined
>>> c.call_y()		【インスタンス属性を参照→失敗】
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in call_y
NameError: global name 'y' is not defined
>>> x = 400		【実行環境レベルでxをセット】
>>> c.call_x()
400			【関数と同様に参照可能】
>>>

 もうお分かりだと思いますが、メソッドが第1引数にインスタンス自身を取る
のは、インスタンスへの暗示的な参照ができないからです。

※このような仕様の理由は、プログラミング言語に対する哲学的なものが多分に
含まれているのでしょうが、手間をかける最大のメリットの一つは、メソッド内
で使用される変数の由来がはっきりしたり、仮引数との名前の衝突を避けたりで
きます。もちろんそれがコストに見合うかどうかを判断するのは、それこそ設計
者の哲学でしょう。



3-2-2-3-2.その他

 引数に関するオプションなどは、関数と全く同じです(詳細は「関数」の項を
参照してください)
 また、関数リテラルで書いた関数をクラス属性として代入しても、問題なくメ
ソッドとして機能します(ただし__init__など、値を返さない特殊メソッドなど
は、関数リテラルでは定義できません)
 以下の例は、インスタンスを作った後にクラスに属性として関数オブジェクト
を登録し、インスタンス経由でメソッドとして使用しています。

>>> class C:
...   pass
...
>>> c = C()
>>> def f(self):
...   return 100
...
>>> C.f = f
>>> C.l = lambda self: 200
>>> c.f()
100
>>> c.l()
200

 もちろん、後付けのインスタンス変数も参照可能です。

>>> c.x = 500
>>> C.l2 = lambda self: self.x
>>> c.l2()
500

 ただし、「クラス経由」でないと、ただの関数になってしまう(第1引数に暗
示的にインスタンス自身を取らない)ので注意してください。

>>> c.L = lambda self:self.x
>>> c.L()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() takes exactly 1 positional argument (0 given)
>>> c.L(c)
500
>>>

 多分、C++やJavaなどのプログラミングをご存知の方は、このファジーさには
眩暈がするかもしれませんが、良きにせよ悪しきにせよ、これがPythonスタイル
です。



3-2-2-4.特殊メソッド

3-2-2-4-0.特殊メソッドの概要

 Pythonではメソッド定義の際に、ある特殊な名称を用いることにより、インス
タンスオブジェクトを初期化させたり、様々な演算子が利用できるように演算子
の多重定義(overload)を行ったり、あるいは属性への入出力を管理することが可
能です。
 特殊メソッドは必ずダブルアンダースコアに挟まれた名前を持ちます(
'__init__'など)。
 特殊メソッドとして登録されている名前と重複しなければ、前後ダブルアンダ
ースコアのメソッド及びデータ属性を設定しても構わないのですが、特殊メソッ
ド及び特殊属性オブジェクトは膨大な数がある(上に、仕様変更でよく追加され
る)ので、自分のオリジナルとして設定するデータ属性/メソッドにはそのよう
な名前を使うのは止めたほうがいいでしょう。
 この「定義」の項では、特殊メソッドのうち、定義に関係の深いメソッドのみ
を取り上げます。
 演算子の多重定義に関係するものや、特定の関数の戻り値として設定されてい
るものは、「オブジェクト」の項に譲ります。
 ただし、演算子の一種に分類される、属性参照演算子の'.'(別名:ドット演算
子)については、定義との関わりが深いので、この項目で取り上げます。



3-2-2-4-1.初期化メソッド('__init__')

 書式:__init__(self[, arg ...])
 動作:インスタンスを初期化する。引数を指定した場合は、コンストラクタ経
由で渡される。

 特殊メソッドの中で、多分一番使用頻度の多いものは、この__init__メソッド
でしょう。
 このメソッドは、インスタンスが作成された際に、デフォルトでそのインスタ
ンスを初期化します。
 例を挙げてみましょう。

>>> class C:
...   def __init__(self):
...     self.x = 10
...     self.y = 20
...
>>> c = C()
>>> c.x
10
>>> c.y
20
>>> class Cv:
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
...
>>> cv = Cv(5, 10)
>>> cv.x
5
>>> cv.y
10
>>>

 最初の例では、引数なしの初期化です。
 第1引数を用いて、__init__の中で初期化していきます。
 次の例は、引数を含めた方法です。
 変数x,yは引数でインスタンス生成時に引数として受け取ったものです。
 self.x, self.yはインスタンス属性をあらわします。
 つまり、インスタンス属性を明示的に指定することにより、おなじ変数名でも
混乱することなく設定することが出来ます。
 なお、__init__メソッドはインスタンス自身を初期化するメソッドなので、
return文を書いてはいけません(エラーになります)



3-2-2-4-2.生成メソッド('__new__')

 書式:__new__(self[, arg..])
 動作:クラスがコンストラクタとして呼び出された際の動作を定義する(クラ
スメソッド)

 __new__メソッドは、真のコンストラクタとでも言うべき特殊メソッドで、ク
ラス名を関数呼び出しされると自動的に呼び出される関数です。
 通常、このメソッドを設定しなおす必要はありません。
 インスタンスの初期化は__init__で行いますので、通常の設定は__init__メソ
ッドを使えば十分です。
 この__new__メソッドは、デザインパターンの導入など、高度なオブジェクト
指向プログラミングを行う際にのみ再設定が必要になります。
 もし、インスタンス生成過程をカスタマイズする必要があり、__new__メソッ
ドを設定しなおす必要がでてきた場合は、以下の点に注意してください。
 まず1点目、__new__メソッドで通常どおりインスタンスを生成する場合は、
それらを明示的に示す必要があります。
 __new__メソッドは、簡単に言えばクラス名を関数呼び出しした際に行う動作
を設定します。
 通常、クラス名を関数呼び出しした際にインスタンスが生成されるのですが、
__new__メソッドを設定した場合は、必要ならば設定しなければなりません。
 インスタンス生成方法については、下に例を挙げています。
 なお、生成されたインスタンスが返された場合、__init__メソッドが設定され
ていれば(その後に)実行します(なお、インスタンスを生成しなかったり、別
の値を返したりした場合は、__init__メソッドは働きません)
 2点目、__init__メソッドと異なり、__new__メソッドではインスタンスを戻
り値として設定する必要があります(でないと、インスタンスの代わりにNoneが
返されます)
 3点目、__new__メソッドはインスタンスに先立つ存在なので、第1引数はイ
ンスタンスではなく「クラス」になります(これは、後で説明する「クラスメソ
ッド」扱いであることを意味します。
 以下に、インスタンス生成数をカウントするカウンタを__new__メソッドに組
み込む例を挙げます。

【インスタンスの数を数えるカウンタを作る】

>>> class C:
...   _cnt = 0
...   def __new__(cls, *args):
...     cls._cnt += 1
...     ins = object.__new__(cls)
...     return ins
...   def __init__(self, *args):
...     self.args = args
...   def getcount(self):
...     return C._cnt
...
>>> c0 = C(1,2,3)
>>> c0.args
(1, 2, 3)
>>> c0.getcount()
1
>>> c1 = C(4,5,6)
>>> c1.args
(4, 5, 6)
>>> c1.getcount()
2
>>> c0.getcount()
2
>>>

 __new__の第1引数名がselfでなくclsになっているのはクラスであることを強
調するための慣例です。
 よってcls._cntはインスタンス属性ではなくクラス属性(クラス変数)であるこ
とに注意してください。
 これは、getcountメソッドでC._cntという形で参照されているものと同じです
(Pythonは属性に対しては必ず明示的に参照先を記します)
 __new__の後の仮引数タプルは、__init__が変数を取らないなら必要ありませ
ん。
 なお、__new__の引数の形式と__init__の引数の形式が違っていても、実際に
クラス名で引数を取って呼び出された形式にどちらでも適合するのであれば、問
題ありません。
 ins = object.__new__(cls)は、クラス(cls)からインスタンス(ins)を生成す
るための慣用句(idiom)です(詳細は、「オブジェクト指向プログラミング」の
項で説明します)
 ちなみに、object.__new__(cls)自身は__init__インスタンスを生成するだけ
で、cls.__init__は実行しませんので注意してください(クラス内で別のオブジ
ェクトを生成して返す時など)
 なお、上の例は__new__メソッドの動作を説明するためのもので、同じことは
__init__メソッドを用いても簡単にできます。

>>> class C:
...   _cnt = 0
...   def __init__(self, *args):
...     C._cnt += 1
...     self.args = args
...   def getcount(self):
...     return C._cnt
...
>>> c = C(1,2,3)
>>> c.getcount()
1
>>> c0 = C(4,5,6)
>>> c.getcount()
2
>>> c0.getcount()
2
>>>

 __new__メソッドを本当に必要とするのは、インスタンスの生成に関わる過程
にかなり大きく関与したい時に限られます。
 以下は、デザインパターンで有名なSingletonパターンを__new__を用いて実装
したものです。

※Sigletonは、クラスのインスタンスがただ一つであることを保証するパターン
です。

>>> class Singleton:
...   instance = None
...   def __new__(cls, *args):
...     if cls.instance == None:
...       ins = object.__new__(cls)
...       ins.args = args
...       cls.instance = ins
...     else:
...       ins = cls.instance
...     return ins
...
>>> s = Singleton(1,2,3)
>>> s.args
(1, 2, 3)
>>> s2 = Singleton(4,5,6)
>>> s2.args
(1, 2, 3)
>>> s2.args = 4,5,6
>>> s2.args
(4, 5, 6)
>>> s.args
(4, 5, 6)
>>>

※↑の例ではsとs2は同一のオブジェクトです

 繰り返しになりますが、__new__メソッドの活用には、Pythonの知識のみなら
ず、オブジェクト指向プログラミングの知識が必要となりますので、ご注意くだ
さい。



3-2-2-4-3.消去メソッド('__del__')

 書式:__del__(self)
 動作:オブジェクト消去時の動作を規定

 このメソッドは、いわゆるデストラクタ(destructor)に相当するもので、イン
スタンスが消去/破壊された時に実行されます。
 つまり、インスタンスが消去されるときに実行したい内容を記述しておけばよ
いのです。
 以下の例は、インスタンスに蓄えられた文字列を、インスタンス消去時にログ
に落とす操作を登録したものです。

>>> class D:
...   def __init__(self):
...     self.s = ''
...   def __del__(self):
...     f = open('log.txt','a')
...     f.write(self.s + '\n')
...     f.close()
...
>>> d1 = D()
>>> d2 = D()
>>> d1.s += 'aiueo\n'
>>> d1.s += 'kakikukeko\n'
>>> d2.s += 'sasisuseso\n'
>>> d2.s += 'tatituteto\n'
>>> del d1
>>> del d2
>>> exit()

c:\prog\python>type log.txt
aiueo
kakikukeko

sasisuseso
tatituteto

 ファイルオブジェクトを直接扱うと、ファイルオブジェクトが存在している間
ファイルハンドルを独占することになりますが、このように貯めた情報を一気に
記録すれば、ファイルハンドルを独占する時間が短くて済みます。
 その他、終了操作などが必要なもの(言語としての儀式は必要ありませんが)
については、このメソッドを活用することができます。



3-2-2-4-4.属性アクセスに関するメソッド

3-2-2-4-4-0.Pythonの属性アクセス

 基本的にPythonは属性(データ属性、メソッド問わず)直接参照したり代入し
たり消去したりすることが自由に出来ます。
 しかし、存在しない属性を参照すれば当然エラーが出ますし、メソッドが内部
で使用しているメソッドをうっかり上書きすると、内部のメカニズムが崩れてし
まう可能性もあります。
 このような場合に使用するのが、属性アクセスをフックするメソッド群です。

※フック(hook)する……鈎(hook)でひっかけて横取りする、というような意味で、
要するにある書式についてその処理を横取りするような動作をすること。Python
ではこのような属性アクセスのフックの他、演算子の適用をフックすることによ
り、演算子の多重定義などを可能にしている。

 しかし、メソッドの中ですら明示的な参照を用いてプログラミングを行う
Pythonでは、属性への制限は、それらへのアクセスの制限にもつながります。
 つまりややこしい話ですが、アクセス制限のメソッドを設定する際に内部で属
性にアクセスしようとすれば、制御がループすることになってしまいます。
 ここで紹介する__getattr__と__setattr__には、それぞれそういったループを
しないための防止策がありますが、__getattribute__という究極のアクセス制限
メソッドにはそれがありません。



3-2-2-4-4-1.属性参照メソッド('__getattr__'と'__getattribute__')

 書式:__getattr__(self, name)
 動作:存在しない属性に対する参照をフックする

 書式:__getattribute__(self, name)
 動作:ドット演算子による全ての属性参照をフックする

 属性参照をフックするメソッドは'__getattr__'と'__getattribute__'ですが、
この二つを同時に使用することはありません。
 取り扱いについては、__getatte__の方が簡単ですので、こちらを先に説明し
ましょう。
 __getattr__特殊メソッドは、インスタンスが持たない属性を参照したときの
振舞を規定します。
 たとえば、xがyという属性を持たなかった際に、x.yを参照するとエラーが生
じます。
 しかしx.__getattr__メソッドが設定されていれば、処理はこのメソッドに委
譲されます。
 変数nameには参照された属性名を文字列として受け取ります(例えば'y')の
で、それを基準に処理を分岐させることもできます。
 注意すべきは、__getattr__がフックするのは、データ属性としてだけではな
く、メソッド呼び出しの場合、例えばx.y()のようなものにも反応するというこ
とです。
 オブジェクトがどのような使われ方をするか十分予測した上でないと、
__getattr__は有効に機能できないかもしれません。
 以下は__getattr__の動作確認です。

【予想外の呼び出しにとりあえず対応する】

>>> class X:
...   def __getattr__(self, name):
...     return name	【呼び出し名をそのまま返す】
...   v = 111
...   def f(self):
...     return 999
...
>>> x = X()
>>> x.x
'x'			【データ属性としての呼び出しに対応】
>>> x.x()		【メソッドとしても呼び出されるが、対応できていない】
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable
>>> x.v
111			【存在するデータ属性はフックしない】
>>> x.f()
999			【存在するメソッドもフックしない】
>>> x.y = 666		【新規にインスタンス属性を追加】
>>> x.y
666			【追加された属性もフックしない】
>>>

 __getattribute__は__getattr__の強化版で、ドット演算子による参照一切を
フックします。
 これは、クラスの中で設定するメソッド内部にも適用されるので、メソッドを
設定する場合には要注意です(そもそも__getattribute__メソッド内で参照をす
ると、無限ループします)
 利用法としては、__getattribute__内部でインスタンスの属性参照の動作を全
て委譲してしまうことが挙げられます。

【内容を保護されたインスタンス】

>>> class X:
...   def __getattribute__(self, name):
...     if name == 'f':
...       return lambda *args: args	【f属性はメソッドとして設定】
...     elif name == 'x':
...       return 666			【x属性はデータ属性として設定】
...     else:
...       return None			【それ以外は反応なし】
...
>>> x = X()
>>> x.x
666
>>> x.f(1,2,3)
(1, 2, 3)
>>> x.k					【反応しない】
>>>
>>> x.x = 777				【x属性を上書き】
>>> x.f = None				【f属性を上書き】
>>> x.x
666					【x属性を参照していないので影響されない】
>>> x.f(5,6,7)
(5, 6, 7)				【f属性を参照していないので影響されない】
>>>

 なお、__getattribute___メソッドがフックするのはドット演算子'.'による参
照だけですので、演算子をフックする特殊メソッドなどは設定できます。
 しかし、内部でインスタンス属性を参照する際はもれなく__getattribute__を
経由することになりますので、設定には相当の注意が必要となります。



3-2-2-4-4-2.属性設定メソッド('__setattr__')

 書式:__setattr__(self, name, value)
 動作:属性への代入をフックする

 __setattr__は属性の代入をフックするメソッドです。
	x.y = v ⇒ x.__setattr__('y',v)

 __setattr__は__getattr__と異なり、既存の属性があろうが無かろうが属性が
への代入を「1つの例外を除いて」フックします。
 例外とは__dict__という属性です。
 __dict__属性は、オブジェクトの全ての属性の情報を集めた辞書のようなオブ
ジェクトで、属性名(文字列)をキーとして、オブジェクトの属性を参照したり属
性に代入したりすることができます。

	x.__dict__['y'] = v ⇒ x.y = v

 この形式は__setattr__にフックされないので、__setattr__本体や他のメソッ
ドで属性代入が必要な箇所で利用することができます。

【新規の属性には頭に'd'をつけることを強要する】

>>> class X:
...   def __setattr__(self, name, value):
...     if name[0] == 'd':	【頭文字が'd'のもののみ許す】
...       self.__dict__[name] = value
...   cx = 666			【クラス内での変数】
...   def __init__(self):
...     self.a = 111		【__init__による初期化 ルール違反】
...     self.d = 222		【__init__による初期化 ルール通り】
...     self.__dict__['v'] = 333	【__dict__による迂回】
...   def z(self):		【メソッド】
...     return 444
...
>>> x = X()
>>> x.a				【__init__内でもルール適用】
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'X' object has no attribute 'a'
>>> x.d				【ルール通り】
222
>>> x.v				【__dict__迂回OK】
333
>>> x.cx			【クラス変数はOK】
666
>>> x.z				【メソッドもOK】
<bound method X.z of <__main__.X object at 0x00B9E490>>
>>> x.z()
444
>>> x.y = 555			【ルール違反】
>>> x.y				【通過せず】
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'X' object has no attribute 'y'
>>> x.dy = 555			【ルール通り】
>>> x.dy
555
>>> x.__dict__['y'] = 777	【__dict__迂回OK】
>>> x.y
777
>>>

 __setattr__は、属性に「誤って」代入してオブジェクトのメカニズムしない
よう、防護柵を作ることなどが可能です。
 ただし、Pythonのシステムで、故意の破壊を防ぐことは至難のワザです(ほとんど出来ません)



3-2-2-4-4-3.__delattr__

 書式:__delattr__(self, name)
 動作:属性の消去をフック

 __delattr__は先の__setattr__と似たような動作をします。
 __dect__を経由したアクセス以外の「del オブジェクト.属性」という書式を
フックします。
 以下に、簡単な例として、属性の消去をブロックする方法を挙げます。

【属性消去をブロックする】

>>> class X:
...   def __delattr__(self,name):
...     print("Don't Delete!")	【消去しようとしたら警告】
...
>>> x = X()
>>> x.x = 100
>>> x.x
100
>>> del x.x
Don't Delete!			【消去しようとしたので警告】
>>> x.x
100
>>> del x.__dict__['x']		【でも__dict__から迂回すれば可能】
>>> x.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'X' object has no attribute 'x'
>>>

 用法も__setattr__と同じで、属性保護などが考えられます。