UP
新スタイルのクラスと旧スタイルのクラス
前のページへ
Python
次のページへ
オブジェクトの属性を操る(2) - 属性へのアクセスをフックする


オブジェクトの属性を操る(1) - 属性へのアクセス

pythonにはオブジェクトの属性を操作するための豊富な機能が存在する。

ここで言うオブジェクトとはクラスとクラスのインスタンスを指す。
クラスもクラスオブジェクトというオブジェクトという事。

【 目次 】

オブジェクトの属性とクラスのスコープ

  • 9.2. Python のスコープと名前空間 - 9. クラス — Python 2.7ja1 documentation

    ところで、 属性 という言葉は、ドットに続く名前すべてに対して使っています — 例えば式 z.real で、 real はオブジェクト z の属性です。厳密にいえば、モジュール内の名前に対する参照は属性の参照です。式 modname.funcname では、 modname はあるモジュールオブジェクトで、 funcname はその属性です。この場合には、モジュールの属性とモジュールの中で定義されているグローバル名の間には、直接的な対応付けがされます。これらの名前は同じ名前空間を共有しているのです!

    属性は読取り専用にも、書込み可能にもできます。書込み可能であれば、属性に代入することができます。モジュール属性は書込み可能で、 modname.the_answer = 42 と書くことができます。書込み可能な属性は、 del 文で削除することもできます。例えば、 del modname.the_answer は、 modname で指定されたオブジェクトから属性 the_answer を除去します。

関数内がローカルな名前空間を持つように、クラス内で定義された変数もクラス内のローカルな名前空間に属する。

class MyCls(object):
    def __init__(self):
        self.x="inst_var"

    y=100
    import pprint

    def method(self):
        print u"メソッドを実行"

    method(None)
    pprint.pprint(locals())

my_inst=MyCls()

実行結果

メソッドを実行
{'__init__': <function __init__ at 0x0275D270>,
 '__module__': '__main__',
 'method': <function method at 0x0275D870>,
 'pprint': <module 'pprint' from 'C:\Python27\lib\pprint.pyc'>,
 'y': 100}

クラス内で宣言したクラス変数やimportしたモジュール名はクラス内のローカルなスコープに属することになる。
そしてこれらのシンボル(変数名)はクラスの属性になる。

オブジェクトの属性辞書__dict__による属性の操作

クラスとインスタンスの情報を取得する」で述べたとおりクラスの属性はクラスの特殊属性__dict__に保存される。

print MyCls.__dict__

実行結果

{'__module__': '__main__', 'pprint': <module 'pprint' from 'C:\Python27\lib\pprint.pyc'>, '__doc__': None, '__dict__': <attribute '__dict__' of 'MyCls' objects>, 'y': 100, '__weakref__': <attribute '__weakref__' of 'MyCls' objects>, 'method': <function method at 0x0275D870>, '__init__': <function __init__ at 0x0275D270>}

インスタンスの属性もインスタンスの特殊属性__dict__に保存される。

print my_inst.__dict__

実行結果

{'x': 'inst_var'}

という事はこの特殊属性__dict__を使ってオブジェクトの属性にアクセスできる事になる。

インスタンスにドットを付けて属性名を指定する以下のようなインスタンスの属性に対する操作は、

my_inst.x="chenged_inst_var"
print my_inst.x
del my_inst.x

オブジェクトの属性辞書__dict__を使って、

my_inst.__dict__["x"]="chenged_inst_var"
print my_inst.__dict__["x"]
del my_inst.__dict__["x"]

に置き換える事ができるという事。

しかし、クラスの属性辞書に対する操作は、辞書の値の参照のみが許されるようで、クラスの属性に対する以下の操作は許されているが、

MyCls.y="chenged_inst_var"
print MyCls.y
del MyCls.y

クラスの属性辞書によるアクセスの場合には

print MyCls.__dict__["y"]

は許されるが、

MyCls.__dict__["y"]="chenged_cls_var"

del MyCls.__dict__["y"]

は許されない。

クラス定義の後で属性を操作する

pythonの面白いところは、クラスで定義されていない属性を後で勝手に追加したり,定義されている属性を削除できる事。

class MyCls(object):
    x="cls_var"

    def __init__(self):
        self.a="inst_var"
my_inst=MyCls()

MyCls.y="add_cls_var"
my_inst.b="add_inst_var"

print vars(MyCls)
print vars(my_inst)

実行結果

{'__module__': '__main__', 'y': 'add_cls_var', '__dict__': <attribute '__dict__' of 'MyCls' objects>, 'x': 'cls_var', '__weakref__': <attribute '__weakref__' of 'MyCls' objects>, '__doc__': None, '__init__': <function __init__ at 0x0268D170>}
{'a': 'inst_var', 'b': 'add_inst_var'}

後で追加されたクラス変数yがクラスの属性に,インスタンス変数bがインスタンス属性に追加されているのがわかる。

クラスの属性xとインスタンスの属性aを削除。

del MyCls.x
del my_inst.a

print vars(MyCls)
print vars(my_inst)

実行結果

{'__module__': '__main__', 'y': 'add_cls_var', '__dict__': <attribute '__dict__' of 'MyCls' objects>, '__weakref__': <attribute '__weakref__' of 'MyCls' objects>, '__doc__': None, '__init__': <function __init__ at 0x02621170>}
{'b': 'add_inst_var'}

クラス属性とインスタンス属性のそれぞれから指定した変数が削除されているのがわかる。

空のクラスの意外な使い道。

pythonのクラスやインスタンスの属性(メソッドも含む)は自由に追加や削除が可能なので、空のクラスを使ってC言語の構造体のような使い方ができる。

以下の例は関数の引数や戻り値をオブジェクトに詰め込んで利用する例である。

class EmptyCls(object):
    pass
obj=EmptyCls()

obj.arg1=u"愚鈍人"
obj.arg2=99

def func(arg_obj):
    # arg1,arg2にを使って何かの処理をおこない結果をreurn_valueに詰め込んで返す。
    arg_obj.reurn_value=u"何かの処理の結果"

func(obj)
print obj.reurn_value

組込み関数を使って文字列変数でオブジェクトの属性を操作する

組込み関数setattr, getattr, hasattr, delattrを使ってオブジェクトの属性を操作することができる。

次の例では、インスタンスにドットを付けて属性にアクセスしている。

class MyCls(object):
    pass

my_inst=MyCls()

my_inst.x="x_value"
print my_inst.x

この例の後の2行のコードは、組込み関数を使って以下のように置き換える事ができる。

setattr(my_inst, "x", "x_value")
print getattr(my_inst, "x")

属性の削除についても、名前にドットを付けて属性にアクセスする以下のコードは

del my_inst.x

組込み関数を使って

delattr(my_inst, "x")

のように置き換える事ができる。

インスタンスだけでなくクラスの属性に対しても、以下のように組込み関数を使ってアクセスする事ができる。

# MyCs.x="class_x_value"
setattr(MyCls,"x", "class_x_value")
print MyCls.x

実行結果

class_x_value

組込み関数とオブジェクトにドットを付けて属性にアクセスするコードとの違いは、組込み関数を使った場合は属性名に文字列変数を使ってアクセスする事ができる点にある。

getattr関数を使って3人の子供の名前を列挙する例を以下に示す。

#coding: UTF-8

class Parent:
    def __init__(self):
        self.child1 = u"太郎"
        self.child2 = u"次郎"
        self.child3 = u"三郎"

parent = Parent()
for i in xrange(1,4):
    print i,u"番目の子供は", getattr(parent,"child" + str(i))

実行結果

1 番目の子供は 太郎
2 番目の子供は 次郎
3 番目の子供は 三郎

上記のコードでは、属性名を示す文字列をfor文の中で合成してインスタンスの属性にアクセスしているが、このような方法はオブジェクトにドットを付けてアクセスする方法では実現できないだろう。

インスタンス属性にアクセスする3つの方法。

これまで述べてきたことをまとめるとインスタンス属性にアクセスするには3つの記述方法がある事になる。

1つ目はドットを付けて属性名を指定する。

my_insy.x

2つ目は組込み関数getattr,setattr,delattr,hasattrを使う

getattr(my_inst,"x"),setattr(my_inst,"x","x_value"),delattr(my_inst,"x"),hasattr(my_inst,"x")

3つ目はインスタンスの属性辞書に直接アクセスする。

my_insy.__dict__["x"]

1つ目と2つ目は、属性名を使ってアクセスするか属性名を示す文字列でアクセスするかの違いがあるが同等の動作をする。
しかし、3つ目の__dict__を使った属性辞書への直接アクセスの場合は、前述のようにクラスの属性に対しては参照のみが許される。
そして__setattr__メソッド__delattr__メソッドディスクリプタ において微妙な動作の違いが出てくる。
それについては今後の記事で述べる事にする。

ページのトップへ戻る