Python(listを使わないTableクラスの拡張)
こちらは以前投稿した
Python(Tableクラスの継承その1)
Python(Tableクラスの継承その2)
の関連投稿になります。
以前の投稿では継承先のクラスにTableのデータリストを二次元リストとして保管し、このリストを基に基底クラスの辞書内データを更新する方法を行っていました。
今回はリストでの書き換えをやめ、基底クラスの辞書データを書き換える方法で拡張をしています。
class Table:
def __init__(self,xss=[[]]):
self.__empty_type = ( None,[],(),{},"" )
"""リストを表として整形する
@param xss 表示したい2次元リスト(行の要素ごとをリストとする)"""
if not all(isinstance(xs,list) for xs in xss):
Table.err("要素がリスト型ではありません")
self.__axis_y = len(xss)
self.__axis_x = max(len(xs) for xs in xss) if xss else 0
tmp_dic = {}
if xss != [[]]:
for _y,xs in enumerate(xss):
for _x,x in enumerate(xs):
if not x in self.__empty_type:
tmp_dic[_y,_x] = x
self.__table_dict = tmp_dic
@property
def get_max_row(self):
return self.__axis_y
@property
def get_max_column(self):
return self.__axis_x
@property
def get_dict(self):
return self.__table_dict
def _validate_indexes(self, index, strict=False):
try:
_y,_x = index
except:
raise IndexError(f"{index}は不正なインデックスです")
self._validate_axis_x_index(_x, strict)
self._validate_axis_y_index(_y, strict)
def _validate_axis_x_index(self, i, strict):
Table.__validate_idx(i, self.__axis_x, strict)
def _validate_axis_y_index(self, i, strict):
Table.__validate_idx(i, self.__axis_y, strict)
@staticmethod
def __validate_idx(idx, maxnum, strict):
"""要素へアクセスするインデックスが利用可能かどうか調べる
@param idx 辞書の要素へアクセスするインデックス
@param max 登録されている要素の最大値
@param strict 最大値の確認をするかどうかの真偽値"""
if not isinstance(idx, int):
raise IndexError(f"{idx}: インデックスは整数でなければなりません")
if idx < 0 or (strict and idx >= maxnum):
raise IndexError("out of range")
def __make_string(self):
"""辞書に登録されている要素から表の文字列を作成する関数
@param d 2次元配列の座標をタプルのキーとする辞書
@return 辞書の座標に要素を入れて作成した表の文字列"""
def check(x,y):
"""辞書内の要素をチェックして値を返す関数
@param x 座標x
@param y 座標y
@return 該当する要素があればその値の文字列、
なければ' '(空白1つ)の文字列"""
if (y,x) in self.__table_dict:
if self.__table_dict[y,x] in self.__empty_type:
return " "
else:
return str(self.__table_dict[y,x])
else:
return " "
tmp_cell_list = list(self.__table_dict.keys())
if len(tmp_cell_list)==0:
return "++\n++"
tmp_list=[[check(x,y) for x in range(self.__axis_x)] for y in range(self.__axis_y)]
tmp_ps_list = [[len(tmp_list[y][x]) for y in range(self.__axis_y)]
for x in range(self.__axis_x)]
ps=[max(i) for i in tmp_ps_list]
str_line = Table.separator(("-" * i for i in ps),"+")
str_table_list = [Table.separator(("%*s"%v for v in zip(ps,tmp_list[n])),"|")
for n in range(self.__axis_y)]
return Table.join_with_newline(str_line, str_table_list)
@staticmethod
def err(message=""):
raise SyntaxError(message)
@staticmethod
def separator(s,sep):
"""リスト内の文字列を指定した文字列で区切り、結合する関数
@param s 文字列
@param sep 区切りに使用する文字列
@return 指定した文字列で区切り、要素を結合した文字列"""
return sep + sep.join(s) + sep
@staticmethod
def join_with_newline(delim, strs):
"""リスト内の文字列を指定した文字列で区切り、結合する関数
@param delim 上段に付足す文字列
@param strs 文字列のリスト
@return 文字列を上段に追加し、要素を結合した文字列"""
d = delim + '\n'
return d + d.join(s+'\n' for s in strs) + delim
def replace_dict(self,dic):
tmp_y,tmp_x = 0,0
for y,x in dic.keys():
tmp_y = max(y, tmp_y)
tmp_x = max(x, tmp_x)
self.__axis_y, self.__axis_x = tmp_y+1, tmp_x+1
self.__table_dict = dic
def __str__(self):
return self.__make_string()
def __contains__(self,value):
return value in self.__table_dict.values()
def __getitem__(self,index):
self._validate_indexes(index, True)
if index in self.__table_dict:
return self.__table_dict[index]
def __setitem__(self,index,value):
self._validate_indexes(index)
_y,_x = index
self.__axis_y = max(_y+1,self.__axis_y)
self.__axis_x = max(_x+1,self.__axis_x)
self.__table_dict[index] = value
基底クラスの変数に__(アンダーバー2つ)を付けて継承先からも隠蔽したため、@propertyのデコレータを付けて取得できるようにしています。
また、replace_dict(self,dic)メソッドを追加し、引数の辞書から自身の保管している変数を更新するようにしました。
from basetable02 import Table
class ExTable(Table):
def __init__(self,xss=[[]],*,type=object):
Table.__init__(self,xss)
if not all(all(isinstance(x,type) for x in xs if x != None) for xs in xss):
raise ValueError(f"データ型が{type}ではないデータが含まれています")
self.__type = type
@property
def get_type(self):
return self.__type
def get_list(self):
"""親Tableクラスのリストから2次元配列を得る関数"""
def check(x,y):
"""辞書内の要素をチェックして値を返す関数
@param x 座標x
@param y 座標y
@return 該当する要素があればその値、なければNone"""
if (y,x) in self.get_dict:
return self.get_dict[y,x]
return [[check(x,y) for x in range(self.get_max_column)]
for y in range(self.get_max_row)]
def erase_column(self,column):
self._validate_axis_x_index(column,True)
tmp_dic = {(y,x-1 if x>column else x):value
for ((y,x),value) in self.get_dict.items()
if x != column}
self.replace_dict(tmp_dic)
def erase_row(self,row):
self._validate_axis_y_index(row,True)
tmp_dic = {(y-1 if y>row else y,x):value
for ((y,x),value) in self.get_dict.items()
if y != row}
self.replace_dict(tmp_dic)
def insert_column(self, column):
self._validate_axis_x_index(column,True)
tmp_dic = {(y,x+1 if x>=column else x):value
for ((y,x),value) in self.get_dict.items()}
self.replace_dict(tmp_dic)
def insert_row(self, row):
self._validate_axis_y_index(row,True)
tmp_dic = {(y+1 if y>=row else y,x):value
for ((y,x),value) in self.get_dict.items()}
self.replace_dict(tmp_dic)
def transpose(self):
tmp_dic = {(j,i):value for (i,j),value in self.get_dict.items()}
self.replace_dict(tmp_dic)
def __setitem__(self, index, value):
if not isinstance(value,self.__type):
raise ValueError(f"データ型が{self.__type}ではありません")
super().__setitem__(index, value)
継承先では基底クラスの辞書をコピーし、行や列を辞書内包表記で書き換え、コピーした辞書で基のデータを更新するために、基底クラスで定義したreplace_dictメソッドを呼ぶようにしました。
しかし、この場合、セッターに可変の辞書を返すようにしたため、ゲッターがなくても内部要素にアクセスして書き換えが可能です。(もともと特殊メソッドで辞書の要素を追加、書き換えは可能)
一応、変数を隠蔽させたので継承先で変数にアクセスさせずに辞書の要素を変化させるようにmapメソッドを基底クラスに持たせ、変数のゲッターはなくす形で実装したのが以下のコードになります。
class Table:
def __init__(self,xss=[[]]):
self.__empty_type = ( None,[],(),{},"" )
"""リストを表として整形する
@param xss 表示したい2次元リスト(行の要素ごとをリストとする)"""
if not all(isinstance(xs,list) for xs in xss):
Table.err("要素がリスト型ではありません")
self.__axis_y = len(xss)
self.__axis_x = max(len(xs) for xs in xss) if xss else 0
tmp_dic = {}
if xss != [[]]:
for _y,xs in enumerate(xss):
for _x,x in enumerate(xs):
if not x in self.__empty_type:
tmp_dic[_y,_x] = x
self.__table_dict = tmp_dic
def _validate_indexes(self, index, strict=False):
try:
_y,_x = index
except:
raise IndexError(f"{index}は不正なインデックスです")
self._validate_axis_x_index(_x, strict)
self._validate_axis_y_index(_y, strict)
def _validate_axis_x_index(self, i, strict):
Table.__validate_idx(i, self.__axis_x, strict)
def _validate_axis_y_index(self, i, strict):
Table.__validate_idx(i, self.__axis_y, strict)
@staticmethod
def __validate_idx(idx, maxnum, strict):
"""要素へアクセスするインデックスが利用可能かどうか調べる
@param idx 辞書の要素へアクセスするインデックス
@param max 登録されている要素の最大値
@param strict 最大値の確認をするかどうかの真偽値"""
if not isinstance(idx, int):
raise IndexError(f"{idx}: インデックスは整数でなければなりません")
if idx < 0 or (strict and idx >= maxnum):
raise IndexError("out of range")
def __make_string(self):
"""辞書に登録されている要素から表の文字列を作成する関数
@param d 2次元配列の座標をタプルのキーとする辞書
@return 辞書の座標に要素を入れて作成した表の文字列"""
def check(x,y):
"""辞書内の要素をチェックして値を返す関数
@param x 座標x
@param y 座標y
@return 該当する要素があればその値の文字列、
なければ' '(空白1つ)の文字列"""
if (y,x) in self.__table_dict:
if self.__table_dict[y,x] in self.__empty_type:
return " "
else:
return str(self.__table_dict[y,x])
else:
return " "
tmp_cell_list = list(self.__table_dict.keys())
if len(tmp_cell_list)==0:
return "++\n++"
tmp_list=[[check(x,y) for x in range(self.__axis_x)] for y in range(self.__axis_y)]
tmp_ps_list = [[len(tmp_list[y][x]) for y in range(self.__axis_y)]
for x in range(self.__axis_x)]
ps=[max(i) for i in tmp_ps_list]
str_line = Table.separator(("-" * i for i in ps),"+")
str_table_list = [Table.separator(("%*s"%v for v in zip(ps,tmp_list[n])),"|")
for n in range(self.__axis_y)]
return Table.join_with_newline(str_line, str_table_list)
@staticmethod
def err(message=""):
raise SyntaxError(message)
@staticmethod
def separator(s,sep):
"""リスト内の文字列を指定した文字列で区切り、結合する関数
@param s 文字列
@param sep 区切りに使用する文字列
@return 指定した文字列で区切り、要素を結合した文字列"""
return sep + sep.join(s) + sep
@staticmethod
def join_with_newline(delim, strs):
"""リスト内の文字列を指定した文字列で区切り、結合する関数
@param delim 上段に付足す文字列
@param strs 文字列のリスト
@return 文字列を上段に追加し、要素を結合した文字列"""
d = delim + '\n'
return d + d.join(s+'\n' for s in strs) + delim
def map(self,func):
tmp_dict = dict()
tmp_y, tmp_x = 0,0
for ((i,j),x) in self.__table_dict.items():
result = func(i,j,x)
if result is not None:
i,j,x = result
idx = i,j
self._validate_indexes(idx)
tmp_y = max(i+1, tmp_y)
tmp_x = max(j+1, tmp_x)
tmp_dict[idx] = x
self.__axis_y, self.__axis_x = tmp_y, tmp_x
self.__table_dict = tmp_dict
def replace_dict(self,dic):
tmp_y,tmp_x = 0,0
for y,x in dic.keys():
tmp_y = max(y, tmp_y)
tmp_x = max(x, tmp_x)
self.__axis_y, self.__axis_x = tmp_y+1, tmp_x+1
self.__table_dict = dic
def get_list(self):
"""親Tableクラスのリストから2次元配列を得る関数"""
def check(x,y):
"""辞書内の要素をチェックして値を返す関数
@param x 座標x
@param y 座標y
@return 該当する要素があればその値、なければNone"""
if (y,x) in self.__table_dict:
return self.__table_dict[y,x]
return [[check(x,y) for x in range(self.__axis_x)]
for y in range(self.__axis_y)]
def __str__(self):
return self.__make_string()
def __contains__(self,value):
return value in self.__table_dict.values()
def __getitem__(self,index):
self._validate_indexes(index, True)
if index in self.__table_dict:
return self.__table_dict[index]
def __setitem__(self,index,value):
self._validate_indexes(index)
_y,_x = index
self.__axis_y = max(_y+1,self.__axis_y)
self.__axis_x = max(_x+1,self.__axis_x)
self.__table_dict[index] = value
継承先から辞書の要素を取得できなくなったので、get_listメソッドは基底クラスに持たせるようにしました。
from basetable03 import Table
class ExTable(Table):
def __init__(self,xss=[[]],*,type=object):
Table.__init__(self,xss)
if not all(all(isinstance(x,type) for x in xs if x != None) for xs in xss):
raise ValueError(f"データ型が{type}ではないデータが含まれています")
self.__type = type
@property
def get_type(self):
return self.__type
def erase_column(self,column):
self._validate_axis_x_index(column,True)
def f(y,x,value):
if x != column:
if x>column:
tmp = x-1
return y,tmp,value
else:
return y,x,value
self.map(f)
def erase_row(self,row):
self._validate_axis_y_index(row,True)
def f(y,x,value):
if y != row:
if y>row:
tmp = y-1
return tmp,x,value
else:
return y,x,value
self.map(f)
def insert_column(self, column):
self._validate_axis_x_index(column,True)
def f(y,x,value):
if x>=column:
tmp = x+1
return y,tmp,value
else:
return y,x,value
self.map(f)
def insert_row(self, row):
self._validate_axis_y_index(row,True)
def f(y,x,value):
if y>=row:
tmp = y+1
return tmp,x,value
else:
return y,x,value
self.map(f)
def transpose(self):
def f(y,x,value):
return x,y,value
self.map(f)
def __setitem__(self, index, value):
if not isinstance(value,self.__type):
raise ValueError(f"データ型が{self.__type}ではありません")
super().__setitem__(index, value)
継承先ではmapメソッドに関数オブジェクトを渡すために、各メソッドで内部関数を定義して引数に渡すようにしました。
これで、継承先からは隠蔽した要素を使うことなく、同様の機能を実装することができました。
このクラス設計について元ネタは以下になります。
Tableクラスの基本仕様
Tableクラス 改善編