Python(Tableクラスの継承その2)

この記事は前回投稿分 Python(Tableクラスの継承その1) の続きになりますので、まずはそちらを確認してください。

それでは仕様(1)特定の行や列を削除するメソッドについて

def erase_column(self,column):
      self._validate_axis_x_index(column,True)
        for xs in self._table_list:
            xs.remove(xs[column])
        self.reset_dict(self._table_list)
        self.get_list()
        self.reset_dict(self._table_list)

    def erase_row(self,row):
        self._validate_axis_y_index(row,True)
        self._table_list.remove(self._table_list[row])        
        self.reset_dict(self._table_list)
        self.get_list()
        self.reset_dict(self._table_list)

column(縦の列)とrow(横の行)を削除するメッソドでは、それぞれ親クラスのTableクラスが持つ_validate_axis_○_indexメソッドを利用して、x軸(横)or y軸(縦)の範囲外を消そうとしていないかチェックします。
有効な引数であれば、子クラスのExTableクラスが管理している_table_listの2次元配列で該当する箇所を削除し、それを再度Tableクラスの辞書に登録し直すためにreset_dictメソッドで変更した2次元配列を引数にして更新処理を行っています。

def reset_dict(self,xss):
        if all(all(x == None for x in xs) for xs in self._table_list):
            self._table_list.clear()
        Table.__init__(self,self._table_list)

reset_dictメソッド は渡された2次元配列の要素が全てNoneでないかチェックした後に、Tableクラスのコンストラクタを呼んで辞書を更新しています。
また、2次元配列の要素が全てNoneとして登録されているだけの場合であれば、辞書に値を登録する必要がないので、clearメソッドで削除した空のリストで辞書をリセットしています。

その下でself.get_list()とself.reset_dict(self._table_list)の記述が、あるのは変だと思われる方も多いかと思いますが、これはExTableクラスで2次元配列の管理を、Tableクラスで辞書の管理をしている弊害の部分になります。
get_list() メソッドはTableクラスの辞書を元に2次元配列を作成していますが、行や列の削除で2次元配列が書き換わっても、この段階で登録されている辞書は以前の状態のままなので、 reset_dict メソッドを呼び、まずは変更を辞書に登録する必要があります。
しかし、この段階では不要な行や列があったとしても辞書にはNoneとして保存されるために出力すると空白の行と列ができてしまいます。
そこで、get_list() メソッドを呼び、登録されている辞書で値がNoneのものを削除した後に、再度、2次元配列を生成し、それを元に辞書の上書きをしてあげることで、不要な行や列をなくして出力できるようにしています。
※ この部分は親のTableクラスで2次元配列の管理も行うようにすれば、よりスマートな設計になりますが、元となるクラスに必要最小限のものは何か、どのような拡張を今後行うのかによって設計し直す必要がありそうです。

def transpose(self):
        tmp_dict={}
        for index,value in self._table_dict.items():
            _y,_x = index
            self._axis_y = max(_x+1,self._axis_y)
            self._axis_x = max(_y+1,self._axis_x)
            trans = _x,_y
            tmp_dict[trans] = value
        self._table_dict = tmp_dict
        self.get_list()

仕様(3)転置は辞書のインデックスを入れ替えた辞書で元の辞書を上書きし、範囲の更新と2次元配列の更新を行うことで実装しました。

2019/11/06追記
for文でswapさせていた部分を辞書内包表記で記述すると1文にまとめることができます。

def transpose(self):
        self._table_dict = {(j,i):value for (i,j),value in self._table_dict.items()}       
        self.get_list()

ExTableクラスの全ソースは以下のようになります。

from basetable01 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
        self.get_list()

    def reset_dict(self,xss):
        if all(all(x == None for x in xs) for xs in self._table_list):
            self._table_list.clear()
        Table.__init__(self,self._table_list)

    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]                    

        tmp_dict={}
        for index,value in self._table_dict.items():
            if value != None:
                tmp_dict[index] = value            
        self._table_dict = tmp_dict

        tmp_cell_list = list(self._table_dict.keys())
        n = range(len(tmp_cell_list))           
        tmp_y,tmp_x = 0,0
        for i in n:
            if tmp_y < tmp_cell_list[i][0]:
                tmp_y = tmp_cell_list[i][0]
            if tmp_x < tmp_cell_list[i][1]:
                tmp_x = tmp_cell_list[i][1]
                
        self._axis_x, self._axis_y = tmp_x+1, tmp_y+1
        self._table_list = [[check(x,y) for x in range(self._axis_x)]\
             for y in range(self._axis_y)]        
        return self._table_list

    def erase_column(self,column):
        self._validate_axis_x_index(column,True)
        for xs in self._table_list:
            xs.remove(xs[column])
        self.reset_dict(self._table_list)
        self.get_list()
        self.reset_dict(self._table_list)

    def erase_row(self,row):
        self._validate_axis_y_index(row,True)
        self._table_list.remove(self._table_list[row])        
        self.reset_dict(self._table_list)
        self.get_list()
        self.reset_dict(self._table_list)
        
    def transpose(self):
        tmp_dict={}
        for index,value in self._table_dict.items():
            _y,_x = index
            self._axis_y = max(_x+1,self._axis_y)
            self._axis_x = max(_y+1,self._axis_x)
            trans = _x,_y
            tmp_dict[trans] = value
        self._table_dict = tmp_dict
        self.get_list()
        
    def __setitem__(self, index, value):
        if not isinstance(value,self._type):
            raise ValueError(f"データ型が{self._type}ではありません")        
        super().__setitem__(index, value)
        self.get_list()

継承は非常に便利ですが、クラス設計を行う場合、基底クラスとして何が必要なのかを考え、必要十分かつ、基底クラスが巨大にならないように考えながらクラス設計に慣れていきたいと思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

This site uses Akismet to reduce spam. Learn how your comment data is processed.