特殊メソッドを使うことで、自作のクラスをシーケンスのように振る舞せたり、 関数のように振る舞せたり、イテレータのように振る舞せたりすることができる。 数値として振る舞わせることさえできる。 このAppendixは、すでに目にした特殊メソッドのレファレンスを提供するとともに、より奥義的な特殊メソッドの一部を手短に紹介する。
基礎的な特殊メソッドは他にもいくつか存在し、それらは自作のクラスをデバッグするときに特に便利。
__init__()
メソッドは、インスタンスが作成された後に呼び出される。もし実際の生成プロセスを制御したい場合は__new__()
メソッドを使用する。
インスタンスを初期化する:
x = MyClass()
内部: x.__init__()
__repr__()
メソッドは、有効なPythonの式を表す文字列を返すべき。
文字列としての「正式な」表現:
repr(x)
内部: x.__repr__()
__str__()
メソッドは、print(x)
するときにも呼び出される。
文字列としての「インフォーマルな」値:
str(x)
内部:
x.__str__()
bytes型の導入にともない、Python 3で新たに加わった。
バイト列としての「インフォーマルな」値:
bytes(x)
内部: x.__bytes__()
慣例により、format_specはFormat Specification Mini-Languageに従うべきである。
Python標準ライブラリのdecimal.pyは、独自の__format__()
メソッドを持っている
フォーマットされた文字列としての値:
format(x, format_spec)
内部: x.__format__(format_spec)
__iter__()
メソッドは、新しいイテレータを作るときに呼び出される。
このメソッドは、イテレータの初期値を設定するのに適した場所。
シーケンスをイテレートする:
iter(seq)
内部:
seq.__iter__()
__next__()
メソッドは、イテレータから次の値を取得するときに呼び出される。
イテレータから次の値を取得する:
next(seq)
内部: seq.__next__()
__reversed__()
メソッドはあまり使われない。
これは、既存のシーケンスを受け取って、そのシーケンスの要素を逆順に(末尾から先頭へ)生み出すイテレータを返す。
逆順のイテレータを作る:
reversed(seq)
内部: seq.__reversed__()
参考:http://docs.python.jp/3/reference/datamodel.html#object.__reversed__
forループはイテレータに従って役目を果せる。以下のループでは:
for x in seq:
print(x)
Python 3 は、seq.__iter__()
を呼び出してイテレータを作り、
次にイテレータの __next__()
メソッドを呼び出して各々のxの値を取得する。
__next__()
メソッドが StopIteration
例外を送出すると、
forループは静かに終了する。
クラスが __getattribute__()
メソッドを定義している場合は、すべての属性名/メソッド名に対するすべての参照においてPythonがこのメソッドを呼び出す(ただし特殊メソッド名は、不快な無限ループを引き起こすので除外される)。
算出属性を取得する(無条件):
x.my_property
内部: x.__getattribute__('my_property')
クラスが __getattr__()
メソッドを定義している場合は、通常通りのすべての場所から属性を探したあとに限って、
Pythonがこのメソッドを呼び出す。
インスタンスxが属性colorを定義している場合には、x.colorはx.__getattr__('color')
を呼び出さない。
これは、すでに定義された x.color
の値を返すだけだ。
算出属性を取得する(フォールバック):
x.my_property
内部:x.__getattr__('my_property')
__setattr__()
メソッドは、属性に値を代入しようとするときに呼び出される。
属性を設定する:
x.my_property = value
内部:
x.__setattr__('my_property', value)
__delattr__()
メソッドは、属性を削除しようとするときに呼び出される。
属性を削除する:
del x.my_property
内部: x.__delattr__('my_property')
__dir__()
メソッドは、__getattr__()
メソッドや __getattribute__()
メソッドを定義するときに便利。
通常、dir(x)
を呼び出すと、標準の属性とメソッドだけがリストアップされる。つまり、__getattr()__
メソッドがcolor属性を動的に提供する場合は、dir(x)
はcolorを利用可能な属性としてリストアップしてくれない。
__dir__()
をオーバーライドすることによって、color属性が利用可能な属性としてリストアップされるようできる。
そうしておけば、クラスを使いたいと思う人にとって助けになる。さもないと、コードの内部を掘り起こさないといけなくなるかもしれない。
全ての属性とメソッドをリストアップする:
dir(x)
内部: x.__dir__()
__getattr__()
メソッド と __getattribute__()
メソッドの違いはわずかだが重要。
これは2つの例で説明できる:
__getattr()__
メソッドに文字列として渡される。その名前が'color'であれば、このメソッドは値を返す(この例ではハードコードされた文字列を返しているだけだが、実際には、いくつかの計算などを行なって、その結果を返すのがふつうだ)。
__getattr()__
メソッドは AttributeError
例外を発生させる必要がある。さもなければ、未定義の属性にアクセスしたときに、コードは密やかに誤動作を始めるだろう(詳しく言うと、このメソッドは、例外を送出しなかったり明示的に値を返さない場合にはPythonの無効値であるNoneを返すのだ。これは、明示的に定義されていないすべての属性値が None になることを意味するが、ほぼ確実に、それは望ましい振る舞いではない)。
class Dynamo:
def __getattr__(self, key):
if key == 'color': # 1
return 'PapayaWhip'
else:
raise AttributeError # 2
dyn = Dynamo()
インスタンスdyn
は color
という名前の属性を持っていないので、
__getattr__()
メソッドが呼び出されて、算出された値が提供される。
dyn.color
'PapayaWhip'
dyn.color = 'LemonChiffon'
dyn.color
を明示的に設定した後は、もはや __getattr__()
メソッドは dyn.color
を提供するために呼び出されない。
インスタンス上にすでに dyn.color
が定義されているため。
dyn.color
'LemonChiffon'
その一方で、__getattribute__()
メソッドは絶対的なものであり、無条件で使われる。
class SuperDynamo:
def __getattribute__(self, key):
if key == 'color':
return 'PapayaWhip'
else:
raise AttributeError
dyn = SuperDynamo()
dyn.color
の値を提供するために __getattribute__()
メソッドが呼び出される。
dyn.color
'PapayaWhip'
明示的にdyn.colorを設定した後でさえも、__getattribute__()
メソッドは依然として呼び出され、dyn.colorの値を提供する。
__getattribute__()
メソッドが存在する場合は、全ての属性とメソッドを探すために、無条件で呼び出される。
属性がインスタンスの生成後に明示的に設定されている場合でさえも呼び出される。
dyn.color = 'LemonChiffon'
dyn.color
'PapayaWhip'
クラスに __getattribute__()
を定義する場合は、
おそらく __setattr__()
メソッドも定義して、2つのメソッド間で属性値が追跡されるように連携させる。
さもなければ、インスタンス作成後に設定した全ての属性は消えてしまうことになる。
__getattribute__()
メソッドにはさらなる注意を払う必要がある。
このメソッドは、Pythonがメソッド名を探すときにも呼び出されるため。
このクラスは、常にAttributeError例外を送出する __getattribute__()
メソッドを定義している。
属性やメソッドの参照はすべて成功しなくなる。
class Rastan:
def __getattribute__(self, key):
raise AttributeError
def swim(self):
pass
hero.swim()を呼び出すとき、PythonはRastanクラスからswim()メソッドを探し出す。
全ての属性とメソッドの参照が __getattribute__()
メソッドを通して行われるので、
この参照も __getattribute__()
メソッドを通して行われる。
この場合は、__getattribute__()
メソッドが AttributeError
例外を送出するので、メソッドの参照は失敗し、
したがってこのメソッド呼び出しは失敗する。
hero = Rastan()
hero.swim()
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-9-bd649b032304> in <module>() 1 hero = Rastan() ----> 2 hero.swim() <ipython-input-8-a640d3923d15> in __getattribute__(self, key) 1 class Rastan: 2 def __getattribute__(self, key): ----> 3 raise AttributeError 4 5 def swim(self): AttributeError:
class Rastan:
def swim(self):
pass
hero = Rastan()
hero.swim()
dir(hero)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'swim']
hero.swim
<bound method Rastan.swim of <__main__.Rastan object at 0x000001960E3D7EF0>>
__call__()
メソッドを定義すると、(関数が呼び出せるのと同じように)クラスのインスタンスを呼び出せるようにできる。
インスタンスを関数のように「呼び出す」:
my_instance()
内部: my_instance.__call__()
zipfile
モジュール は、これを使って、暗号化されたzipファイルを与えられたパスワードで復号するクラスを定義している。
zipの復号アルゴリズムは、復号を行っているあいだ、内部状態を保持する必要がある。
復号器をクラスとして定義することによって、復号器クラスの個々のインスタンスの中に内部状態を保持できるようになる。
この状態は __init__()
メソッドで初期化され、ファイルが復号されるにつれて更新される。
しかし、このクラスは関数のように「呼び出し可能」でもあるので、そのインスタンスを map()
関数の最初の引数として渡せるのだ:
_ZipDecryptor
クラスは、3つの回転キーの形で状態を保持する。これらは後に _UpdateKeys()
メソッド(ここには示されていない)の中で更新される。
__call__()
メソッドを定義している。このメソッドは、クラスインスタンスを関数のように呼び出せるようにする。
この例での __call__()
メソッドは、zipファイルの1つのバイトを復号し、復号されたバイトに基づいて回転キーを更新する。
# excerpt from zipfile.py
class _ZipDecrypter:
.
.
.
def __init__(self, pwd):
self.key0 = 305419896 # 1
self.key1 = 591751049
self.key2 = 878082192
for p in pwd:
self._UpdateKeys(p)
def __call__(self, c): # 2
assert isinstance(c, int)
k = self.key2 | 2
c = c ^ (((k * (k^1)) >> 8) & 255)
self._UpdateKeys(c)
return c
.
.
.
zd = _ZipDecrypter(pwd) # 3
bytes = zef_file.read(12)
h = list(map(zd, bytes[0:12])) # 4
3.zd
は _ZipDecryptor
クラスのインスタンス。
変数pwd
は、 __init__()
メソッドに渡され、そこで格納されて、回転キーの最初の更新に使われる。
4.zipファイルの最初の12バイトが得られたら、
そのバイトを zd
にマッピングすることで復号を行う。
これは実質的には zd
を12回「呼び出す」ことになり、
それは __call__()
メソッドを12回呼び出し、それは内部状態を12回更新して結果のバイトを12回返す。
クラスが、値の集合のためのコンテナとして振る舞うのなら(つまり、そのクラスは値を「含んでいる」か?という質問が意味をなすのであれば)、 おそらくそのクラスは、シーケンスとして振る舞えるようにするための特殊メソッドを定義すべき。
シーケンスの長さ:
len(seq)
内部: seq.__len__()
シーケンスが特定の値を含んでいるかどうかを知る:
x in seq
内部: seq.__contains__(x)
cgi
モジュールは、これらのメソッドを FieldStorage
クラスの中で使っている。
このクラスは、動的なWebページに送信されたすべてのフォームフィールドやクエリクエリパラメータを表現するもの。
import cgi
cgi
<module 'cgi' from 'C:\\Miniconda3\\lib\\cgi.py'>
cgi.FieldStorage
クラスのインスタンスを作成すると、
クエリ文字列に特定のパラメータが含まれているかどうかを in
演算子を使って確認できる。
fs = cgi.FieldStorage()
if 'q' in fs:
print('contain "q"')
# 動作を説明するために、cgi.pyから抜粋
class FieldStorage:
.
.
.
def __contains__(self, key): 2
if self.list is None:
raise TypeError('not indexable')
return any(item.name == key for item in self.list) 3
def __len__(self): 4
return len(self.keys()) 5
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()
関数に応答するだけでなく、
キーに基づいて値を返す完全な辞書として振る舞うクラスを定義できる。
キーに対応する値を得る:
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クラスもこれらの特殊メソッドを実装しており、これは次のようなことができることを意味している:
fs
オブジェクトは cgi.FieldStorage
のインスタンスだが、fs['q']
のような式を評価することもできる。
# A script which responds to http://example.com/search?q=cgi
import cgi
fs = cgi.FieldStorage()
if 'q' in fs:
print('q= ' + fs['q']) # 1
fs['q']
は、key引数に'q'を設定して __getitem__()
メソッドを呼び出す。
このメソッドは、内部に保持されたクエリパラメータのリスト (self.list
) の中から、
その .name
が与えられたキーと一致する要素を探し出す。
# 動作を示すために、cgi.pyから抜粋
class FieldStorage:
.
.
.
def __getitem__(self, key): # 2
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
適切な特殊メソッドを使うと、数値のように振る舞う独自のクラスを定義できる。
要するに、それらを足したり、引いたり、その他の数学的な演算を行うことができるようになる。
fractions
モジュールはこの方法で実装されている
— つまり Fraction
クラスはこれらの特殊メソッドを実装しており、それによって次のようなことができるようになっている:
from fractions import Fraction
x = Fraction(1, 3)
x / 3
Fraction(1, 9)
数値のように振る舞うクラスを実装する際に必要となる特殊メソッドの包括的なリストを示す。
演算 | 実際のコード | 内部で呼び出さるメソッド |
---|---|---|
加算 | 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) |
これらを実装しているのに、ある種の引数を扱うことができない場合はどうなるのだろうか? 例えばこういうことだ:
from fractions import Fraction
x = Fraction(1, 3)
1 / x
Fraction(3, 1)
この例は(前の例のように)Fraction
を整数で割っているのではない。
前の例は分かりやすかった。
前の例は、x / 3
が x.__truediv__(3)
を呼び出し、
Fraction
の __truediv__()
メソッドがすべての計算を処理していた。
しかし整数型は分数を使った算術演算のやり方を「知らない」。
だとすると、この例はどのように動作しているのだろうか?
算術演算の特殊メソッドには、
演算対象を反転させた2つ目のセットが存在する。
被演算子を2つとる算術演算(例えば x / y
)が与えられた場合、
これを実行する方法は2通り存在する:
x
に対して、自身をy
で割るように告げる、もしくは(x/y)y
に対して、x
を自身で割るように告げる。(x/y)先に挙げた特殊メソッドのセットは1つ目のアプローチを取る。
つまりこれらの特殊メソッドは、x / yが与えられたときにx
が「私は自分をy
で割る方法を知っているよ」と言うための手段を提供する。
以下に挙げる特殊メソッドの集合は2つ目のアプローチを取る。
つまりこれらの特殊メソッドは、y
が「私は、自分が分母になってxを割る方法を知っている」と言うための手段を提供する。
演算 | 実際のコード | 内部で呼び出さるメソッド |
---|---|---|
加算 | 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
のような「インプレイス」の演算を行おうとする場合は、定義可能な特殊メソッドがさらに存在する。
演算 | 実際のコード | 内部で呼び出さるメソッド |
---|---|---|
インプレイスの加算 | 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は次のように動作する:
x.__itruediv__(y)
の呼び出しを試みる。このメソッドが定義されていて NotImplemented
以外の値を返すのであれば、完了。
x.__truediv__(y)
の呼び出しを試みる。このこのメソッドが定義されていて NotImplemented
以外の文字を返すのであれば、
xの古い値を捨てて、戻り値で置き換える。
つまり、代わりに x = x / y
を実行したのとちょうど同じこと。
y.__rtruediv__(x)
の呼び出しを試みる。このメソッドが定義されていて NotImplemented
以外の文字を返すのであれば、x
の古い値を捨てて、戻り値で置き換える。
したがって、__itruediv__()
のようなインプレイス演算のメソッドを定義しなければならないのは、
インプレイス演算子に対して何か特別な最適化を施したい場合だけ。
そうでなければ、原則的にPythonは、 通常の+演算子と変数代入を組合せてインプレイス演算の式を作り上げる。
「単項」演算もいくつか存在し、 これらは、数値としてふるまうオブジェクト1つに対して実行できる。
演算 | 実際のコード | 内部で呼び出さるメソッド |
---|---|---|
負の数 | -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__()] |
多くのデータ型は比較できる — 文字列、リスト、そして辞書でさえも比較可能。 クラスを作る際に、そのオブジェクト同士の比較が意味をなすのであれば、 以下の特殊メソッドを使って比較を実装できる。
演算 | 実際のコード | 内部で呼び出さるメソッド |
---|---|---|
等式 | 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
プロトコルに関する説明を読み、
下記の特殊メソッドがいつ・どのように呼び出されるのかを確認。
オブジェクトのカスタマイズされたコピー:
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ブロックに入るときに何か特別なことをする:
with x:
内部: x.__enter__()
withブロックから出るときに何か特別なことをする:
with x:
内部: x.__exit__(exc_type、exc_value、traceback)
これは、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() 1
return self 2
def __exit__(self, *args):
'''Context management protocol. Calls close()'''
self.close() 3
ファイルオブジェクトは __enter__()
メソッドと __exit__()
メソッドを定義している。__enter__()
メソッドはファイルが開かれていることを確認する。もしそうでない場合は_checkClosed()
メソッドが例外を発生する。
__enter__()
メソッドは、ほぼすべての場合において self
を返すべき。
このオブジェクトは、withブロックがプロパティやメソッドをディスパッチするために使用する。
with
ブロックが終わると、ファイルオブジェクトは自動的に閉じられる。
__exit__()
メソッドの中で、self.close()
を呼び出している。
__exit__()
メソッドは、
たとえ with
ブロックの中で例外が発生したとしても必ず呼び出される。
事実、例外が発生した場合は、例外の情報が __exit__()
メソッドに渡される。
何をしているのかを理解しているのであれば、クラスがどのように比較されるか、属性がどのように定義されるか、どんなクラスがそのクラスのサブクラスと見なされるか、などの制御をほぼ完全に掌握できる。
クラスのコンストラクタ:
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)
Pythonが特殊メソッド __del__()
を呼び出す正確なタイミングは信じられないほど複雑。これを完全に理解するには、Python がメモリ上でオブジェクトを追跡する方法を知る必要がある。
def str2table(st:str):
import re
dist = re.sub(r'^([^|])', r'|\1', st, flags=re.MULTILINE)
dist = re.sub(r'([^|])$', r'\1|', dist, flags=re.MULTILINE)
dist = re.sub(r'\t[\t|\b]*', r'|', dist)
print(dist)
st = '''等式 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__()'''
print(str2table(st))