UP
メソッドオブジェクト(3)
前のページへ
Python
次のページへ
継承と多重継承とミックスイン


クラスのふるまいと特殊属性

【 目次 】

特殊メソッドと特殊フィールド

pythonのクラスには特殊メソッド(special methods/magic methods)や特殊フィールド というものが存在し、これをオーバライトする事でクラスのふるまいをカスタマイズする事ができる。

  • データモデル — Python 2.7ja1 documentation

    特殊な名前をもったメソッドを定義することで、特殊な構文 (算術演算や添え字表記、スライス表記のような) 特定の演算をクラスで実装することができます。これは、個々のクラスが Python 言語で提供されている演算子に対応した独自の振る舞いをできるようにするための、演算子のオーバロード (operator overloading) に対する Python のアプローチです。

  • Python Tips:自作クラスの演算子のふるまいを定義したい - Life with Python

    特殊メソッドというのは、「ふるまいは自分で定義できるけれど、その呼び出しタイミングなど使われ方はあらかじめ決められているメソッド」のこと。

特殊メソッドとして一番お目にかかるのはクラスのコンストラクタとしての役割をはたす__init__。
他にも、いろいろな形式の特殊メソッド、特殊フィールドが定義されている。

__init__メソッド - コンストラクタ

コンストラクタは特殊メソッド__init__ をオーバライトして定義、このメソッドにオブジェクトの初期化処理を記述する。

class Person(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age

Personクラスのインスタンスpersonの生成は

person = Person("gudon",99)

この時、__init__メソッドが実行されてインスタンス変数にnameとageの値が代入されることになる。

print vars(person)

実行結果

{'age': 99, 'name': 'gudon'}

コンストラクタの継承は可能か?

javaではコンストラクタは継承されない。

ではpythonの場合はどうであろうか?

前述のPersonクラスを継承したPersonExクラスを定義してインスタンスを作成してインスタンス変数を表示してみる。

class PersonEx(Person):
    pass

person_ex = PersonEx("gudon",99)
print vars(person_ex)

親クラスのコンストラクタが実行されてインスタンス変数にnameとageの値が代入されているのが確認できる。

実行結果

{'age': 99, 'name': 'gudon'}

コンストラクタが継承されている。

親クラスのコンストラクタを利用する - super

Personクラスを拡張してWorkerクラスを定義してインスタンス変数jobを新たに追加してみる。

class Worker(Person):
    def __init__(self,name,age,job):
        self.name = name
        self.age = age
        self.job=job

上記のコードのnameとageの代入処理は親クラスの__init__メソッドの処理と同じであり、組込み関数superを使って親クラスの__init__メソッドが再利用できる。

class Worker(Person):
    def __init__(self,name,age,job):
        super(Worker,self).__init__(name,age)
        self.job = job

そして、この組込み関数superは多重継承のクラスの場合にも対応している。

デストラクタ__del__メソッド と with 文

デストラクタは特殊メソッド__del__を用いこのメソッドにオブジェクトの終了処理を記述する。

デストラクタは使わない方が良いそうで。

代わりに終了処理にはwithステートメントという手もあるという事か。

オブジェクトを文字列に変換 - __str__ と __repr__

オブジェクトを文字列として表現して返すメソッドとして特殊メソッド__str__と__repr__がある。
JavaのtoStringメソッドに相当するものだろう。

__str__と__repr__の違いは何だろう。

公式ドキュメントによると、

__str__は人がみて理解しやすい文字列を
__repr__はpythonがオブジェクトを再生成するために使えるようなより正確な情報を返すかそうでない場合は<>で囲ってオブジェクトの内容を正確に理解しやすいより有益な情報を返す
という事のようだ。
__str__はprint文や組込み関数strによって呼び出され、__repr__は組込み関数reprによって呼び出される。

__repr__は定義しているが __str__ が定義されていない場合、__repr__は__str__のかわりに呼び出されるようだ。
じゃ、逆に__str__ が定義されていて__repr__が定義されていない場合、__repr__のかわりに__str__がよびだされるのだろうか?

実際に確認してみた。

class MyCls1(object):
    def __str__(self):
        return "__str__string"

my_inst1=MyCls1()
print repr(my_inst1)

class MyCls2(object):
    def __repr__(self):
        return "__repr__string"

my_inst2=MyCls2()
print str(my_inst2)

実行結果

<__main__.MyCls1 object at 0x02749EF0>
__repr__string

__repr__は__str__ のかわりになるが、__str__は__repr__のかわりにならないようで、__repr__が定義されていない場合はスパークラスの__repr__が呼び出されるようだ。

reprってどういう意味だろう?
調べてみたがよく分からない。
多分、representの略だろうという結論になった。
representの意味は「〔ものが〕~を表す[示す・象徴する・意味する]」なのでなんとなくそれっぽいかな。

pythonの標準モジュールにreprというモジュールまである。

他にも__unicode__なんてのがあってunicode関数から呼び出されるようで、__unicode__が定義されていない場合は__str__を、__str__も定義されていない場合は__repr__を呼び出そうとするらしいが、python3の場合はstrがユニコードになっているので必要ないようだ。

参考までに、前述のクラスのインスタンスをunicode関数で表示してみた。

print unicode(my_inst1)
print unicode(my_inst2)

実行結果

__str__string
__repr__string

呼び出し可能オブジェクトを実装するための__call__メソッド

クラスに特殊メソッド__call__を定義すると、インスタンスを関数のように使う事ができる。 このメソッドを使って関数オブジェクトのようなふるまいをするクラスを定義できるという事なのか。

あるクラスのインスタンスxにおいて、x(arguments) とすると、 x.__call__(arguments)に置き換えられて__call__メソッドが実行される事になる。

呼び出し可能オブジェクトの例を。

class Funcs(object):
    def __init__(self, funcs):
        self.funcs = funcs

    def __call__(self,arg):
        list=[]
        for func in self.funcs:
            list.append(func(arg))
        return list

import math

funcs=Funcs((math.sin, math.cos, math.tan))
print funcs(math.pi/4)

Funcsというクラスを定義。
Funcsはコンストラクタで複数の関数のリストを受取る。
そして特殊メソッド_call__を使って関数の実行結果のリストを返す。

実行結果

[0.7071067811865475, 0.7071067811865476, 0.9999999999999999]

この例では、sin,cos,tanの3つの三角関数の演算結果をリストとして表示している。

クラスを定義した後、別の外部で定義した関数で_call__メソッドを置き換えてみる。

def new_call(self,arg):
    total=0
    for func in self.funcs:
        total+=func(arg)
    return total

Funcs.__call__=new_call
print funcs(math.pi/4)

この例では、複数の関数の実行結果の合計を返すように_call__メソッドを上書きしている。

実行結果

2.41421356237

組込み関数callableを使うと、クラスが_call__メソッドを実装しているかどうか,つまりインスタンスが関数として実行できるかどうかを調べる事ができる。

print callable(funcs)

実行結果

True

ところが、Python3ではcallable関数が廃止されている。

代わりに

hasattr(anything, '__call__')

を使うようだ。

追記 - pyhton3のcallable関数が復活

pyhton3のcallable関数は復活したようだ。

インデクシング - __getitem__
- 配列のようにふるまうオブジェクトを実装する

__getitem__メソッドを定義すると配列のようにオブジェクトをアクセスする事ができる。

C#のインデクサに相等するのかな。
以下のプログラムは配列のインデックスを指定するとその自乗を返すオブジェクトの例である。

class Square(object):
    def __getitem__(self,key):
        return key * key

square=Square()
for i in xrange(1,5):
    print i,square[i]

実行結果

1 1
2 4
3 9
4 16

もう1つサンプルを
__getitem__メソッドを使ってインスタンス変数を配列のようにアクセスする事ができる。

class MyCls(object):
    def __getitem__(self, key):
        return self.__dict__[key]
    def __setitem__(self, key, value):
        self.__dict__[key]=value

my_inst=MyCls()

# インスタンス変数に値を代入して配列として値を取り出す。
my_inst.x="a"
print my_inst["x"]
# 逆に配列に値を代入してインスタンス変数として値を取り出す。
my_inst["y"]=99
print my_inst.y

__getitem__メソッドの他に__setitem__メソッドや__delitem__メソッドなど他にもコンテナをエミュレートする特殊メソッドが用意されていて、自作のコンテナオブジェクトを実装する事が可能。
以下の例は簡易的な辞書クラスを実装した例である。
実用性は無いが自前のコンテナオブジェクトを実装するためのイメージが少しはつかめるかな?

class MyDictCls(object):
    def __init__(self):
        self._dict=dict()
    def __getitem__(self,key):
        return self._dict[key]
    def __setitem__(self,key,value):
        self._dict[key]=value
    def __delitem__(self,key):
        del self._dict[key]
    def __len__(self):
        return len(self._dict)

my_dict=MyDictCls()
my_dict["x"]="a"
my_dict["y"]="b"
my_dict["z"]="c"

for key in ("x","y","z"):
    print key," : ",my_dict[key]

実行結果

x  :  a
y  :  b
z  :  c

del文にて__delitem__メソッドが呼び出され,len関数にて__len__メソッドが呼び出される事になる。

del my_dict["x"]
print len(my_dict)

実行結果

2

イテレータ - __iter__

特殊メソッド__iter__を使ってクラスにイテレータを実装する事ができる。
これについては以下を参照。

属性の追加を禁止する - __slots__属性

__slots__を使うと指定した属性名のみが属性として使用可能になり、それ以外の属性名の使用を禁止できる。
クラスの特殊属性__slots__に、文字列、反復可能オブジェクト、あるいはインスタンスが用いる変数名を表す文字列からなるシーケンスを代入することで使用したい属性名を指定する。

class Person(object):
    __slots__=("name","age")

    def __init__(self, name, age):
        self.name=name
        self.age=age

person=Person("gudon", 99)

この時、インスタンスpersonにはname属性とage属性のみが許される。
従って、次のように、__init__メソッドで

    def __init__(self, name, age):
        self.name=name
        self.age=age
        self.sex="man"

としたり、インスタンス生成後、にsex属性を追加したりすると

person.sex="man"

実行結果

AttributeError: 'Person' object has no attribute 'sex'

のようにエラーが発生する。

__slots__を使うと、通常は自動で生成されるインスタンスの__dict__属性が生成されなくなりメモリー使用量をおさえる事ができる。

print hasattr(person,"__dict__")

実行結果

False

親クラスに既に__dict__属性が存在する場合、子クラスにおいて__slots__を指定しても子クラスの属性名の使用を禁止する事はできない。

class Person(object):
    def __init__(self, name,age):
        self.name=name
        self.age=age

class Worker(Person):
    __slots__=("name","age","job")

    def __init__(self, name, age, job):
        super(Worker,self).__init__(name,age)
        self.job=job

worker=Worker("gudon",99,"SE")

print hasattr(worker,"__dict__")
worker.sex="man"

実行結果

True

Workerクラスにはクラス変数__slots__が指定されているが、その親クラスであるPersonには__slots__変数が指定されていないのでWorkerは__dict__属性を持ち、__slots__で指定された以外の属性sexを持つことができる。

Workerクラスの__slots__の機能を有効にするには、おおもとの親クラスにも__slots__を指定する必要がある。

class Person(object):
    __slots__=("name","age")

演算子のオーバーロード

他にも、シーケンス型をエミュレーションしたり数値型をエミュレーションしたりと豊富な特殊メソッドが用意されている。 オブジェクトの演算子のオーバーロードをおこなうには演算子のオーバーロード用に定義された特殊メソッドを実装する事になる。

ページのトップへ戻る