現在地: ホーム Dive Into Python 3

難易度: ♦♦♦♦♦

特殊メソッド名

私の十八番は、他の誰もが間違っているときに、自分だけ正しい所にいるってことだね。
ジョージ・バーナード・ショー

 

飛び込む

この本の全体を通して、いくつかの「特殊メソッド」(特定の構文を使うときにPythonが呼び出す「魔法」のメソッド)の例を見てきた。特殊メソッドを使うことで、自作のクラスをシーケンスのように振る舞せたり、関数のように振る舞せたり、イテレータのように振る舞せたりすることができる。なんと数値として振る舞わせることさえできるのだ。このAppendixは、すでに目にした特殊メソッドのレファレンスを提供するとともに、より奥義的な特殊メソッドの一部を手短に紹介する。

基礎

クラスの紹介を読み終えているならば、最も有名な特殊メソッドである__init__()をすでに目にしているはずだ。私が書くクラスの大半は、結局なんらかの初期化を必要とすることになる。基礎的な特殊メソッドは他にもいくつか存在し、それらは自作のクラスをデバッグするときに特に便利だ。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
インスタンスを初期化する x = MyClass() x.__init__()
文字列としての「正式な」表現 repr(x) x.__repr__()
文字列としての「インフォーマルな」値 str(x) x.__str__()
バイト列としての「インフォーマルな」値 bytes(x) x.__bytes__()
フォーマットされた文字列としての値 format(x, format_spec) x.__format__(format_spec)
  1. __init__()メソッドは、インスタンスが作成された後に呼び出される。もし実際の生成プロセスを制御したい場合は__new__()メソッドを使用する。
  2. 慣例により、__repr__()メソッドは、有効なPythonの式を表す文字列を返すべきだ。
  3. __str__()メソッドは、print(x)するときにも呼び出される。
  4. bytes型の導入にともない、Python 3で新たに加わった
  5. 慣例により、format_specFormat Specification Mini-Languageに従うべきである。Python標準ライブラリのdecimal.pyは、独自の__format__()メソッドを持っている。

イテレータのように振る舞うクラス

イテレータの章では、__iter__()メソッドと__next__()メソッドを使ってイテレータをゼロから作り上げる方法を見た。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
シーケンスをイテレートする iter(seq) seq.__iter__()
イテレータから次の値を取得する next(seq) seq.__next__()
逆順のイテレータを作る reversed(seq) seq.__reversed__()
  1. __iter__()メソッドは、新しいイテレータを作るときに呼び出される。このメソッドは、イテレータの初期値を設定するのに適した場所だ。
  2. __next__()メソッドは、イテレータから次の値を取得するときに呼び出される。
  3. __reversed__()メソッドはあまり使われない。これは、既存のシーケンスを受け取って、そのシーケンスの要素を逆順に(末尾から先頭へ)生み出すイテレータを返す。

イテレータの章で見たように、forループはイテレータに従って役目を果せる。以下のループでは:

for x in seq:
    print(x)

Python 3は、seq.__iter__()を呼び出してイテレータを作り、次にイテレータの__next__()メソッドを呼び出して各々のxの値を取得する。__next__()メソッドがStopIteration例外を送出すると、forループは静かに終了する。

算出属性

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
算出属性を取得する(無条件) x.my_property x.__getattribute__('my_property')
算出属性を取得する(フォールバック) x.my_property x.__getattr__('my_property')
属性を設定する x.my_property = value x.__setattr__('my_property', value)
属性を削除する del x.my_property x.__delattr__('my_property')
全ての属性とメソッドをリストアップする dir(x) x.__dir__()
  1. クラスが__getattribute__()メソッドを定義している場合は、すべての属性名/メソッド名に対するすべての参照においてPythonがこのメソッドを呼び出す(ただし特殊メソッド名は、不快な無限ループを引き起こすので除外される)。
  2. クラスが__getattr__()メソッドを定義している場合は、通常通りのすべての場所から属性を探したあとに限って、Pythonがこのメソッドを呼び出す。インスタンスxが属性colorを定義している場合には、x.colorx.__getattr__('color')を呼び出さない。これは、すでに定義されたx.colorの値を返すだけだ。
  3. __setattr__()メソッドは、属性に値を代入しようとするときに呼び出される。
  4. __delattr__()メソッドは、属性を削除しようとするときに呼び出される。
  5. __dir__()メソッドは、__getattr__()メソッドや__getattribute__()メソッドを定義するときに便利だ。通常、dir(x)を呼び出すと、標準の属性とメソッドだけがリストアップされる。つまり、__getattr()__メソッドがcolor属性を動的に提供する場合は、dir(x)colorを利用可能な属性としてリストアップしてくれないのだ。__dir__()をオーバーライドすることによって、color属性が利用可能な属性としてリストアップされるようできる。そうしておけば、あなたのクラスを使いたいと思う人にとって助けになるだろう。さもないと、コードの内部を掘り起こさないといけなくなるかもしれない。

__getattr__()メソッドと__getattribute__()メソッドの違いはわずかだが重要だ。これは2つの例で説明できる:

class Dynamo:
    def __getattr__(self, key):
        if key == 'color':         
            return 'PapayaWhip'
        else:
            raise AttributeError   

>>> dyn = Dynamo()
>>> dyn.color                      
'PapayaWhip'
>>> dyn.color = 'LemonChiffon'
>>> dyn.color                      
'LemonChiffon'
  1. 属性名は、__getattr()__メソッドに文字列として渡される。その名前が'color'であれば、このメソッドは値を返す(この例ではハードコードされた文字列を返しているだけだが、実際には、いくつかの計算などを行なって、その結果を返すのがふつうだ)。
  2. 不明な属性名が与えられた場合、__getattr()__メソッドはAttributeError例外を発生させる必要がある。さもなければ、未定義の属性にアクセスしたときに、コードは密やかに誤動作を始めるだろう(詳しく言うと、このメソッドは、例外を送出しなかったり明示的に値を返さない場合にはPythonの無効値であるNoneを返すのだ。これは、明示的に定義されていないすべての属性値がNoneになることを意味するが、ほぼ確実に、それは望ましい振る舞いではない)。
  3. インスタンスdyncolorという名前の属性を持っていないので、__getattr__()メソッドが呼び出されて、算出された値が提供される。
  4. dyn.colorを明示的に設定した後は、もはや__getattr__()メソッドはdyn.colorを提供するために呼び出されない。インスタンス上にすでにdyn.colorが定義されているからだ。

その一方で、__getattribute__()メソッドは絶対的なものであり、無条件で使われる。

class SuperDynamo:
    def __getattribute__(self, key):
        if key == 'color':
            return 'PapayaWhip'
        else:
            raise AttributeError

>>> dyn = SuperDynamo()
>>> dyn.color                      
'PapayaWhip'
>>> dyn.color = 'LemonChiffon'
>>> dyn.color                      
'PapayaWhip'
  1. dyn.colorの値を提供するために__getattribute__()メソッドが呼び出される。
  2. 明示的にdyn.colorを設定した後でさえも、__getattribute__()メソッドは依然として呼び出されdyn.colorの値を提供する。__getattribute__()メソッドが存在する場合は、全ての属性とメソッドを探すために、無条件で呼び出されるのだ。属性がインスタンスの生成後に明示的に設定されている場合でさえも呼び出される。

クラスに__getattribute__()を定義する場合は、おそらく__setattr__()メソッドも定義して、2つのメソッド間で属性値が追跡されるように連携させたいだろう。さもなければ、インスタンス作成後に設定した全ての属性はブラックホールへと消えてしまうことになる。

__getattribute__()メソッドにはさらなる注意を払う必要がある。このメソッドは、Pythonがメソッド名を探すときにも呼び出されるからだ。

class Rastan:
    def __getattribute__(self, key):
        raise AttributeError           
    def swim(self):
        pass

>>> hero = Rastan()
>>> hero.swim()                        
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __getattribute__
AttributeError
  1. このクラスは、常にAttributeError例外を送出する__getattribute__()メソッドを定義している。属性やメソッドの参照はすべて成功しなくなる。
  2. hero.swim()を呼び出すとき、PythonはRastanクラスからswim()メソッドを探し出す。全ての属性とメソッドの参照が__getattribute__()メソッドを通して行われるので、この参照も__getattribute__()メソッドを通して行われる。この場合は、__getattribute__()メソッドがAttributeError例外を送出するので、メソッドの参照は失敗し、したがってこのメソッド呼び出しは失敗する。

関数のように振る舞うクラス

__call__()メソッドを定義すると、(関数が呼び出せるのと同じように)クラスのインスタンスを呼び出せるようにできる。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
インスタンスを関数のように「呼び出す」 my_instance() my_instance.__call__()

zipfileモジュールは、これを使って、暗号化されたzipファイルを与えられたパスワードで復号するクラスを定義している。zipの復号アルゴリズムは、復号を行っているあいだ、内部状態を保持する必要がある。復号器をクラスとして定義することによって、復号器クラスの個々のインスタンスの中に内部状態を保持できるようになる。この状態は__init__()メソッドで初期化され、ファイルが復号されるにつれて更新される。しかし、このクラスは関数のように「呼び出し可能」でもあるので、そのインスタンスをmap()関数の最初の引数として渡せるのだ:

# excerpt from zipfile.py
class _ZipDecrypter:
.
.
.
    def __init__(self, pwd):
        self.key0 = 305419896               
        self.key1 = 591751049
        self.key2 = 878082192
        for p in pwd:
            self._UpdateKeys(p)

    def __call__(self, c):                  
        assert isinstance(c, int)
        k = self.key2 | 2
        c = c ^ (((k * (k^1)) >> 8) & 255)
        self._UpdateKeys(c)
        return c
.
.
.
zd = _ZipDecrypter(pwd)                    
bytes = zef_file.read(12)
h = list(map(zd, bytes[0:12]))             
  1. _ZipDecryptorクラスは、3つの回転キーの形で状態を保持する。これらは後に_UpdateKeys()メソッド(ここには示されていない)の中で更新される。
  2. このクラスは__call__()メソッドを定義している。このメソッドは、クラスインスタンスを関数のように呼び出せるようにする。この例での__call__()メソッドは、zipファイルの1つのバイトを復号し、復号されたバイトに基づいて回転キーを更新する。
  3. zd_ZipDecryptorクラスのインスタンスだ。変数pwdは、__init__()メソッドに渡され、そこで格納されて、回転キーの最初の更新に使われる。
  4. zipファイルの最初の12バイトが得られたら、そのバイトをzdにマッピングすることで復号を行う。これは実質的にはzdを12回「呼び出す」ことになり、それは__call__()メソッドを12回呼び出し、それは内部状態を12回更新して結果のバイトを12回返す。

シーケンスのように振る舞うクラス

クラスが、値の集合のためのコンテナとして振る舞うのなら(つまり、そのクラスは値を「含んでいる」か?という質問が意味をなすのであれば)、おそらくそのクラスは、シーケンスとして振る舞えるようにするための特殊メソッドを定義すべきだろう。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
シーケンスの長さ len(seq) seq.__len__()
シーケンスが特定の値を含んでいるかどうかを知る x in seq seq.__contains__(x)

cgiモジュールは、これらのメソッドをFieldStorageクラスの中で使っている。このクラスは、動的なWebページに送信されたすべてのフォームフィールドやクエリクエリパラメータを表現するものだ。

# A script which responds to http://example.com/search?q=cgi
import cgi
fs = cgi.FieldStorage()
if 'q' in fs:                                               
  do_search()

# 動作を説明するために、cgi.pyから抜粋
class FieldStorage:
.
.
.
    def __contains__(self, key):                            
        if self.list is None:
            raise TypeError('not indexable')
        return any(item.name == key for item in self.list)  

    def __len__(self):                                      
        return len(self.keys())                             
  1. cgi.FieldStorageクラスのインスタンスを作成すると、クエリ文字列に特定のパラメータが含まれているかどうかをin演算子を使って確認できる。
  2. __contains__()メソッドがこれを可能にしている魔法だ。
  3. if 'q' in fsと書くと、Pythonは__contains__()メソッドをfsオブジェクトの中から探し出す。このメソッドはcgi.pyに定義されている。'q'の値は引数keyとして__contains__()メソッドに渡される。
  4. このFieldStorageクラスは長さを返す機能もサポートしているので、len(fs)と書くことができる。これはFieldStorageクラスの__len__()メソッドを呼び出し、このクラスが認識したクエリパラメータの数が返される。
  5. self.list is Noneであるかどうかはself.keys()メソッドが確認するので、__len__メソッドが余計なエラーチェックをする必要はない。

辞書のように振る舞うクラス

前節の内容を少し拡張することで、in演算子とlen()関数に応答するだけでなく、キーに基づいて値を返す完全な辞書として振る舞うクラスを定義できる。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
キーに対応する値を得る x[key] x.__getitem__(key)
キーに対応する値を設定する x[key] = value x.__setitem__(key, value)
キーと値のペアを削除する del x[key] x.__delitem__(key)
存在しないキーのためのデフォルト値を提供する x[nonexistent_key] x.__missing__(nonexistent_key)

cgiモジュールにあるFieldStorageクラスもこれらの特殊メソッドを実装しており、これは次のようなことができることを意味している:

# A script which responds to http://example.com/search?q=cgi
import cgi
fs = cgi.FieldStorage()
if 'q' in fs:
  do_search(fs['q'])                              

# 動作を示すために、cgi.pyから抜粋
class FieldStorage:
.
.
.
    def __getitem__(self, key):                   
        if self.list is None:
            raise TypeError('not indexable')
        found = []
        for item in self.list:
            if item.name == key: found.append(item)
        if not found:
            raise KeyError(key)
        if len(found) == 1:
            return found[0]
        else:
            return found
  1. fsオブジェクトはcgi.FieldStorageのインスタンスだが、fs['q']のような式を評価することもできる。
  2. fs['q']は、key引数に'q'を設定して__getitem__()メソッドを呼び出す。このメソッドは、内部に保持されたクエリパラメータのリスト (self.list) の中から、その.nameが与えられたキーと一致する要素を探し出す。

数値のように振る舞うクラス

適切な特殊メソッドを使うと、数値のように振る舞う独自のクラスを定義できる。要するに、それらを足したり、引いたり、その他の数学的な演算を行うことができるようになるのだ。fractionsモジュールはこの方法で実装されている — つまりFractionクラスはこれらの特殊メソッドを実装しており、それによって次のようなことができるようになっている:

>>> from fractions import Fraction
>>> x = Fraction(1, 3)
>>> x / 3
Fraction(1, 9)

数値のように振る舞うクラスを実装する際に必要となる特殊メソッドの包括的なリストを示す。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
加算 x + y x.__add__(y)
減算 x - y x.__sub__(y)
乗算 x * y x.__mul__(y)
除算 x / y x.__truediv__(y)
整数除算 x // y x.__floordiv__(y)
モジュロ (余り) x % y x.__mod__(y)
整数除算とモジュロ divmod(x, y) x.__divmod__(y)
べき乗 x ** y x.__pow__(y)
左ビットシフト x << y x.__lshift__(y)
右ビットシフト x >> y x.__rshift__(y)
ビット単位のand x & y x.__and__(y)
ビット単位のxor x ^ y x.__xor__(y)
ビット単位のor x | y x.__or__(y)

xがこれらのメソッドを実装したクラスのインスタンスであれば、すべてがうまく動作する。しかし、xがこれらのメソッドを実装していない場合はどうなるのだろうか? または、さらに悪いことに、これらを実装しているのに、ある種の引数を扱うことができない場合はどうなるのだろうか? 例えばこういうことだ:

>>> from fractions import Fraction
>>> x = Fraction(1, 3)
>>> 1 / x
Fraction(3, 1)

この例は(前の例のように)Fractionを整数で割っているのではない。前の例は分かりやすかった。前の例は、x / 3x.__truediv__(3)を呼び出し、Fraction__truediv__()メソッドがすべての計算を処理していた。しかし整数型は分数を使った算術演算のやり方を「知らない」。だとすると、この例はどのように動作しているのだろうか?

算術演算の特殊メソッドには、演算対象を反転させた2つ目のセットが存在する。被演算子を2つとる算術演算(例えば x / y)が与えられた場合、これを実行する方法は2通り存在する:

  1. xに対して、自身をyで割るように告げる、もしくは
  2. yに対して、xを自身で割るように告げる。

先に挙げた特殊メソッドのセットは1つ目のアプローチを取る。つまりこれらの特殊メソッドは、x / yが与えられたときにxが「私は自分をyで割る方法を知っているよ」と言うための手段を提供する。以下に挙げる特殊メソッドの集合は2つ目のアプローチを取る。つまりこれらの特殊メソッドは、yが「私は、自分が分母になってxを割る方法を知っている」と言うための手段を提供するのだ。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
加算 x + y y.__radd__(x)
減算 x - y y.__rsub__(x)
乗算 x * y y.__rmul__(x)
除算 x / y y.__rtruediv__(x)
整数除算 x // y y.__rfloordiv__(x)
モジュロ (余り) x % y y.__rmod__(x)
整数除算とモジュロ divmod(x, y) y.__rdivmod__(x)
べき乗 x ** y y.__rpow__(x)
左ビットシフト x << y y.__rlshift__(x)
右ビットシフト x >> y y.__rrshift__(x)
ビット単位のand x & y y.__rand__(x)
ビット単位のxor x ^ y y.__rxor__(x)
ビット単位のor x | y y.__ror__(x)

ちょっと待って! これで終わりじゃない! x /= 3のような「インプレイス」の演算を行おうとする場合は、定義可能な特殊メソッドがさらに存在するのだ。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
インプレイスの加算 x += y x.__iadd__(y)
インプレイスの減算 x -= y x.__isub__(y)
インプレイスの乗算 x *= y x.__imul__(y)
インプレイスの除算 x /= y x.__itruediv__(y)
インプレイスの整数除算 x //= y x.__ifloordiv__(y)
インプレイスのモジュロ x %= y x.__imod__(y)
インプレイスのべき乗 x **= y x.__ipow__(y)
インプレイスの左ビットシフト x <<= y x.__ilshift__(y)
インプレイスの右シフト x >>= y x.__irshift__(y)
インプレイスのビット単位のand x &= y x.__iand__(y)
インプレイスのビット単位のxor x ^= y x.__ixor__(y)
インプレイスのビット単位のor x |= y x.__ior__(y)

注: ほとんどの状況において、インプレイス演算のメソッドは必要ない。特定の演算用のインプレイスメソッドが定義されていない場合、Pythonは別の方法でそれらのメソッドを試みようとする。例えば、x /= yを実行するとき、Pythonは次のように動作する:

  1. x.__itruediv__(y)の呼び出しを試みる。このメソッドが定義されていてNotImplemented以外の値を返すのであれば、もう完了だ。
  2. x.__truediv__(y)の呼び出しを試みる。このこのメソッドが定義されていてNotImplemented以外の文字を返すのであれば、xの古い値を捨てて、戻り値で置き換える。つまり、代わりに x = x / yを実行したのとちょうど同じことだ。
  3. y.__rtruediv__(x)の呼び出しを試みる。このメソッドが定義されていてNotImplemented以外の文字を返すのであれば、xの古い値を捨てて、戻り値で置き換える。

したがって、__itruediv__()のようなインプレイス演算のメソッドを定義しなければならないのは、インプレイス演算子に対して何か特別な最適化を施したい場合だけだ。そうでなければ、原則的にPythonは、通常の+演算子と変数代入を組合せてインプレイス演算の式を作り上げる。

「単項」演算もいくつか存在し、これらは、数値としてふるまうオブジェクト1つに対して実行できる。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
負の数 -x x.__neg__()
正の数 +x x.__pos__()
絶対値 abs(x) x.__abs__()
反転 ~x x.__invert__()
複素数 complex(x) x.__complex__()
整数 int(x) x.__int__()
浮動小数点数 float(x) x.__float__()
最も近い整数へ丸めた数値 round(x) x.__round__()
最も近いn桁へ丸めた数値 round(x, n) x.__round__(n)
最小の整数 >= x math.ceil(x) x.__ceil__()
最大の整数 <= x math.floor(x) x.__floor__()
0に向けた最も近い整数への切り捨て math.trunc(x) x.__trunc__()
PEP 357 リストのインデックスとしての数 a_list[x] a_list[x.__index__()]

比較可能なクラス

比較というのは厳密には数値の範疇ではないので、私はこの節を前節とは分けることにした。多くのデータ型は比較できる — 文字列、リスト、そして辞書でさえも比較可能だ。クラスを作る際に、そのオブジェクト同士の比較が意味をなすのであれば、以下の特殊メソッドを使って比較を実装できる。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
等式 x == y x.__eq__(y)
不等 x != y x.__ne__(y)
より小さい x < y x.__lt__(y)
より小さいか等しい x <= y x.__le__(y)
より大きい x > y x.__gt__(y)
より大きいか等しい x >= y x.__ge__(y)
ブール値のコンテクストでの真偽値 if x: x.__bool__()

__lt__()メソッドが定義されているが__gt__()メソッドは定義されていない場合、Pythonは被演算子の順番を入れ替えて__lt__()メソッドを使用する。しかし、Pythonが演算子を組み合わせることはない。例えば、__lt__()メソッドと__eq()__を実装してx <= yが成り立つかを確認しようとしても、Pythonが__lt__()__eq()__を順に呼び出すことはない。Pythonは__le__()メソッドしか呼び出さないのだ。

シリアライズ可能なクラス

Pythonは、任意のオブジェクトのシリアライズとアンシリアライズをサポートしている(Pythonの文献の多くは、これを「Pickle化」や「非Pickle化」と呼んでいる)。この機能は、ファイルに状態を保存して後でそれを復元するのに役立つ。すべてのネイティブデータ型はあらかじめPickle化をサポートしている。作成したクラスをPickle化に対応させたい場合は、Pickleプロトコルに関する説明を読み、下記の特殊メソッドがいつ・どのように呼び出されるのかを確認してほしい。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
オブジェクトのカスタマイズされたコピー copy.copy(x) x.__copy__()
オブジェクトのカスタマイズされた深いコピー copy.deepcopy(x) x.__deepcopy__()
Pickle化する前のオブジェクトの状態の取得 pickle.dump(x, file) x.__getstate__()
オブジェクトのシリアライズ pickle.dump(x, file) x.__reduce__()
オブジェクトのシリアライズ (新Pickle化プロトコル) pickle.dump(x, file, protocol_version) x.__reduce_ex__(protocol_version)
* 非Pickle化時にどのようにオブジェクトを生成するかの制御 x = pickle.load(file) x.__getnewargs__()
* 非Pickle化の後にオブジェクトの状態を復元する x = pickle.load(file) x.__setstate__()

* シリアライズされたオブジェクトを再作成するには、Pythonは、シリアライズされたオブジェクトに似せた新しいオブジェクトを作り、その新しいオブジェクトにすべての属性値を設定しなければならない。__getnewargs__()メソッドはオブジェクトがどのように作られるのかを制御し、__setstate__()メソッドは属性値がどのように設定されるのかを制御する。

withブロックで使えるクラス

withブロックは実行時コンテクストを定義する。with文を実行するとそのコンテクストに「入り」、そのブロックの最後の文を実行し終えるとそのコンテクストから「出る」。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
withブロックに入るときに何か特別なことをする with x: x.__enter__()
withブロックから出るときに何か特別なことをする with x: x.__exit__(exc_typeexc_valuetraceback)

これは、with fileイディオムがどのように動作するのかを示している。

# excerpt from io.py:
def _checkClosed(self, msg=None):
    '''Internal: raise an ValueError if file is closed
    '''
    if self.closed:
        raise ValueError('I/O operation on closed file.'
                         if msg is None else msg)

def __enter__(self):
    '''Context management protocol.  Returns self.'''
    self._checkClosed()                                
    return self                                        

def __exit__(self, *args):
    '''Context management protocol.  Calls close()'''
    self.close()                                       
  1. ファイルオブジェクトは__enter__()メソッドと__exit__()メソッドを定義している。__enter__()メソッドはファイルが開かれていることを確認する。もしそうでない場合は_checkClosed()メソッドが例外を発生する。
  2. __enter__()メソッドは、ほぼすべての場合においてselfを返すべきだ。このオブジェクトは、withブロックがプロパティやメソッドをディスパッチするために使用する。
  3. withブロックが終わると、ファイルオブジェクトは自動的に閉じられる。これはどのように実現されているのだろうか? __exit__()メソッドの中で、self.close()を呼び出しているのだ。

The __exit__()メソッドは、たとえwithブロックの中で例外が発生したとしても必ず呼び出される。事実、例外が発生した場合は、例外の情報が__exit__()メソッドに渡される。詳細はWith Statement Context Managersを見てほしい。

コンテクストマネージャについての説明は、ファイルを自動的に閉じる標準出力をリダイレクトするを見てほしい。

完全に奥義的なもの

自分が何をしているのかを理解しているのであれば、クラスがどのように比較されるか、属性がどのように定義されるか、どんなクラスがそのクラスのサブクラスと見なされるか、などの制御をほぼ完全に掌握できる。

Notes 望むものは…… よって、こう書く…… そして、Pythonが呼び出すのは……
クラスのコンストラクタ x = MyClass() x.__new__()
* クラスのデストラクタ del x x.__del__()
指定した属性のみを定義できるようにする x.__slots__()
カスタムのハッシュ値 hash(x) x.__hash__()
プロパティの値を取得する x.color type(x).__dict__['color'].__get__(x, type(x))
プロパティの値を設定する x.color = 'PapayaWhip' type(x).__dict__['color'].__set__(x, 'PapayaWhip')
プロパティを削除する del x.color type(x).__dict__['color'].__delete__(x)
オブジェクトがあなたのクラスのインスタンスかどうかの判断を制御する isinstance(x, MyClass) MyClass.__instancecheck__(x)
クラスがあなたのクラスのサブクラスかどうかの判断を制御する issubclass(C, MyClass) MyClass.__subclasscheck__(C)
クラスがあなたの抽象基底クラスのサブクラスかどうかの判断を制御する issubclass(C, MyABC) MyABC.__subclasshook__(C)

* Pythonが特殊メソッド__del__()を呼び出す正確なタイミングは信じられないほど複雑だ。これを完全に理解するには、Pythonがメモリ上でオブジェクトを追跡する方法を知る必要がある。これについての良い記事がPython garbage collection and class destructorsにある。それに加えてweak referencesweakref モジュール、おまけとしてgc モジュールも読むと良いだろう。

もっと知りたい人のために

このAppendixで言及したモジュール:

その他の軽い読みもの:

© 2001– Mark Pilgrim
© Fukada, Fujimoto(日本語版)