今回は,Pythonでリストのようなものを実装します.ここでリストのようなというのは以下の条件を満たすこととします.
- インデックスにより要素を取り出せる(
__getitem__
を実装している) - for文に添えることができる(
__iter__
を実装している) - スライスを利用できる(
__getitem__
でスライスの処理を実装している)
具体的には,以下のように桁ごとに分割できる整数を実装していきます
digit_devided_int = DigitDividedInt(1234567890)
print(digit_devided_int[3])
print(list(digit_devided_int))
7
[0, 9, 8, 7, 6, 5, 4, 3, 2, 1]
for digit in digit_devided_int:
print(digit)
0
9
8
7
6
5
4
3
2
1
list(digit_devided_int[0:5:2])
[0, 8, 6]
DigitDividedInt
の定義は以下となります.数値を桁ごとに分割するには文字列に変換することでも実現できその方が効率的ですが,今回は文字列の変換を用いずに実装してみます.
class DigitDividedInt:
"""
桁ごとに分割するint
"""
def __init__(self, _int):
self._inner_int = _int
self._digit_dict = {}
digit_counter = 1
while True:
if _int < 10**(digit_counter+1):
break
digit_counter += 1
self.max_digit = digit_counter # 桁数(0からスタート)
for i in list(range(digit_counter+1))[::-1]: # 後ろから
digit_number = _int // (10**i)
self._digit_dict[str(10**i)] = digit_number
_int = _int - digit_number * (10**i)
def __int__(self):
return int(self._inner_int)
def __len__(self):
return len(self._digit_dict.keys())
def __getitem__(self, _slice):
if isinstance(_slice, int):
index = _slice
return self._getitem_index(index)
elif isinstance(_slice, str):
key = _slice
return self._getitem_key(key)
elif isinstance(_slice, slice):
return self._getitem_slice(_slice)
else:
raise Exception("Invalid arg type of __getitem__")
def _getitem_index(self, index):
if index > self.max_digit:
raise IndexError("DigitDivideInt index out of range")
return self._digit_dict[str(10**index)]
def _getitem_key(self, key):
if key not in self._digit_dict.keys():
raise KeyError(str(key))
return self._digit_dict[key]
def _getitem_slice(self, _slice):
start, stop, step = _slice.start, _slice.stop, _slice.step
if (isinstance(start, int) or start is None) and (isinstance(stop, int) or stop is None) and (isinstance(step, int) or step is None):
if start is None: # startがNoneの場合
start = 0
if stop is None: # stopがNoneの場合
stop = len(self)
if step is None: # stepがNoneの場合
step = 1
for index in range(start, stop, step):
yield self._getitem_index(index)
elif (isinstance(start, str) or start is None) and (isinstance(stop, str) or stop is None) and (isinstance(step, int) or step is None):
key_list = list(self._digit_dict.keys())
if (start not in key_list) or (stop not in key_list): # start, stopがキーにあるかどうか
raise Exception("start and stop of slicke must be in digit of int")
int_key_list = map(int, key_list) # 整数にしたキーのリスト
if start is None: # startがNoneの場合
start = str(min(int_key_list))
if stop is None: # stopがNoneの場合
stop = str(max(int_key_list))
if step is None: # stepがNoneの場合
step = 1
# 範囲内のキーの取得とソート
int_key_in_range = [int_key for int_key in int_key_list if (int(start) <= int_key) and (int_key < int(stop))]
key_in_range = [str(key) for key in sorted(int_key_in_range)]
for key in key_in_range[::step]:
yield self._getitem_key(key)
else:
raise TypeError("slice types are invalid")
def __iter__(self):
for index in range(len(self)):
yield self[index]
def __list__(self):
return list(iter(self))
初期化メソッドでは桁とその値を対応させたディクショナリself._digit_dict
を作成しています.例えば,先ほどの例の_digit_dict
はこのようになります.
digit_devided_int._digit_dict
{'1000000000': 1,
'100000000': 2,
'10000000': 3,
'1000000': 4,
'100000': 5,
'10000': 6,
'1000': 7,
'100': 8,
'10': 9,
'1': 0}
そして条件1と3を満たすために特殊メソッド__getitem__
を実装します.__getimtem__
はdigit_devided_int[1]
のように添え字を与えたときに呼ばれます.
今回は添え字の型によって条件分岐してそれぞれメソッドを実行しています.おまけとして'1000'
などの文字列を与えたときも辞書のように値を取得できます.
def __getitem__(self, _slice):
if isinstance(_slice, int):
index = _slice
return self._getitem_index(index)
elif isinstance(_slice, str):
key = _slice
return self._getitem_key(key)
elif isinstance(_slice, slice):
return self._getitem_slice(_slice)
else:
raise Exception("Invalid arg type of __getitem__")
普通に整数のインデックスを添えた場合は_getitem_index
が呼ばれます._getitem_index
メソッドではインデックスから対応するキーを作成し,self._digit_dict
から値を取り出しています.
def _getitem_index(self, index):
if index > self.max_digit:
raise IndexError("DigitDivideInt index out of range")
return self._digit_dict[str(10**index)]
digit_devided_int[0:8:3]
のようにスライスを添えたときに呼ばれるのが_getitem_slice
メソッドです.スライスに数値を与えたときと文字列を与えたときの二つを実装していますが,今回は数値を与えたときを見ていきます.スライスは部分的に与えることが多いので,Noneの時も大丈夫なようにします.ここではスライスの処理をrange
に移譲させfor
文で取り出しながらyield
するジェネレーターを返します.これは取り出す処理の負荷が大きく一つ一つしか取り出せないような状況を想定したものであり,今回の例では普通にリストを返して問題ありません.
def _getitem_slice(self, _slice):
start, stop, step = _slice.start, _slice.stop, _slice.step
if (isinstance(start, int) or start is None) and (isinstance(stop, int) or stop is None) and (isinstance(step, int) or step is None):
if start is None: # startがNoneの場合
start = 0
if stop is None: # stopがNoneの場合
stop = len(self)
if step is None: # stepがNoneの場合
step = 1
for index in range(start, stop, step):
yield self._getitem_index(index)
elif (isinstance(start, str) or start is None) and (isinstance(stop, str) or stop is None) and (isinstance(step, int) or step is None):
key_list = list(self._digit_dict.keys())
if (start not in key_list) or (stop not in key_list): # start, stopがキーにあるかどうか
raise Exception("start and stop of slice must be in digit of int")
int_key_list = map(int, key_list) # 数値にしたキーのリスト
if start is None: # startがNoneの場合
start = str(min(int_key_list))
if stop is None: # stopがNoneの場合
stop = str(max(int_key_list))
if step is None: # stepがNoneの場合
step = 1
# 範囲内のキーの取得とソート
int_key_in_range = [int_key for int_key in int_key_list if (int(start) <= int_key) and (int_key < int(stop))]
key_in_range = [str(key) for key in sorted(int_key_in_range)]
for key in key_in_range[::step]:
yield self._getitem_key(key)
else:
raise TypeError("slice types are invalid")
2の条件を満たすには,特殊メソッド__iter__
を実装します.今回はリストを返しても問題ありませんが,取り出しの負荷が大きい場合を想定してジェネレーターを返しています.len
関数も使えるように特殊メソッド__len__
も実装しておきます.
def __iter__(self):
for index in range(len(self)):
yield self[index]
まとめ
リストのように
- インデックスにより要素を取り出せる
- for文に添えることができる
- スライスを利用できる
の条件を満たすオブジェクトを作成しました.