kevin-toolbox-dev 1.4.9__py3-none-any.whl → 1.4.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. kevin_toolbox/__init__.py +2 -2
  2. kevin_toolbox/computer_science/algorithm/cache_manager/__init__.py +1 -0
  3. kevin_toolbox/computer_science/algorithm/cache_manager/cache/cache_base.py +10 -0
  4. kevin_toolbox/computer_science/algorithm/cache_manager/cache/memo_cache.py +17 -0
  5. kevin_toolbox/computer_science/algorithm/cache_manager/cache_manager.py +24 -74
  6. kevin_toolbox/computer_science/algorithm/cache_manager/cache_manager_wto_strategy.py +157 -0
  7. kevin_toolbox/computer_science/algorithm/for_seq/__init__.py +1 -0
  8. kevin_toolbox/computer_science/algorithm/for_seq/sample_subset_most_evenly.py +50 -0
  9. kevin_toolbox/computer_science/algorithm/redirector/__init__.py +1 -0
  10. kevin_toolbox/computer_science/algorithm/redirector/redirectable_sequence_fetcher.py +258 -0
  11. kevin_toolbox/computer_science/algorithm/sampler/__init__.py +1 -0
  12. kevin_toolbox/computer_science/algorithm/sampler/reservoir_sampler.py +116 -0
  13. kevin_toolbox/computer_science/algorithm/statistician/accumulator_base.py +1 -1
  14. kevin_toolbox/data_flow/file/kevin_notation/kevin_notation_reader.py +18 -7
  15. kevin_toolbox/data_flow/file/kevin_notation/kevin_notation_writer.py +8 -1
  16. kevin_toolbox/data_flow/file/kevin_notation/test/test_data/data_0.py +2 -1
  17. kevin_toolbox/data_flow/file/kevin_notation/test/test_kevin_notation.py +1 -4
  18. kevin_toolbox/data_flow/file/kevin_notation/test/test_kevin_notation_debug.py +16 -4
  19. kevin_toolbox/data_flow/file/markdown/table/get_format.py +3 -0
  20. kevin_toolbox/patches/for_logging/build_logger.py +3 -1
  21. kevin_toolbox_dev-1.4.10.dist-info/METADATA +106 -0
  22. {kevin_toolbox_dev-1.4.9.dist-info → kevin_toolbox_dev-1.4.10.dist-info}/RECORD +24 -18
  23. kevin_toolbox_dev-1.4.9.dist-info/METADATA +0 -75
  24. {kevin_toolbox_dev-1.4.9.dist-info → kevin_toolbox_dev-1.4.10.dist-info}/WHEEL +0 -0
  25. {kevin_toolbox_dev-1.4.9.dist-info → kevin_toolbox_dev-1.4.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,258 @@
1
+ import random
2
+ from kevin_toolbox.patches.for_logging import build_logger
3
+ from kevin_toolbox.patches.for_numpy.random import get_rng, set_rng_state, get_rng_state
4
+ from kevin_toolbox.computer_science.algorithm.cache_manager import Cache_Manager, Cache_Manager_wto_Strategy
5
+
6
+
7
+ def _randomly_idx_redirector(idx, seq_len, attempts, rng, *args):
8
+ if idx == 0:
9
+ return rng.randint(1, seq_len - 1)
10
+ elif idx == seq_len - 1:
11
+ return rng.randint(0, seq_len - 2)
12
+ else:
13
+ return rng.choices([rng.randint(0, idx - 1), rng.randint(idx + 1, seq_len - 1)],
14
+ weights=[idx, seq_len - idx - 1])[0]
15
+
16
+
17
+ idx_redirector_s = {
18
+ "decrease": lambda idx, *args: idx - 1,
19
+ "increase": lambda idx, *args: idx + 1,
20
+ "randomly": _randomly_idx_redirector,
21
+ }
22
+
23
+ EMPTY = object()
24
+
25
+
26
+ def _round_idx(idx, st, ed):
27
+ if idx < st or idx >= ed:
28
+ idx = (idx - st) % (ed - st) + st
29
+ assert st <= idx < ed
30
+ return idx
31
+
32
+
33
+ class Redirectable_Sequence_Fetcher:
34
+ """
35
+ 用于从给定 seq 中获取元素,通过跳转来处理获取失败的情况
36
+
37
+ 功能描述:
38
+ 1. 对于给定的索引 idx,若能通过 seq(idx) 成功获取,则直接返回获取的结果。
39
+ 2. 若不能成功获取,则会根据给定的规则修改索引(如idx-1)重新尝试获取,递归调用直至获取成功或者递归调用次数达到上限。
40
+ 2.a 若开启了跳转记忆功能,则会为获取失败的 idx 记录其最终重定向到的新的 idx,以及其获取失败的次数。
41
+ 当失败次数达到上限后,则不再进行尝试并直接返回重新向后的新的 idx 的结果。
42
+ 若在此过程中原来失败的 idx 又能再次获取成功,则将失败次数减1,直至归零并删除该记录。
43
+ 3. 若递归次数达到上限,则进行报错或者返回给定的默认值。
44
+ 3.a 若开启了跳转记忆功能,在重试过程中,一旦某次调用成功,记录原始索引与最终有效索引之间的映射关系。
45
+
46
+ 使用建议:
47
+ - 数据读取或模型训练过程中,当某些外部因素导致部分索引数据获取失败时,自动进行索引跳转和重试,从而保证整个流程的鲁棒性和连续性。
48
+ """
49
+
50
+ def __init__(self, **kwargs):
51
+ """
52
+ 参数:
53
+ seq: <callable> 元素获取器。
54
+ 要求能通过 seq(idx) 或者 seq[idx] 返回元素。
55
+ value_checker: <callable> 元素检查器。
56
+ 形如 func(v) ==> boolean 的函数,当返回 True 时表示成功获取。
57
+ 默认为 None,不对元素进行检查。
58
+ seq_len: <int> 序列长度。
59
+ 默认不指定,将尝试通过 len(seq) 获取。
60
+ idx_redirector: <str/callable> 对 idx 进行重定向的方式。
61
+ 形如 func(idx, seq_len, attempts, rng) ==> new_idx 的函数,
62
+ 其中 attempts 是已进行重定向的次数,rng是随机生成器。
63
+ 当设定为 str 时,则使用默认的函数。目前支持以下选项:
64
+ - "decrease": new_idx=idx-1
65
+ - "increase": new_idx=idx+1
66
+ - "randomly": 随机跳转(默认)
67
+ redirect_max_attempts: <int> 进行重定向的次数上限。
68
+ 默认为 3。
69
+ default_value: <any> 重定向失败时返回的值。
70
+ 默认不指定,此时重定向失败后将引发报错。
71
+ 所谓重定向失败,就是在进行 redirect_max_attempts 次重定向后仍然无法成功获取值。
72
+ memory: <int/Cache_Manager> 跳转记忆器。
73
+ 当给定值为 int 时,将以该值为 upper_bound 构建 Cache_Manager,
74
+ 特别地,当设定为 -1 时,表示容量无上限。
75
+ 默认为 None,表示不使用记忆器。
76
+ use_memory_after_failures: <int> 在获取失败多少次后(failures计数+1大于该值后)将不再尝试获取而直接使用记忆。
77
+ 默认为 3。
78
+ 当设置为 None 时,表示从不使用记忆。
79
+ memory_decay_rate: <float> failures 计数衰减的速度。
80
+ 建议使用 0~1 之间的值。
81
+ 默认为 0.1,表示每直接使用一次记忆,则对 failures 计算减去 0.1
82
+ logger: <str/Logger> 用于记录每次发生的重定向行为。
83
+ 若为 dict,则需要包含 "target", "level", "formatter" 等键值对。
84
+ 若为 str,则会自动构建一个以该值为 target 的记录器。
85
+ 具体可以参见 for_logging.build_logger()
86
+ 默认为 None,表示不需要进行记录。
87
+ seed: <int> 随机种子
88
+ """
89
+ # 默认参数
90
+ paras = {
91
+ "seq": None,
92
+ "value_checker": None,
93
+ "idx_redirector": "randomly",
94
+ "memory": None,
95
+ #
96
+ "seq_len": None,
97
+ "redirect_max_attempts": 3,
98
+ "default_value": EMPTY,
99
+ "use_memory_after_failures": 3,
100
+ "memory_decay_rate": 0.1,
101
+ "logger": None,
102
+ "seed": 114514
103
+ }
104
+
105
+ # 获取参数
106
+ paras.update(kwargs)
107
+
108
+ # 校验参数
109
+ if paras["seq_len"] is None:
110
+ assert hasattr(paras["seq"], "__len__"), "cannot infer the range of idx from seq"
111
+ paras["seq_len"] = len(paras["seq"])
112
+ assert paras["seq_len"] >= 0
113
+ self.seq = paras["seq"]
114
+ if hasattr(paras["seq"], "__getitem__"):
115
+ self.seq = lambda idx: paras["seq"][idx]
116
+ assert callable(self.seq)
117
+ assert paras["value_checker"] is None or callable(paras["value_checker"])
118
+ self.value_checker = paras["value_checker"]
119
+ assert paras["redirect_max_attempts"] >= 0
120
+ #
121
+ self.idx_redirector = idx_redirector_s[paras[
122
+ "idx_redirector"]] if paras["idx_redirector"] in idx_redirector_s else paras["idx_redirector"]
123
+ assert callable(self.idx_redirector)
124
+ #
125
+ self.memory = paras["memory"]
126
+ if paras["memory"] is not None:
127
+ if isinstance(paras["memory"], int):
128
+ self.memory = Cache_Manager(upper_bound=paras["memory"]
129
+ ) if paras["memory"] > 0 else Cache_Manager_wto_Strategy()
130
+ assert isinstance(self.memory, (Cache_Manager_wto_Strategy,))
131
+ #
132
+ self.logger = paras["logger"]
133
+ if paras["logger"] is not None:
134
+ if isinstance(paras["logger"], str):
135
+ paras["logger"] = dict(target=paras["logger"])
136
+ if isinstance(paras["logger"], dict):
137
+ paras["logger"].setdefault("level", "INFO")
138
+ self.logger = build_logger(name=f':Redirectable_Sequence_Fetcher:{id(self)}',
139
+ handler_ls=[paras["logger"]], )
140
+ #
141
+ self.rng = get_rng(seed=paras["seed"], rng=None)
142
+
143
+ self.paras = paras
144
+
145
+ def fetch(self, idx):
146
+ b_success = False
147
+ error = None
148
+ res = None
149
+ try:
150
+ res = self.seq(idx)
151
+ b_success = True
152
+ except Exception as e:
153
+ error = e
154
+ if self.value_checker is not None and b_success:
155
+ b_success = self.value_checker(res)
156
+ if not b_success:
157
+ error = ValueError(f"value checker failed for idx={idx}")
158
+ return res, b_success, error
159
+
160
+ def redirectable_fetch(self, idx):
161
+ attempts = 0
162
+ new_idx = idx
163
+ old_idx = idx
164
+
165
+ # 尝试从 memory 中获取 new_idx
166
+ b_use_memory = False
167
+ if self.memory is not None and self.memory.has(key=idx):
168
+ v_s = self.memory.get(key=idx)
169
+ if "failures" in v_s and v_s["failures"] + 1 > self.paras["use_memory_after_failures"]:
170
+ v_s["failures"] -= self.paras["memory_decay_rate"]
171
+ attempts = self.paras["redirect_max_attempts"]
172
+ new_idx = v_s["final"]
173
+ b_use_memory = True
174
+ if self.logger is not None:
175
+ self.logger.info(f"used memory for idx={idx}, jump to new_idx={new_idx}.")
176
+
177
+ # 从 seq 中获取值
178
+ res, b_success, error = None, False, None
179
+ while attempts < self.paras["redirect_max_attempts"] + 1:
180
+ res, b_success, error = self.fetch(new_idx)
181
+ if b_success:
182
+ if self.memory is not None and self.memory.has(key=new_idx):
183
+ v_s = self.memory.get(key=new_idx)
184
+ v_s["failures"] -= 1
185
+ if v_s["failures"] <= 1e-10:
186
+ self.memory.pop(key=new_idx)
187
+ break
188
+ old_idx = new_idx
189
+ if self.paras["seq_len"] > 1:
190
+ new_idx = self.idx_redirector(new_idx, self.paras["seq_len"], attempts, self.rng)
191
+ new_idx = _round_idx(new_idx, st=0, ed=self.paras["seq_len"])
192
+ #
193
+ if self.memory is not None:
194
+ v_s = self.memory.get(key=old_idx, b_add_if_not_found=True, default_factory=dict)
195
+ v_s["next"] = new_idx
196
+ #
197
+ attempts += 1
198
+ if self.logger is not None:
199
+ self.logger.info(f"attempts {attempts}:")
200
+ self.logger.warn(f"failed to fetch {old_idx}, because of {error}.")
201
+ self.logger.info(f"redirected from {old_idx} to {new_idx}.")
202
+
203
+ if not b_success:
204
+ if self.logger is not None:
205
+ self.logger.error(f"failed to fetch {idx} after {attempts} attempts, because of {error}.")
206
+ if self.paras["default_value"] is EMPTY:
207
+ raise error
208
+ return self.paras["default_value"]
209
+ else:
210
+ if new_idx != idx and self.memory is not None: # 经过了重定向
211
+ v_s = self.memory.get(key=idx)
212
+ v_s["final"] = new_idx
213
+ if not b_use_memory:
214
+ v_s["failures"] = v_s.get("failures", 0) + 1
215
+ return res
216
+
217
+ def __call__(self, idx):
218
+ if idx >= len(self) or idx < -len(self):
219
+ raise IndexError("Index out of range")
220
+ idx = _round_idx(idx, st=0, ed=len(self))
221
+ return self.redirectable_fetch(idx)
222
+
223
+ def __getitem__(self, idx):
224
+ return self(idx)
225
+
226
+ def __len__(self):
227
+ return self.paras["seq_len"]
228
+
229
+ def clear(self):
230
+ if self.memory is not None:
231
+ self.memory.clear()
232
+ if self.logger is not None:
233
+ self.logger.info("invoked clear()")
234
+
235
+ # ---------------------- 用于保存和加载状态 ---------------------- #
236
+ def load_state_dict(self, state_dict):
237
+ """
238
+ 加载状态
239
+ """
240
+ self.clear()
241
+ if self.logger is not None:
242
+ self.logger.info("invoked load_state_dict()")
243
+ if self.memory is not None:
244
+ self.memory.load_state_dict(state_dict=state_dict["memory"])
245
+ set_rng_state(state=state_dict["rng_state"], rng=self.rng)
246
+
247
+ def state_dict(self, b_deepcopy=True):
248
+ """
249
+ 获取状态
250
+ """
251
+ temp = {
252
+ "memory": self.memory.state_dict(b_deepcopy=False) if self.memory is not None else None,
253
+ "rng_state": get_rng_state(rng=self.rng),
254
+ }
255
+ if b_deepcopy:
256
+ import kevin_toolbox.nested_dict_list as ndl
257
+ temp = ndl.copy_(var=temp, b_deepcopy=True, b_keep_internal_references=True)
258
+ return temp
@@ -0,0 +1 @@
1
+ from .reservoir_sampler import Reservoir_Sampler
@@ -0,0 +1,116 @@
1
+ from kevin_toolbox.patches.for_numpy.random import get_rng, get_rng_state, set_rng_state
2
+
3
+
4
+ class Reservoir_Sampler:
5
+ """
6
+ 水库采样器
7
+ """
8
+
9
+ def __init__(self, **kwargs):
10
+ """
11
+ 参数:
12
+ capacity: <int> 水库的容量
13
+ seed, rng: 设定随机发生器
14
+ """
15
+ # 默认参数
16
+ paras = {
17
+ "capacity": 1,
18
+ #
19
+ "seed": None,
20
+ "rng": None,
21
+ }
22
+
23
+ # 获取参数
24
+ paras.update(kwargs)
25
+
26
+ # 校验参数
27
+ assert paras["capacity"] >= 1
28
+
29
+ #
30
+ self.paras = paras
31
+ self.reservoir = []
32
+ self.state = self._init_state()
33
+ self.rng = get_rng(seed=paras["seed"], rng=paras["rng"])
34
+
35
+ @staticmethod
36
+ def _init_state():
37
+ """
38
+ 初始化状态
39
+ """
40
+ return dict(
41
+ total_nums=0,
42
+ )
43
+
44
+ def add(self, item, **kwargs):
45
+ """
46
+ 添加单个数据 item 到采样器中。
47
+ 对于前 k 个数据,直接存入水库;之后以 k/(当前数据数)概率选择替换水库中的一个随机位置。
48
+ """
49
+ self.state["total_nums"] += 1
50
+ if self.state["total_nums"] <= self.paras["capacity"]:
51
+ self.reservoir.append(item)
52
+ else:
53
+ # 生成一个 0 到 count-1 之间的随机整数
54
+ j = self.rng.randint(0, self.state["total_nums"] - 1)
55
+ if j < self.paras["capacity"]:
56
+ self.reservoir[j] = item
57
+
58
+ def add_sequence(self, item_ls, **kwargs):
59
+ for item in item_ls:
60
+ self.add(item, **kwargs)
61
+
62
+ def get(self, **kwargs):
63
+ """
64
+ 返回当前水库中的数据列表(拷贝)。
65
+ """
66
+ return self.reservoir.copy()
67
+
68
+ def clear(self):
69
+ """
70
+ 清空已有数据和状态,重置采样器。
71
+ """
72
+ self.reservoir.clear()
73
+ self.state = self._init_state()
74
+ self.rng = get_rng(seed=self.paras["seed"], rng=self.paras["rng"])
75
+
76
+ def __len__(self):
77
+ return self.state["total_nums"]
78
+
79
+ # ---------------------- 用于保存和加载状态 ---------------------- #
80
+
81
+ def load_state_dict(self, state_dict):
82
+ """
83
+ 加载状态
84
+ """
85
+ self.clear()
86
+ self.state.update(state_dict["state"])
87
+ self.reservoir.extend(state_dict["reservoir"])
88
+ set_rng_state(state=state_dict["rng_state"], rng=self.rng)
89
+
90
+ def state_dict(self, b_deepcopy=True):
91
+ """
92
+ 获取状态
93
+ """
94
+ temp = {"state": self.state, "reservoir": self.reservoir, "rng_state": get_rng_state(rng=self.rng)}
95
+ if b_deepcopy:
96
+ import kevin_toolbox.nested_dict_list as ndl
97
+ temp = ndl.copy_(var=temp, b_deepcopy=True, b_keep_internal_references=True)
98
+ return temp
99
+
100
+
101
+ # 测试示例
102
+ if __name__ == "__main__":
103
+ sampler = Reservoir_Sampler(capacity=5, seed=12345)
104
+ for i in range(1, 21):
105
+ sampler.add(i)
106
+ print("当前水库数据:", sampler.get())
107
+
108
+ state = sampler.state_dict()
109
+ print("状态字典:", state)
110
+
111
+ # 清空后再恢复状态
112
+ sampler.clear()
113
+ print("清空后:", sampler.get())
114
+
115
+ sampler.load_state_dict(state)
116
+ print("恢复后水库数据:", sampler.get())
@@ -37,7 +37,7 @@ class Accumulator_Base(object):
37
37
  add() (*)添加单个数据
38
38
  add_sequence() (*)添加一系列数据
39
39
  get() (*)获取累积值
40
- clear() 情况已有数据(self.var)和状态(self.state)
40
+ clear() 清空已有数据(self.var)和状态(self.state)
41
41
  state_dict() 返回当前实例的状态(返回一个包含 self.var 和 self.state 的字典)
42
42
  load_state_dict() 通过接受 state_dict 来更新当前实例的状态
43
43
 
@@ -3,6 +3,12 @@ from kevin_toolbox.data_flow.core.reader import File_Iterative_Reader
3
3
  from kevin_toolbox.data_flow.file.kevin_notation.converter import Converter, CONVERTER_FOR_READER
4
4
 
5
5
 
6
+ def set_comment_flag_to_reader(reader, comment_flag):
7
+ if comment_flag is not None and isinstance(comment_flag, str) and len(comment_flag) > 0:
8
+ reader.paras["filter_"] = lambda x: x != "\n" and not x.startswith(comment_flag) # 去除注释
9
+ reader.paras["map_func"] = lambda x: x.rsplit("\n", 1)[0].split(comment_flag, 1)[0]
10
+
11
+
6
12
  class Kevin_Notation_Reader:
7
13
  """
8
14
  遵守 kevin_notation 格式的数据文本读取器(格式要求参见本模块下的 readme)
@@ -51,8 +57,8 @@ class Kevin_Notation_Reader:
51
57
  # 读取开头
52
58
  self.reader = File_Iterative_Reader(file_path=self.paras["file_path"], file_obj=self.paras["file_obj"],
53
59
  pre_jump_size=self.paras["beg"],
54
- filter_=lambda x: x != "\n" and not x.startswith("//"), # 去除注释
55
- map_func=lambda x: x.rsplit("\n", 1)[0].split("//", 1)[0],
60
+ filter_=lambda x: x != "\n", # 去除注释
61
+ map_func=lambda x: x.rsplit("\n", 1)[0],
56
62
  drop=False)
57
63
  # kevin_notation
58
64
  offset = self.read_head(reader=self.reader, head="# --kevin_notation--")
@@ -62,14 +68,16 @@ class Kevin_Notation_Reader:
62
68
  self.metadata, count = self.read_metadata(reader=self.reader, converter=self.paras["converter"])
63
69
  offset += count
64
70
 
71
+ temp = {k: v for k, v in self.reader.paras.items() if
72
+ k in ["file_path", "file_obj", "filter_", "map_func", "drop"]}
65
73
  del self.reader
66
74
 
67
75
  # 读取内容
68
- self.reader = File_Iterative_Reader(file_path=self.paras["file_path"], file_obj=self.paras["file_obj"],
69
- pre_jump_size=self.paras["beg"] + offset,
70
- filter_=lambda x: x != "\n" and not x.startswith("//"), # 去除注释
71
- map_func=lambda x: x.rsplit("\n", 1)[0].split("//", 1)[0],
72
- drop=False)
76
+ comment_flag = self.metadata.get("comment_flag", None)
77
+ temp["pre_jump_size"] = self.paras["beg"] + offset
78
+ if comment_flag is not None and isinstance(comment_flag, str) and len(comment_flag) > 0:
79
+ temp["pre_jump_size"] -= 1 # 将自动忽略注释那一行,因此需要减去1
80
+ self.reader = File_Iterative_Reader(**temp)
73
81
 
74
82
  # contents
75
83
  self.read_head(reader=self.reader, head="# --contents--")
@@ -131,6 +139,9 @@ class Kevin_Notation_Reader:
131
139
  if len(value) == 1 and key not in ["column_name", "column_type"]:
132
140
  value = value[0]
133
141
  #
142
+ if key == "comment_flag":
143
+ set_comment_flag_to_reader(reader=reader, comment_flag=value)
144
+ #
134
145
  res[key] = value
135
146
 
136
147
  # 最后检验内容
@@ -31,7 +31,9 @@ class Kevin_Notation_Writer:
31
31
  用于根据指定数据类型选取适当的函数来处理输入数据。
32
32
  sep: <string> 默认的分隔符
33
33
  默认使用 \t
34
-
34
+ comment_flag: <string> 默认的注释标志符
35
+ 默认为 None,表示不支持使用注释。
36
+ 建议使用 \\ 作为注释标志符
35
37
 
36
38
  工作流程:
37
39
  __init__(mode="w") ──► stage=0 ──► metadata_begin()
@@ -60,6 +62,7 @@ class Kevin_Notation_Writer:
60
62
  # 可选参数
61
63
  "converter": CONVERTER_FOR_WRITER,
62
64
  "sep": "\t",
65
+ "comment_flag": None
63
66
  }
64
67
 
65
68
  # 获取参数
@@ -77,6 +80,7 @@ class Kevin_Notation_Writer:
77
80
  #
78
81
  assert isinstance(paras["converter"], (Converter, dict,))
79
82
  assert isinstance(paras["sep"], (str,))
83
+ assert isinstance(paras["comment_flag"], (str, type(None)))
80
84
 
81
85
  self.paras = paras
82
86
 
@@ -104,6 +108,7 @@ class Kevin_Notation_Writer:
104
108
  else:
105
109
  # 采用覆盖写模式
106
110
  self.metadata["sep"] = self.paras["sep"]
111
+ self.metadata["comment_flag"] = self.paras["comment_flag"]
107
112
  # 获取文件对象
108
113
  self.file = open(self.paras["file_path"], **self.paras["paras_for_open"])
109
114
  # 写入文件标记
@@ -128,6 +133,8 @@ class Kevin_Notation_Writer:
128
133
 
129
134
  self.file.write(f"# --metadata--\n")
130
135
  self._write_metadata("sep", self.paras["sep"])
136
+ if self.paras["comment_flag"] is not None:
137
+ self._write_metadata("comment_flag", self.paras["comment_flag"])
131
138
  self.file.flush()
132
139
 
133
140
  def _write_metadata(self, key, value):
@@ -1,4 +1,5 @@
1
- metadata = {'sep': '\t', 'title': 'this is the title', 'column_name': ['epoch', 'loss', 'model_name'],
1
+ metadata = {'sep': '\t', 'comment_flag': '//', 'title': 'this is the title',
2
+ 'column_name': ['epoch', 'loss', 'model_name'],
2
3
  'column_type': ['int', 'float', 'str'], 'column_num': 3}
3
4
  content = {'epoch': [0, 1, 10, 11],
4
5
  'loss': [2.31, 2.22, 12.31, 12.22],
@@ -1,15 +1,13 @@
1
1
  import pytest
2
2
  from kevin_toolbox.patches.for_test import check_consistency
3
-
4
3
  import os
5
4
  import numpy as np
6
-
7
5
  from kevin_toolbox.data_flow.file import kevin_notation
8
6
  from kevin_toolbox.data_flow.file.kevin_notation.test.test_data.data_all import metadata_ls, content_ls, file_path_ls
9
7
 
10
8
 
11
9
  @pytest.mark.parametrize("expected_metadata, expected_content, file_path",
12
- zip(metadata_ls, content_ls, file_path_ls))
10
+ zip(metadata_ls[:], content_ls[:], file_path_ls[:]))
13
11
  def test_reader(expected_metadata, expected_content, file_path):
14
12
  print("test Reader")
15
13
 
@@ -24,7 +22,6 @@ def test_reader(expected_metadata, expected_content, file_path):
24
22
  for chunk in reader:
25
23
  for key in content.keys():
26
24
  content[key].extend(chunk[key])
27
- # print(content)
28
25
  check_consistency(expected_content, content)
29
26
 
30
27
 
@@ -1,12 +1,11 @@
1
1
  import pytest
2
- from kevin_toolbox.patches.for_test import check_consistency
3
-
4
2
  import os
5
- import numpy as np
6
-
7
3
  from kevin_toolbox.data_flow.file import kevin_notation
4
+ from kevin_toolbox.patches.for_test import check_consistency
8
5
  from kevin_toolbox.data_flow.file.kevin_notation.test.test_data.data_all import metadata_ls, content_ls, file_path_ls
9
6
 
7
+ data_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "test_data")
8
+
10
9
 
11
10
  @pytest.mark.parametrize("expected_metadata, expected_content, file_path",
12
11
  zip(metadata_ls, content_ls, file_path_ls))
@@ -25,3 +24,16 @@ def test_write(expected_metadata, expected_content, file_path):
25
24
  with pytest.raises(AssertionError):
26
25
  list(expected_content.values())[0].clear()
27
26
  kevin_notation.write(metadata=expected_metadata, content=expected_content, file_path=file_path)
27
+
28
+
29
+ def test_read():
30
+ """
31
+ 测试在不启用注释时,能否正确读取带有 // 的值
32
+ bug 原因:由于之前的版本默认启用"//"作为注释符号,因此导致无法正确读取带有"//"的值
33
+ 解决:在 Kevin_Notation_Reader 和 Kevin_Notation_Writer 取消默认使用注释,并限定只有在 metadata 中显式指定注释标志符才会启用
34
+ """
35
+ input_file = os.path.join(data_dir, "data_2.kvt")
36
+ metadata, content = kevin_notation.read(file_path=input_file)
37
+ check_consistency(
38
+ {'image_path': ['a//image1.jpg'], 'label': [0]}, content
39
+ )
@@ -1,3 +1,4 @@
1
+ import warnings
1
2
  from kevin_toolbox.data_flow.file.markdown.table import Table_Format
2
3
 
3
4
 
@@ -12,4 +13,6 @@ def get_format(content_s):
12
13
  res = Table_Format.COMPLETE_DICT
13
14
  elif isinstance(v, (list, tuple)):
14
15
  res = Table_Format.SIMPLE_DICT
16
+ if res is None:
17
+ warnings.warn(f'failed to get format from given content_s: {content_s}')
15
18
  return res
@@ -50,7 +50,9 @@ def build_logger(name, handler_ls, level=logging.DEBUG,
50
50
  handler = logging.StreamHandler()
51
51
  else:
52
52
  raise ValueError(f'unexpected target {details["target"]}')
53
- handler.setLevel(details.get("level", level))
53
+ temp = details.get("level", level)
54
+ temp = getattr(logging, temp) if isinstance(temp, (str,)) else temp
55
+ handler.setLevel(temp)
54
56
  handler.setFormatter(logging.Formatter(details.get("formatter", formatter)))
55
57
  # 添加到logger中
56
58
  logger.addHandler(handler)