2014/12/19

【Python】 Class(4) 属性を見えなくしたり挿せなくしたりする

Class(2) 属性をあとから足したりする」と対になる内容です。
(紳士的な)カプセル化とかも少し。



触らないで欲しい属性を示す


class CapsuleTest:
    a = 1
    _b = 2
    __c = 3

外からどうこうして欲しくない値、いわゆるプライベート変数を用意したいとき、PythonのClassでは変数名の頭にアンダースコアを付けます。
アンダースコアひとつ+変数名 の場合は、実はなにも起きません。
内部用途ですよ!気をつけて!」とそのクラスを使っている人にお知らせするのが目的で、使う側が気をつけようという慣習となっています。

アンダースコアふたつ+変数名 にすると、ほかの変数と同じ感じでは呼び出せなくなります。

cpslTest = CapsuleTest()

print cpslTest.a    # -> 1
print cpslTest._b    # -> 2
print cpslTest.__c

# Error: CapsuleTest instance has no attribute '__c'
# Traceback (most recent call last):
#   File "<maya console>", line 1, in <module>
# AttributeError: CapsuleTest instance has no attribute '__c' #

「__c」を呼ぼうとしたらエラーになってしまいました。
クラス内には記述しているんですが、使えないと思わせる挙動です。

実際にはどういう状態になっているかというと……

print dir(cpslTest)

['_CapsuleTest__c', '__doc__', '__module__', '_b', 'a']

「'_CapsuleTest__c'」が、もともとの __c です。実際、その属性名では呼び出せます。

print cpslTest._CapsuleTest__c    # -> 3

このように、頭にアンダースコアがふたつ付いた変数は、「_{クラス名}__変数名」と名前を変えて隠されます。
別の言い方をすると、使用不能にされたかと思いきや名前が変わっただけです。(これを「name mangling(名前分からなくするヤーツ)」と呼ぶそうです)

(いざというときのためにアクセス容易にしておくから)いざというときじゃない時は紳士的に、見てみぬフリをしておこうというアレです。

dir()から見えなくする


かように紳士協定と好奇心のバランスが試されるPythonですが、dir()関数で返って来ないようにすることも出来ます。
「いざというときにアクセス容易なように」という善意を考えれば、dirから隠すのってどうなん?という感じもありますが、継承しまくって値が氾濫してくる場合などには、整理の意味でありかと思います。

特殊関数 __dir__() を使います。

class DirTest( object ):
    a = 'z'
    b = 840
    c = ['hoshii', 'kudasai',]
 
    def __dir__(self):
        return ['a','b']

dirTest = DirTest()

print dir(dirTest)    # -> ['a', 'b']

※ちなみに新スタイルクラスじゃなくてもこれ効きます。

このように、 __dir__関数 でリターンしているリストが、そのまま dir() の結果になります。
クラスの中身とまったく関係ないリストを返すようにすると、 dir() でもそのようになりますので、それなりに真面目に使う必要があります(笑

ちなみに、上の例では dir() から見えなくしている「c」 ですが、アクセスはそのままできます。

print dirTest.c    # -> ['hoshii', 'kudasai',]


属性の追加や書き換えを制限する


以下は新スタイルクラスでないと有効にならない内容です。

クラスに無い属性でも、後から追加できるという内容を以前書きました。
これを制限する時は、 __slots__ を使います。

class SlotTest(object):
    __slots__ = ['a','b']
 
    a = 1234
    x = 'xyz'

a 、xという属性を用意しておきました。
で、__slots__ という変数にリストを代入しています。リストの中身は「a」と「b」です。

slotTest = SlotTest()

slotTest.b = 'abcd'
print slotTest.b    # -> 'abcd'

↑ b という属性を後から追加。ふつうに出来ます。

slotTest.c = 'efgh'

# Error: 'SlotTest' object has no attribute 'c'
# Traceback (most recent call last):
#   File "<maya console>", line 1, in <module>
# AttributeError: 'SlotTest' object has no attribute 'c' #

c を追加しようとすると、エラーになってしまい追加できません。
dでもfでも他の名前でも、__slots__リストの中に無い属性は受け付けてくれなくなりました。

ちなみに dir() で見てみると……

slotTest2 = SlotTest()
print dir(slotTest2)

['__class__', '__delattr__', (中略) '__subclasshook__', 'a', 'b', 'x']

b は、代入する前から、もう用意されてるていです。
もちろんこの段階でアクセスしても、エラーになりますが(▼)

slotTest2.b
# Error: b
# Traceback (most recent call last):
#   File "<maya console>", line 1, in <module>
# AttributeError: b #


また、__slots__ のリストに入れていない「x」について。
読むことは出来ますが、書き換えは出来ない『read-only』だと叱られてしまいます。

print slotTest.x
slotTest.x = 'xxyyzz'

# Error: 'SlotTest' object attribute 'x' is read-only
# Traceback (most recent call last):
#   File "<maya console>", line 1, in <module>
# AttributeError: 'SlotTest' object attribute 'x' is read-only #


__slots__ について検索してみると、主にメモリ節約的な内容が出てくるので、なるほどそういう目的で使うものなのカー!と、後から了解しました(笑
言語リファレンスを呼んでいても記憶容量についての言及が多いので、やはりその辺を手入れするために用意されたもののようです。なるほどー


属性関係もうちょい続きます。



■参考

9.6. プライベート変数 @ Python 2.7ja1 Documentation
http://docs.python.jp/2/tutorial/classes.html#tut-private

6.4. dir() 関数 @ Python 2.7ja1 Documentation
http://docs.python.jp/2/tutorial/modules.html#dir
dir([object]) @ 2.組み込み関数 - Python 2.7ja1 Documentation
http://docs.python.jp/2/library/functions.html#dir

__slots__ @ データモデル - Python 2.7ja1 Documentation
http://docs.python.jp/2/reference/datamodel.html#slots

__slots__使ってみた @ yattの日記
http://d.hatena.ne.jp/yatt/20100719/1279527122
(▲)どれくらいメモリの節約になるか検証されてます。


0 件のコメント:

コメントを投稿