Source code for board.loggable

#!/usr/bin/env python3 
# coding: utf_8
# loggable.py 
"""ボードの基本機能を提供するモジュール.

* loggableモジュールは,ボードオブジェクトが,複合画像の木のノードとしてもつ最低限の機能を提供する.
* 複合画像を表すボードオブジェクトは木構造をなす.
* Loggableオブジェクトは,次の機能を提供する: 

	* 親子情報の管理
	* ログ出力の管理
"""
import sys
from typing import Any, NoReturn
import common as com
import kwargs as kw

#==========
# 補助クラス
#==========

[docs]class LoggableHolder: """Loggableにおける子のホルダー.子に加えて,補助情報を保持する子クラスを生成して利用する. Args: child (Loggable) : 保持する子の初期値.default=None. """ def __init__(self, child=None): self.set_child(child) return
[docs] def get_child(self): """ホルダーの子要素を設定する.子要素は`None`を許す. Returns: (Loggable) : ホルダーがもつ子要素 """ return self.child
[docs] def set_child(self, child=None): """ホルダーの子要素を設定し,自分自身を返す. 子要素`child`が`None`のときは,エラーを投げて終了する. Args: child (Loggable) : ホルダーに設定する子要素 Returns: (Loggable) : 自分自身.(cascading style API) """ if child == None or not isinstance(child, Loggable): com.panic(f'if child is not None, it must be Loggable: it={child}') self.child = child return self
pass ##class ChildHolder
#========== # 基底オブジェクトのクラス #==========
[docs]class Loggable: """ボードの最基底クラス. 複合画像を表すボードオブジェクトの木のノードがもつ最低限の機能を提供する. Args: dep_init (int) : 深さの初期値.default=None. tags (str, list(str)) : タグ文字列またはタグ文字列のリスト.タグ文字列のリストは,オブジェクトの属性`tags`に保持され,実装者と利用者により,各種の用途に用いることができる.default=None. verbose (bool) : デバッグ用出力のフラグ.default=False. **kwargs : 任意のオプション引数.オブジェクトの属性`kwargs`に保持されて,実装者と利用者により,各種の用途に用いることができる. Attributes: tags (str, list(str)) : タグ文字列またはタグ文字列のリスト.実装者と利用者により,各種の用途に用いることができる. kwargs (dict) : オプション引数の辞書.オブジェクトの属性`kwargs`に保持されて,実装者と利用者により,各種の用途に用いることができる. """ _freeLID : int = 0 #loggable id (lid) def __init__(self, dep_init=0, # dep_init=None, max_children=-1, tags=None, verbose=False, **kwargs): """ 生成するコンストラクタ. Args: depth (int): デバッグ用の入れ子の深さを表す非負整数.デフォールトdepth=0 verbose (bool): 実行情報を表示する **kwargs (dict) : 他のキーワード引数.上位クラスに渡される. Atributes : depth (int): デバッグ用のボード構成の入れ子の深さを表す非負整数. verbose (bool): 実行情報を表示する. id (int): ボードオブジェクトの連番.0で始まる. kwargs (dict) : キーワードを格納する辞書.キーワードは任意のものを使って良い.ボードの内部変数の階層化のためであり,関数`kw.get(kwargs, key)`を用いてアクセスする. """ ## 引数 self.depth = dep_init #dep_init can be None, which means `undefined`. self.verbose = verbose self.tags = None self.kwargs = kwargs ## 内部変数 self.parent : Loggable = None # 親オブジェクト self.id : int = Loggable._freeLID; Loggable._freeLID += 1 ## 子供 self.children_ = [] # 子のリスト self.ord_ = -1 # 子ID (親の子リスト中の自身の添字).外部アクセスに必要. self.max_children_ = max_children # 子数の上限(>=0).-1 means unbounded #タグ if tags != None: self.add_tag(tags) return #===== # キーワード辞書 #=====
[docs] def vars(self) -> dict: """自身のオブジェクトの属性からなる辞書を返す. その際,未定義(値がNone)の属性はスキップする. """ return kw.reduce(vars(self))
[docs] def myinfo(self, depth=False, ord=False, tag=False) -> str: """便利関数.ログ用に,Boardオブジェクトの情報の文字列を返す. """ bstr = self.__class__.__name__ if depth: bstr += f"[depth={ self.depth }]" if ord: bstr += f"[ord={ self.ord_ }]" if tag and self.tag: bstr += f"['{ self.tag }']" return bstr
#===== # 親子関係 #=====
[docs] def fetch(self, key=None, default=None, verbose=False) -> Any: """自分の先祖にフィールド`key`が定義されているボードがあれば,そのような直近の先祖での値を返す.もしそのような先祖がなければ,`default`が指定されていれば,その値を返し,それ以外ならば`None`を返す. Args: key (str) : 検索するキー文字列 default (Any) : キーをもつ先祖が見つからない時に返すデフォールト値 Returns: (Any) : キーをもつ最近先祖におけるキーの値 """ com.ensure(key != None, f'fetch: key={key} must not be None!') com.ensure(default != None, f'fetch: default={default} must not be None!') if verbose: self.repo(msg=f'@debug:fetch: {self.myinfo()}.fetch:'+ f' key={key} => {key in vars(self)}: against'+ f' vars={kw.extract(self.vars(), deleted=["children_"])}') if key in vars(self) and (vars(self)[key] != None): return vars(self)[key] elif self.parent == None or not (isinstance(self.parent, Loggable)): return default else: ## assuming isinstance(self.parent, Loggable) val_ = self.parent.fetch(key=key, default=default) if verbose: self.repo(msg=f'@debug:fetch: {self.myinfo()}.fetch => val={val_}') com.ensure(val_ != None, f'fetch: val={val_} must not be None!') return val_
#exp 親子関係の管理 def _register_child(self, child=None): """親子関係の管理. Args: child (Board) : 自身に追加する子.制約として,childは親をもってはいけない.すなわち,`child.parent != None`の場合は,エラーとなる. Note: 自身が既に子をもっていた場合は,エラーにならず,単に新しい子で上書きする.子が既に他の親を持っていた場合は,エラーを投げて強制終了する. """ # com.ensure(isinstance(child, Loggable), # f'child={child} must be a subclass of Loggable!') com.ensure_value(child, name='child', etype=Loggable, nullable=False) com.ensure(child.parent == None, f'parent is already defined! parent={child.parent}') ##親の属性verboseを子に引き継ぐ. child.verbose = child.verbose or self.verbose ##親ボードを設定 child.parent = self Loggable._adjust_depth(self, child) # 深さ調整 return def _adjust_depth(parent=None, child=None, verbose=None): """再帰的に,属性`depth`を調整し,自分と全ての子孫に正しく深さを設定する. Args: parent (Loggable) : 継ぎ木される親 child (Loggable) : 継ぎ木する子 Note: 次のように処理を行う: * 木 t が整合的な深さ番号づけをもつとは,(i) `$depth(t.root) \ge 0$`,かつ,(ii) 根以外のノード$v$に対して $PropDepth(v.parent, v) <=> depth(v) = depth(v.parent) + 1$が成立する. * 既存の親木と子木のすべては,整合的な深さ番号づけをもつと仮定する. * ノード$u$にノード$v$を継ぎ木する場合に,性質$PropDepth(u, v)$が成立すれば何もしない.成立しなければ,$depth(v) := depth(v.parent) + 1$を実行し,$v$の全ての子$w in v.children()$に対して,$PropDepth(v, w)$の成立を検査する. """ #print(f'{ "| "*dep }{ X.myinfo()} \tdepth={ X.depth }') com.ensure((parent != None and isinstance(parent, Loggable)), f'parent must be a Loggable!') com.ensure((child != None and isinstance(child, Loggable)), f'child must be a Loggable!') com.ensure((parent.depth != None and type(parent.depth) in (int, float)), f'parent.depth must be valid!: it={ parent.depth }') if child.depth == parent.depth + 1: pass else: #親から子へ深さを引き継ぐ child.depth = parent.depth + 1 #すべての子に対して,再帰的に処理を行う for value in child.children(): _, child1 = value Loggable._adjust_depth(parent=child, child=child1, verbose=verbose) return #===== # 子のリスト #===== # exp 基本:子を追加する
[docs] def append(self, pair=None) -> 'Loggable': """子ボードの対 pair (trans, child)を受け取り,子配列に追加する. * trans (cairo.Matrix) : 自座標における子の配置を指示する変換オブジェクト * child (Board): 子として追加するBoardオブジェクト Args: pair (tuple) : 変換と子ボードの対 (trans, child) Returns: (Board) : 追加した子 Example:: root = Canvas() child = root.put(Board()) child = parent.put(trans=Translate(dest=(1, 2)), Rectangle()) """ #子の型チェック com.ensure((pair != None and com.is_typeof_seq(pair, etype=None, dim=2)), ##etype=Noneは任意の型を許し,長さ=2のみテストする f'{self.myinfo()}.put(): pair={pair} must be a pair of trans and child board!') trans, child = pair #子の型チェック com.ensure(child != None and isinstance(child, Loggable), f'{self.myinfo()}.put(): child must be a Loggable!: {child}') # 親子関係の管理 self._register_child(child) # 子を追加する if self.max_children_ >= 0 and len(self.children_) >= self.max_children_: com.panic(f'cannot add more than max_children_={self.max_children_}!') else: child.ord_ = len(self.children_) #子ID self.children_.append(pair) #子リスト #ログ if self.verbose: self.repo(msg=f'=> added: {self.myinfo()}.put(): trans={trans} child={ child } with vars={ child.vars() }...') return child #Do not change!
# exp 基本:子を追加する
[docs] def set_child_by_idx(self, idx=None, pair=None) -> 'Loggable': """添字 idx と変換と子ボードの対 pair (trans, child)を受け取り, 子配列のidxセルにpairを代入する. 添字は,下記の例のように元の配列から取り出したものに限る. * trans (cairo.Matrix) : 自座標における子の配置を指示する変換オブジェクト * child (Board): 子として追加するBoardオブジェクト Args: idx (int) : 子配列の添字 pair (tuple) : 変換と子ボードの対 (trans, child) Example:: for idx, value in parent: trans, child = value trans1, child1 = modifing(trans, child) parent.set(idx, trans=trans1, child1) """ #子の型チェック com.ensure((pair != None and com.is_typeof_seq(pair, etype=None, dim=2)), ##etype=Noneは任意の型を許し,長さ=2のみテストする f'{self.myinfo()}.put(): pair={pair} must be a pair of trans and child board!') trans, child = pair # com.ensure(trans != None and isinstance(trans, GeoTransform), # f'{self.myinfo()}.put(): trans must be a GeoTransform!: {trans}') com.ensure(child != None and isinstance(child, Loggable), f'{self.myinfo()}.put(): child must be a Loggable!: {child}') # 親子関係の管理 child.parent = None #親への参照を来る self._register_child(child) # 子を追加する com.ensure(idx < len(self.children_), f'{self.myinfo()}.set(): index={idx} >= num of children={len(self.children_)}!') child.ord_ = idx #子ID self.children_[idx] = (trans, child) #子リスト. 上書きなので注意! #ログ if self.verbose: self.repo(msg=f'overwrite =>{self.myinfo()}.set(): idx={idx} trans={trans} child={ child } with vars={ child.vars() }...') return child
##Override
[docs] def children(self) -> list: """子エントリの並び children を返す.ここに,子エントリvalue = (trans, child) は,変換 trans と子オブジェクトchildの対である. Returns: list: 子エントリvalue = (trans, child) のリスト """ return self.children_ ##ダミー:子クラスでOverrideすること
# idx = 0 # for trans, child in self.children_: # #子の型チェック # com.ensure(isinstance(child, BoardBase), # 'child must be a subclass of BoardBase!: {child}') # child_box = child.get_box() # triple = trans, child, child_box # yield idx, triple # idx += 1 ##Override
[docs] def children_enumerated(self) -> list: """添字と子エントリの対`idx, value`の並び children を返す. 現在は,`trans, child = value`である.部分クラスで上書きする. Returns: list: 子オブジェクトのリスト Notes : 読み出し`idx, value = children_enumerated()`と書き込み`children_replace(idx, value)`を対にして用いる. """ return enumerate(self.children_) ##ダミー:子クラスでOverrideすること
#===== # タグ #=====
[docs] def get_tags(self) -> list: """タグ文字列のリストを返す. Returns: (list(str)) : タグ文字列のリスト """ return self.tags
[docs] def add_tag(self, tags) -> 'Loggable': """タグ文字列を追加する. Args: tags (str) : タグ文字列.任意のグルーピングに用いる. Returns: (Loggable) : 自分自身.いわゆる'cascade object call interface' のため. """ if self.tags == None: self.tags = [] if isinstance(tags, str): self.tags.append(tags) elif com.is_typeof_seq(tags, etype=str): for a_tag in tags: if not (a_tag in self.tags): self.tags.append(a_tag) else: panic(f'tags={tags} must have as type str or list(str))!: {type(tags)}') return self #for cascade object interface
#===== # ログ出力 #=====
[docs] def repo(self, msg=None, header=True, is_child=False, file=sys.stderr): """入れ子深さを考慮したレポート文字列を出力する. Args: dep=0 (int): 入れ小深さ msg=None (str): 出力する文字列 header=True (str): 出力するヘッダー文字列.ふつうは関数名 'methodname' file (outstream): 出力ストリーム.デフォールトsys.stderr Examples:: self.repo(depth, msg=f'.send_draw: cmd={cmd} kwargs={kwargs1}') """ if not isinstance(msg, str): print(f'warning: common.repo: msg="{msg} (type={type(msg)})" must be str!') if self.depth != None: dep_ = self.depth else: dep_ = 0 if is_child: dep_ += 1 if not (isinstance(dep_, int)): print(f'warning: common.py.repo: dep_={ dep_ } must be of int type!') h=f'{ "| " * dep_ }' #入れ小深さに対応する柱文字列を作成 if header: h += f'@{ self.myinfo() }' if len(msg) >= 1 and not (msg[0] in ('.', '_')): h += ': ' print(f'{h}{msg}', file=file)
##===== ## 表示 ##=====
[docs] def dump(self, dep=0, file=sys.stdout): """Loggableオブジェクトの構造を印刷する. Args: B (Loggable) : ボードオブジェクトの根 """ print(f'{ "| "*dep }{ self.myinfo()}\t'+ f' depth={ self.depth }'+f' ord_={ self.ord_ }' f' verbose={ self.verbose }', end='', file=file) if self.get_tags() != None: print(f' tags={self.get_tags()}', end='', file=file) print('', file=file) for idx, pair in self.children_enumerated(): trans, child = pair #分解 if isinstance(child, Loggable): child.dump(dep=dep+1, file=file) return
pass
## EOF