pyxllib 0.3.197__py3-none-any.whl → 3.201.1__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 (127) hide show
  1. pyxllib/__init__.py +14 -21
  2. pyxllib/algo/__init__.py +8 -8
  3. pyxllib/algo/disjoint.py +54 -54
  4. pyxllib/algo/geo.py +537 -541
  5. pyxllib/algo/intervals.py +964 -964
  6. pyxllib/algo/matcher.py +389 -389
  7. pyxllib/algo/newbie.py +166 -166
  8. pyxllib/algo/pupil.py +629 -629
  9. pyxllib/algo/shapelylib.py +67 -67
  10. pyxllib/algo/specialist.py +241 -241
  11. pyxllib/algo/stat.py +494 -494
  12. pyxllib/algo/treelib.py +145 -149
  13. pyxllib/algo/unitlib.py +62 -66
  14. pyxllib/autogui/__init__.py +5 -5
  15. pyxllib/autogui/activewin.py +246 -246
  16. pyxllib/autogui/all.py +9 -9
  17. pyxllib/autogui/autogui.py +846 -852
  18. pyxllib/autogui/uiautolib.py +362 -362
  19. pyxllib/autogui/virtualkey.py +102 -102
  20. pyxllib/autogui/wechat.py +827 -827
  21. pyxllib/autogui/wechat_msg.py +421 -421
  22. pyxllib/autogui/wxautolib.py +84 -84
  23. pyxllib/cv/__init__.py +5 -5
  24. pyxllib/cv/expert.py +267 -267
  25. pyxllib/cv/imfile.py +159 -159
  26. pyxllib/cv/imhash.py +39 -39
  27. pyxllib/cv/pupil.py +9 -9
  28. pyxllib/cv/rgbfmt.py +1525 -1525
  29. pyxllib/cv/slidercaptcha.py +137 -137
  30. pyxllib/cv/trackbartools.py +251 -251
  31. pyxllib/cv/xlcvlib.py +1040 -1040
  32. pyxllib/cv/xlpillib.py +423 -423
  33. pyxllib/data/echarts.py +236 -240
  34. pyxllib/data/jsonlib.py +85 -89
  35. pyxllib/data/oss.py +72 -72
  36. pyxllib/data/pglib.py +1111 -1127
  37. pyxllib/data/sqlite.py +568 -568
  38. pyxllib/data/sqllib.py +297 -297
  39. pyxllib/ext/JLineViewer.py +505 -505
  40. pyxllib/ext/__init__.py +6 -6
  41. pyxllib/ext/demolib.py +251 -246
  42. pyxllib/ext/drissionlib.py +277 -277
  43. pyxllib/ext/kq5034lib.py +12 -12
  44. pyxllib/ext/qt.py +449 -449
  45. pyxllib/ext/robustprocfile.py +493 -497
  46. pyxllib/ext/seleniumlib.py +76 -76
  47. pyxllib/ext/tk.py +173 -173
  48. pyxllib/ext/unixlib.py +821 -827
  49. pyxllib/ext/utools.py +345 -351
  50. pyxllib/ext/webhook.py +124 -119
  51. pyxllib/ext/win32lib.py +40 -40
  52. pyxllib/ext/wjxlib.py +91 -88
  53. pyxllib/ext/wpsapi.py +124 -124
  54. pyxllib/ext/xlwork.py +9 -9
  55. pyxllib/ext/yuquelib.py +1110 -1105
  56. pyxllib/file/__init__.py +17 -17
  57. pyxllib/file/docxlib.py +757 -761
  58. pyxllib/file/gitlib.py +309 -309
  59. pyxllib/file/libreoffice.py +165 -165
  60. pyxllib/file/movielib.py +144 -148
  61. pyxllib/file/newbie.py +10 -10
  62. pyxllib/file/onenotelib.py +1469 -1469
  63. pyxllib/file/packlib/__init__.py +330 -330
  64. pyxllib/file/packlib/zipfile.py +2441 -2441
  65. pyxllib/file/pdflib.py +422 -426
  66. pyxllib/file/pupil.py +185 -185
  67. pyxllib/file/specialist/__init__.py +681 -685
  68. pyxllib/file/specialist/dirlib.py +799 -799
  69. pyxllib/file/specialist/download.py +193 -193
  70. pyxllib/file/specialist/filelib.py +2825 -2829
  71. pyxllib/file/xlsxlib.py +3122 -3131
  72. pyxllib/file/xlsyncfile.py +341 -341
  73. pyxllib/prog/__init__.py +5 -5
  74. pyxllib/prog/cachetools.py +58 -64
  75. pyxllib/prog/deprecatedlib.py +233 -233
  76. pyxllib/prog/filelock.py +42 -42
  77. pyxllib/prog/ipyexec.py +253 -253
  78. pyxllib/prog/multiprogs.py +940 -940
  79. pyxllib/prog/newbie.py +451 -451
  80. pyxllib/prog/pupil.py +1208 -1197
  81. pyxllib/prog/sitepackages.py +33 -33
  82. pyxllib/prog/specialist/__init__.py +348 -391
  83. pyxllib/prog/specialist/bc.py +203 -203
  84. pyxllib/prog/specialist/browser.py +497 -497
  85. pyxllib/prog/specialist/common.py +347 -347
  86. pyxllib/prog/specialist/datetime.py +198 -198
  87. pyxllib/prog/specialist/tictoc.py +240 -240
  88. pyxllib/prog/specialist/xllog.py +180 -180
  89. pyxllib/prog/xlosenv.py +110 -108
  90. pyxllib/stdlib/__init__.py +17 -17
  91. pyxllib/stdlib/tablepyxl/__init__.py +10 -10
  92. pyxllib/stdlib/tablepyxl/style.py +303 -303
  93. pyxllib/stdlib/tablepyxl/tablepyxl.py +130 -130
  94. pyxllib/text/__init__.py +8 -8
  95. pyxllib/text/ahocorasick.py +36 -39
  96. pyxllib/text/airscript.js +754 -744
  97. pyxllib/text/charclasslib.py +121 -121
  98. pyxllib/text/jiebalib.py +267 -267
  99. pyxllib/text/jinjalib.py +27 -32
  100. pyxllib/text/jsa_ai_prompt.md +271 -271
  101. pyxllib/text/jscode.py +922 -922
  102. pyxllib/text/latex/__init__.py +158 -158
  103. pyxllib/text/levenshtein.py +303 -303
  104. pyxllib/text/nestenv.py +1215 -1215
  105. pyxllib/text/newbie.py +300 -300
  106. pyxllib/text/pupil/__init__.py +8 -8
  107. pyxllib/text/pupil/common.py +1121 -1121
  108. pyxllib/text/pupil/xlalign.py +326 -326
  109. pyxllib/text/pycode.py +47 -47
  110. pyxllib/text/specialist/__init__.py +8 -8
  111. pyxllib/text/specialist/common.py +112 -112
  112. pyxllib/text/specialist/ptag.py +186 -186
  113. pyxllib/text/spellchecker.py +172 -172
  114. pyxllib/text/templates/echart_base.html +10 -10
  115. pyxllib/text/templates/highlight_code.html +16 -16
  116. pyxllib/text/templates/latex_editor.html +102 -102
  117. pyxllib/text/vbacode.py +17 -17
  118. pyxllib/text/xmllib.py +741 -747
  119. pyxllib/xl.py +42 -39
  120. pyxllib/xlcv.py +17 -17
  121. pyxllib-3.201.1.dist-info/METADATA +296 -0
  122. pyxllib-3.201.1.dist-info/RECORD +125 -0
  123. {pyxllib-0.3.197.dist-info → pyxllib-3.201.1.dist-info}/licenses/LICENSE +190 -190
  124. pyxllib/ext/old.py +0 -663
  125. pyxllib-0.3.197.dist-info/METADATA +0 -48
  126. pyxllib-0.3.197.dist-info/RECORD +0 -126
  127. {pyxllib-0.3.197.dist-info → pyxllib-3.201.1.dist-info}/WHEEL +0 -0
pyxllib/prog/newbie.py CHANGED
@@ -1,451 +1,451 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # @Author : 陈坤泽
4
- # @Email : 877362867@qq.com
5
- # @Date : 2021/06/06 10:51
6
-
7
-
8
- def typename(c):
9
- """ 简化输出的type类型
10
-
11
- >>> typename(123)
12
- 'int'
13
- """
14
- return str(type(c))[8:-2]
15
-
16
-
17
- class SingletonForEveryClass(type):
18
- """ 注意如果A是单例类,B从A继承,那么实际有且仅有A、B两个不同的实例对象 """
19
- _instances = {}
20
-
21
- def __call__(cls, *args, **kwargs):
22
- tag = f'{cls}'
23
- # 其实转字符串来判断是不太严谨的,有些类型字符串后的显示效果是一模一样的
24
- # dprint(tag)
25
- if tag not in cls._instances:
26
- cls._instances[tag] = super(SingletonForEveryClass, cls).__call__(*args, **kwargs)
27
- return cls._instances[tag]
28
-
29
-
30
- class SingletonForEveryInitArgs(type):
31
- """ Python单例模式(Singleton)的N种实现 - 知乎: https://zhuanlan.zhihu.com/p/37534850
32
-
33
- 注意!注意!注意!重要的事说三遍!
34
- 这里的单例类不是传统意义上的单例类。
35
- 传统意义的单例类,不管用怎样不同的初始化参数创建对象,永远都只有最初的那个对象类。
36
- 但是这个单例类,为每种不同的参数创建形式,都构造了一个对象。
37
- """
38
- _instances = {}
39
-
40
- def __call__(cls, *args, **kwargs):
41
- tag = f'{cls}{args}{kwargs}' # id加上所有参数的影响来控制单例类
42
- # 其实转字符串来判断是不太严谨的,有些类型字符串后的显示效果是一模一样的
43
- # dprint(tag)
44
- if tag not in cls._instances:
45
- cls._instances[tag] = super(SingletonForEveryInitArgs, cls).__call__(*args, **kwargs)
46
- return cls._instances[tag]
47
-
48
-
49
- def xlbool(v):
50
- """ 有些类型不能直接判断真假,例如具有多值的np.array,df等
51
-
52
- 这些有歧义的情况,在我的mybool里暂时都判断为True,如果有需要,需要精细化判断,可以扩展自己的npbool、dfbool
53
- """
54
- try:
55
- return bool(v)
56
- except ValueError:
57
- return True
58
-
59
-
60
- def document(func):
61
- """文档函数装饰器
62
- 用该装饰器器时,表明一个函数是用伪代码在表示一系列的操作逻辑,不能直接拿来执行的
63
- 很可能是一套半自动化工具
64
- """
65
-
66
- def wrapper(*args):
67
- raise RuntimeError(f'函数:{func.__name__} 是一个伪代码流程示例文档,不能直接运行')
68
-
69
- return wrapper
70
-
71
-
72
- class RunOnlyOnce:
73
- """ 被装饰的函数,不同的参数输入形式,只会被执行一次,
74
-
75
- 重复执行时会从内存直接调用上次相同参数调用下的运行的结果
76
- 可以使用reset成员函数重置,下一次调用该函数时则会重新执行
77
-
78
- 文档:https://www.yuque.com/xlpr/pyxllib/RunOnlyOnce
79
-
80
- 使用好该装饰器,可能让一些动态规划dp、搜索问题变得更简洁,
81
- 以及一些配置文件操作,可以做到只读一遍
82
- """
83
-
84
- def __init__(self, func, distinct_args=True):
85
- """
86
- :param func: 封装的函数
87
- :param distinct_args: 默认不同输入参数形式,都会保存一个结果
88
- 设为False,则不管何种参数形式,函数就真的只会保存第一次运行的结果
89
- """
90
- self.func = func
91
- self.distinct_args = distinct_args
92
- self.results = {}
93
-
94
- @classmethod
95
- def decorator(cls, distinct_args=True):
96
- """ 作为装饰器的时候,如果要设置参数,要用这个接口 """
97
-
98
- def wrap(func):
99
- return cls(func, distinct_args)
100
-
101
- return wrap
102
-
103
- def __call__(self, *args, **kwargs):
104
- tag = f'{args}{kwargs}' if self.distinct_args else ''
105
- # TODO 思考更严谨,考虑了值类型的tag标记
106
- # 目前的tag规则,是必要不充分条件。还可以使用id,则是充分不必要条件
107
- # 能找到充要条件来做是最好的,不行的话,也应该用更严谨的充分条件来做
108
- # TODO kwargs的顺序应该是没影响的,要去掉顺序干扰
109
- if tag not in self.results:
110
- self.results[tag] = self.func(*args, **kwargs)
111
- return self.results[tag]
112
-
113
- def reset(self):
114
- self.results = {}
115
-
116
-
117
- def len_in_dim2(arr):
118
- """ 计算类List结构在第2维上的最大长度
119
-
120
- >>> len_in_dim2([[1,1], [2], [3,3,3]])
121
- 3
122
-
123
- >>> len_in_dim2([1, 2, 3]) # TODO 是不是应该改成0合理?但不知道牵涉到哪些功能影响
124
- 1
125
- """
126
- if not isinstance(arr, (list, tuple)):
127
- raise TypeError('类型错误,不是list构成的二维数组')
128
-
129
- # 找出元素最多的列
130
- column_num = 0
131
- for i, item in enumerate(arr):
132
- if isinstance(item, (list, tuple)): # 该行是一个一维数组
133
- column_num = max(column_num, len(item))
134
- else: # 如果不是数组,是指单个元素,当成1列处理
135
- column_num = max(column_num, 1)
136
-
137
- return column_num
138
-
139
-
140
- def ensure_array(arr, default_value=''):
141
- """对一个由list、tuple组成的二维数组,确保所有第二维的列数都相同
142
-
143
- >>> ensure_array([[1,1], [2], [3,3,3]])
144
- [[1, 1, ''], [2, '', ''], [3, 3, 3]]
145
- """
146
- max_cols = len_in_dim2(arr)
147
- if max_cols == 1:
148
- return arr
149
- dv = str(default_value)
150
- a = [[]] * len(arr)
151
- for i, ls in enumerate(arr):
152
- if isinstance(ls, (list, tuple)):
153
- t = list(arr[i])
154
- else:
155
- t = [ls] # 如果不是数组,是指单个元素,当成1列处理
156
- a[i] = t + [dv] * (max_cols - len(t)) # 左边的写list,是防止有的情况是tuple,要强制转list后拼接
157
- return a
158
-
159
-
160
- def swap_rowcol(a, *, ensure_arr=False, default_value=''):
161
- """矩阵行列互换
162
-
163
- 注:如果列数是不均匀的,则会以最小列数作为行数
164
-
165
- >>> swap_rowcol([[1,2,3], [4,5,6]])
166
- [[1, 4], [2, 5], [3, 6]]
167
- """
168
- if ensure_arr:
169
- a = ensure_array(a, default_value)
170
- # 这是非常有教学意义的行列互换实现代码
171
- return list(map(list, zip(*a)))
172
-
173
-
174
- class GrowingList(list):
175
- """可变长list"""
176
-
177
- def __init__(self, default_value=None):
178
- super().__init__(self)
179
- self.default_value = default_value
180
-
181
- def __getitem__(self, index):
182
- if index >= len(self):
183
- self.extend([self.default_value] * (index + 1 - len(self)))
184
- return list.__getitem__(self, index)
185
-
186
- def __setitem__(self, index, value):
187
- if index >= len(self):
188
- self.extend([self.default_value] * (index + 1 - len(self)))
189
- list.__setitem__(self, index, value)
190
-
191
-
192
- class GenFunction:
193
- """ 一般用来生成高阶函数的函数对象
194
-
195
- 这个名字可能还不是很精确,后面有想法再改
196
- """
197
-
198
- @classmethod
199
- def ensure_func(cls, x, default):
200
- """ 确保x是callable对象,否则用default初始化 """
201
- if callable(x):
202
- return x
203
- else:
204
- return default
205
-
206
-
207
- def first_nonnone(args, judge=None):
208
- """ 返回第1个满足条件的值
209
-
210
- :param args: 参数清单
211
- :param judge: 判断器,默认返回第一个非None值,也可以自定义判定函数
212
- """
213
- judge = GenFunction.ensure_func(judge, lambda x: x is not None)
214
- for x in args:
215
- if judge(x):
216
- return x
217
- return args[-1] # 全部都不满足,返回最后一个值
218
-
219
-
220
- def round_int(x, *, ndim=0):
221
- """ 先四舍五入,再取整
222
-
223
- :param x: 一个数值,或者多维数组
224
- :param ndim: x是数值是默认0,否则指定数组维度,批量处理
225
- 比如ndim=1是一维数组
226
- ndim=2是二维数组
227
-
228
- >>> round_int(1.5)
229
- 2
230
- >>> round_int(1.4)
231
- 1
232
- >>> round_int([2.3, 1.42], ndim=1)
233
- [2, 1]
234
- >>> round_int([[2.3, 1.42], [3.6]], ndim=2)
235
- [[2, 1], [4]]
236
- """
237
- if ndim:
238
- return [round_int(a, ndim=ndim - 1) for a in x]
239
- else:
240
- return int(round(x, 0))
241
-
242
-
243
- def human_readable_size(n):
244
- """ 我个人习惯常用的size显示方式 """
245
- for u in [' B', 'KB', 'MB', 'GB']:
246
- if n < 1024:
247
- return f'{round_int(n)}{u}'
248
- else:
249
- n /= 1024
250
- else:
251
- return f'{round_int(n)}TB'
252
-
253
-
254
- def human_readable_number(value, base_type='K', precision=4):
255
- """ 数字美化输出函数
256
-
257
- :param float|int value: 要转换的数值
258
- :param int precision: 有效数字的长度
259
- :param str base_type: 进制类型,'K'为1000进制, 'KB'为1024进制(KiB同理), '万'为中文万进制
260
- :return: 美化后的字符串
261
- """
262
- if value is None:
263
- return ''
264
-
265
- if abs(value) < 1:
266
- return f'{value:.{precision}g}'
267
-
268
- # 设置不同进制的单位和基数
269
- units, base = {
270
- 'K': (['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'], 1000),
271
- 'KB': (['', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 1024),
272
- 'KiB': (['', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'], 1024),
273
- '万': (['', '万', '亿', '万亿', '亿亿'], 10000),
274
- '秒': (['秒', [60, '分'], [60, '时'], [24, '天'], [7, '周'], [4.345, '月'], [12, '年']], 60),
275
- }.get(base_type, ([''], 1)) # 默认为空单位和基数1
276
-
277
- x, i = abs(value), 0
278
- while x >= base and i < len(units) - 1:
279
- x /= base
280
- i += 1
281
- if isinstance(units[i], list):
282
- base = units[i][0]
283
- units[i] = units[i][1]
284
-
285
- x = f'{x:.{precision}g}' # 四舍五入到指定精度
286
- prefix = '-' if value < 0 else '' # 负数处理
287
- return f"{prefix}{x}{units[i]}"
288
-
289
-
290
- class CvtType:
291
- """ 这些系列的转换函数,转换失败统一报错为ValueError
292
-
293
- 返回异常会比较好,否则返回None、False之类的,
294
- 有时候可能就是要转换的值呢,会有歧义
295
- """
296
-
297
- @classmethod
298
- def str2list(cls, arg):
299
- try:
300
- res = eval(arg)
301
- except SyntaxError:
302
- raise ValueError
303
-
304
- if not isinstance(res, list):
305
- raise ValueError
306
-
307
- return res
308
-
309
- @classmethod
310
- def str2dict(cls, arg):
311
- try:
312
- res = eval(arg)
313
- except SyntaxError:
314
- raise ValueError
315
-
316
- if not isinstance(res, dict):
317
- raise ValueError
318
-
319
- return res
320
-
321
- @classmethod
322
- def factory(cls, name):
323
- """ 从字符串名称,映射到对应的转换函数 """
324
- return {'int': int,
325
- 'float': float,
326
- 'str': str,
327
- 'list': cls.str2list,
328
- 'dict': cls.str2dict}.get(name, None)
329
-
330
-
331
- def mod_minabs(x, m):
332
- a = x % m
333
- return a if a < m / 2 else a - m
334
-
335
-
336
- class classproperty(property):
337
- """ python - Using property() on classmethods - Stack Overflow
338
- https://stackoverflow.com/questions/128573/using-property-on-classmethods
339
- """
340
-
341
- def __get__(self, obj, objtype=None):
342
- return super(classproperty, self).__get__(objtype)
343
-
344
- def __set__(self, obj, value):
345
- super(classproperty, self).__set__(type(obj), value)
346
-
347
- def __delete__(self, obj):
348
- super(classproperty, self).__delete__(type(obj))
349
-
350
-
351
- def decode_bitflags(n, flags, return_type=dict):
352
- """ 解析一个位标记的功能
353
-
354
- :param int n: 一个整数标记
355
- :param list|tuple flags: 一个对应明文数组
356
- flags[0]对应2**0的明文,flags[1]对应2**1的明文,以此类推
357
- :param type return_type: 返回的数据类型
358
- 默认dict,记录所有key的bool结果
359
- 可以设set,只返回真的标记结果
360
-
361
- >>> decode_bitflags(18, ('superscript', 'italic', 'serifed', 'monospaced', 'bold'))
362
- {'superscript': 2, 'italic': 0, 'serifed': 0, 'monospaced': 16, 'bold': 0}
363
- >>> decode_bitflags(18, ('superscript', 'italic', 'serifed', 'monospaced', 'bold'), set)
364
- {'superscript', 'monospaced'}
365
- """
366
- if return_type == dict:
367
- return {x: n & (2 << i) for i, x in enumerate(flags)}
368
- elif return_type == set:
369
- return {x for i, x in enumerate(flags) if (n & (2 << i))}
370
- else:
371
- raise ValueError
372
-
373
-
374
- def xl_format_g(x, p=3):
375
- """ 普通format的g模式不太满足我的需求,做了点修改
376
-
377
- 注:g是比较方便的一种数值格式化方法,会比较智能地判断是否整数显示,或者普通显示、科学计数法显示
378
-
379
- :param x: 数值x
380
- """
381
- s = f'{x:g}'
382
- if 'e' in s:
383
- # 如果变成了科学计数法,明确保留3位有效数字
384
- return '{:.{}g}'.format(x, p=3)
385
- else:
386
- # 否则返回默认的g格式
387
- return s
388
-
389
-
390
- class EmptyWith:
391
- """ 空上下文类
392
- """
393
-
394
- def __enter__(self):
395
- pass
396
-
397
- def __exit__(self, exc_type, exc_val, exc_tb):
398
- pass
399
-
400
-
401
- def funcmsg(func):
402
- """输出函数func所在的文件、函数名、函数起始行"""
403
- # showdir(func)
404
- if not hasattr(func, '__name__'): # 没有__name__属性表示这很可能是一个装饰器去处理原函数了
405
- if hasattr(func, 'func'): # 我的装饰器常用func成员存储原函数对象
406
- func = func.func
407
- else:
408
- return f'装饰器:{type(func)},无法定位'
409
- return f'函数名:{func.__name__},来自文件:{func.__code__.co_filename},所在行号={func.__code__.co_firstlineno}'
410
-
411
-
412
- def set_global_var(name, value):
413
- """ 设置某个全局变量,一般用在一些特殊的需要跨作用域进行调试的场景
414
- 切勿!切勿!切勿用于正式功能,否则会导致难以维护控制的功能代码
415
-
416
- 为了避免和某些关键的全局变量名冲突,这里的变量命令统一会加上pyxllib_的前缀
417
- """
418
- g = globals()
419
- name = f'pyxllib_{name}'
420
- g[name] = value
421
-
422
-
423
- def get_global_var(name, default_value=None):
424
- """ 获取某个全局变量的值 """
425
- g = globals()
426
- name = f'pyxllib_{name}'
427
- if name not in g:
428
- g[name] = default_value
429
- return g[name]
430
-
431
-
432
- def convert_to_json_compatible(d, custom_converter=None):
433
- """ 递归地将字典等类型转换为JSON兼容格式。对于非标准JSON类型,使用自定义转换器或默认转换为字符串。
434
-
435
- :param d: 要转换的字典或列表。
436
- :param custom_converter: 自定义转换函数,用于处理非标准JSON类型的值。
437
- :return: 转换后的字典或列表。
438
-
439
- todo 这个函数想法是好的,但总感觉精确性中,总容易有些问题的,需要更多的考察测试
440
- """
441
- if custom_converter is None:
442
- custom_converter = str
443
-
444
- if isinstance(d, dict): # defaultdict呢?
445
- return {k: convert_to_json_compatible(v, custom_converter) for k, v in d.items()}
446
- elif isinstance(d, list):
447
- return [convert_to_json_compatible(v, custom_converter) for v in d]
448
- elif isinstance(d, (int, float, str, bool)) or d is None:
449
- return d
450
- else:
451
- return custom_converter(d)
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # @Author : 陈坤泽
4
+ # @Email : 877362867@qq.com
5
+ # @Date : 2021/06/06 10:51
6
+
7
+
8
+ def typename(c):
9
+ """ 简化输出的type类型
10
+
11
+ >>> typename(123)
12
+ 'int'
13
+ """
14
+ return str(type(c))[8:-2]
15
+
16
+
17
+ class SingletonForEveryClass(type):
18
+ """ 注意如果A是单例类,B从A继承,那么实际有且仅有A、B两个不同的实例对象 """
19
+ _instances = {}
20
+
21
+ def __call__(cls, *args, **kwargs):
22
+ tag = f'{cls}'
23
+ # 其实转字符串来判断是不太严谨的,有些类型字符串后的显示效果是一模一样的
24
+ # dprint(tag)
25
+ if tag not in cls._instances:
26
+ cls._instances[tag] = super(SingletonForEveryClass, cls).__call__(*args, **kwargs)
27
+ return cls._instances[tag]
28
+
29
+
30
+ class SingletonForEveryInitArgs(type):
31
+ """ Python单例模式(Singleton)的N种实现 - 知乎: https://zhuanlan.zhihu.com/p/37534850
32
+
33
+ 注意!注意!注意!重要的事说三遍!
34
+ 这里的单例类不是传统意义上的单例类。
35
+ 传统意义的单例类,不管用怎样不同的初始化参数创建对象,永远都只有最初的那个对象类。
36
+ 但是这个单例类,为每种不同的参数创建形式,都构造了一个对象。
37
+ """
38
+ _instances = {}
39
+
40
+ def __call__(cls, *args, **kwargs):
41
+ tag = f'{cls}{args}{kwargs}' # id加上所有参数的影响来控制单例类
42
+ # 其实转字符串来判断是不太严谨的,有些类型字符串后的显示效果是一模一样的
43
+ # dprint(tag)
44
+ if tag not in cls._instances:
45
+ cls._instances[tag] = super(SingletonForEveryInitArgs, cls).__call__(*args, **kwargs)
46
+ return cls._instances[tag]
47
+
48
+
49
+ def xlbool(v):
50
+ """ 有些类型不能直接判断真假,例如具有多值的np.array,df等
51
+
52
+ 这些有歧义的情况,在我的mybool里暂时都判断为True,如果有需要,需要精细化判断,可以扩展自己的npbool、dfbool
53
+ """
54
+ try:
55
+ return bool(v)
56
+ except ValueError:
57
+ return True
58
+
59
+
60
+ def document(func):
61
+ """文档函数装饰器
62
+ 用该装饰器器时,表明一个函数是用伪代码在表示一系列的操作逻辑,不能直接拿来执行的
63
+ 很可能是一套半自动化工具
64
+ """
65
+
66
+ def wrapper(*args):
67
+ raise RuntimeError(f'函数:{func.__name__} 是一个伪代码流程示例文档,不能直接运行')
68
+
69
+ return wrapper
70
+
71
+
72
+ class RunOnlyOnce:
73
+ """ 被装饰的函数,不同的参数输入形式,只会被执行一次,
74
+
75
+ 重复执行时会从内存直接调用上次相同参数调用下的运行的结果
76
+ 可以使用reset成员函数重置,下一次调用该函数时则会重新执行
77
+
78
+ 文档:https://www.yuque.com/xlpr/pyxllib/RunOnlyOnce
79
+
80
+ 使用好该装饰器,可能让一些动态规划dp、搜索问题变得更简洁,
81
+ 以及一些配置文件操作,可以做到只读一遍
82
+ """
83
+
84
+ def __init__(self, func, distinct_args=True):
85
+ """
86
+ :param func: 封装的函数
87
+ :param distinct_args: 默认不同输入参数形式,都会保存一个结果
88
+ 设为False,则不管何种参数形式,函数就真的只会保存第一次运行的结果
89
+ """
90
+ self.func = func
91
+ self.distinct_args = distinct_args
92
+ self.results = {}
93
+
94
+ @classmethod
95
+ def decorator(cls, distinct_args=True):
96
+ """ 作为装饰器的时候,如果要设置参数,要用这个接口 """
97
+
98
+ def wrap(func):
99
+ return cls(func, distinct_args)
100
+
101
+ return wrap
102
+
103
+ def __call__(self, *args, **kwargs):
104
+ tag = f'{args}{kwargs}' if self.distinct_args else ''
105
+ # TODO 思考更严谨,考虑了值类型的tag标记
106
+ # 目前的tag规则,是必要不充分条件。还可以使用id,则是充分不必要条件
107
+ # 能找到充要条件来做是最好的,不行的话,也应该用更严谨的充分条件来做
108
+ # TODO kwargs的顺序应该是没影响的,要去掉顺序干扰
109
+ if tag not in self.results:
110
+ self.results[tag] = self.func(*args, **kwargs)
111
+ return self.results[tag]
112
+
113
+ def reset(self):
114
+ self.results = {}
115
+
116
+
117
+ def len_in_dim2(arr):
118
+ """ 计算类List结构在第2维上的最大长度
119
+
120
+ >>> len_in_dim2([[1,1], [2], [3,3,3]])
121
+ 3
122
+
123
+ >>> len_in_dim2([1, 2, 3]) # TODO 是不是应该改成0合理?但不知道牵涉到哪些功能影响
124
+ 1
125
+ """
126
+ if not isinstance(arr, (list, tuple)):
127
+ raise TypeError('类型错误,不是list构成的二维数组')
128
+
129
+ # 找出元素最多的列
130
+ column_num = 0
131
+ for i, item in enumerate(arr):
132
+ if isinstance(item, (list, tuple)): # 该行是一个一维数组
133
+ column_num = max(column_num, len(item))
134
+ else: # 如果不是数组,是指单个元素,当成1列处理
135
+ column_num = max(column_num, 1)
136
+
137
+ return column_num
138
+
139
+
140
+ def ensure_array(arr, default_value=''):
141
+ """对一个由list、tuple组成的二维数组,确保所有第二维的列数都相同
142
+
143
+ >>> ensure_array([[1,1], [2], [3,3,3]])
144
+ [[1, 1, ''], [2, '', ''], [3, 3, 3]]
145
+ """
146
+ max_cols = len_in_dim2(arr)
147
+ if max_cols == 1:
148
+ return arr
149
+ dv = str(default_value)
150
+ a = [[]] * len(arr)
151
+ for i, ls in enumerate(arr):
152
+ if isinstance(ls, (list, tuple)):
153
+ t = list(arr[i])
154
+ else:
155
+ t = [ls] # 如果不是数组,是指单个元素,当成1列处理
156
+ a[i] = t + [dv] * (max_cols - len(t)) # 左边的写list,是防止有的情况是tuple,要强制转list后拼接
157
+ return a
158
+
159
+
160
+ def swap_rowcol(a, *, ensure_arr=False, default_value=''):
161
+ """矩阵行列互换
162
+
163
+ 注:如果列数是不均匀的,则会以最小列数作为行数
164
+
165
+ >>> swap_rowcol([[1,2,3], [4,5,6]])
166
+ [[1, 4], [2, 5], [3, 6]]
167
+ """
168
+ if ensure_arr:
169
+ a = ensure_array(a, default_value)
170
+ # 这是非常有教学意义的行列互换实现代码
171
+ return list(map(list, zip(*a)))
172
+
173
+
174
+ class GrowingList(list):
175
+ """可变长list"""
176
+
177
+ def __init__(self, default_value=None):
178
+ super().__init__(self)
179
+ self.default_value = default_value
180
+
181
+ def __getitem__(self, index):
182
+ if index >= len(self):
183
+ self.extend([self.default_value] * (index + 1 - len(self)))
184
+ return list.__getitem__(self, index)
185
+
186
+ def __setitem__(self, index, value):
187
+ if index >= len(self):
188
+ self.extend([self.default_value] * (index + 1 - len(self)))
189
+ list.__setitem__(self, index, value)
190
+
191
+
192
+ class GenFunction:
193
+ """ 一般用来生成高阶函数的函数对象
194
+
195
+ 这个名字可能还不是很精确,后面有想法再改
196
+ """
197
+
198
+ @classmethod
199
+ def ensure_func(cls, x, default):
200
+ """ 确保x是callable对象,否则用default初始化 """
201
+ if callable(x):
202
+ return x
203
+ else:
204
+ return default
205
+
206
+
207
+ def first_nonnone(args, judge=None):
208
+ """ 返回第1个满足条件的值
209
+
210
+ :param args: 参数清单
211
+ :param judge: 判断器,默认返回第一个非None值,也可以自定义判定函数
212
+ """
213
+ judge = GenFunction.ensure_func(judge, lambda x: x is not None)
214
+ for x in args:
215
+ if judge(x):
216
+ return x
217
+ return args[-1] # 全部都不满足,返回最后一个值
218
+
219
+
220
+ def round_int(x, *, ndim=0):
221
+ """ 先四舍五入,再取整
222
+
223
+ :param x: 一个数值,或者多维数组
224
+ :param ndim: x是数值是默认0,否则指定数组维度,批量处理
225
+ 比如ndim=1是一维数组
226
+ ndim=2是二维数组
227
+
228
+ >>> round_int(1.5)
229
+ 2
230
+ >>> round_int(1.4)
231
+ 1
232
+ >>> round_int([2.3, 1.42], ndim=1)
233
+ [2, 1]
234
+ >>> round_int([[2.3, 1.42], [3.6]], ndim=2)
235
+ [[2, 1], [4]]
236
+ """
237
+ if ndim:
238
+ return [round_int(a, ndim=ndim - 1) for a in x]
239
+ else:
240
+ return int(round(x, 0))
241
+
242
+
243
+ def human_readable_size(n):
244
+ """ 我个人习惯常用的size显示方式 """
245
+ for u in [' B', 'KB', 'MB', 'GB']:
246
+ if n < 1024:
247
+ return f'{round_int(n)}{u}'
248
+ else:
249
+ n /= 1024
250
+ else:
251
+ return f'{round_int(n)}TB'
252
+
253
+
254
+ def human_readable_number(value, base_type='K', precision=4):
255
+ """ 数字美化输出函数
256
+
257
+ :param float|int value: 要转换的数值
258
+ :param int precision: 有效数字的长度
259
+ :param str base_type: 进制类型,'K'为1000进制, 'KB'为1024进制(KiB同理), '万'为中文万进制
260
+ :return: 美化后的字符串
261
+ """
262
+ if value is None:
263
+ return ''
264
+
265
+ if abs(value) < 1:
266
+ return f'{value:.{precision}g}'
267
+
268
+ # 设置不同进制的单位和基数
269
+ units, base = {
270
+ 'K': (['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'], 1000),
271
+ 'KB': (['', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 1024),
272
+ 'KiB': (['', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'], 1024),
273
+ '万': (['', '万', '亿', '万亿', '亿亿'], 10000),
274
+ '秒': (['秒', [60, '分'], [60, '时'], [24, '天'], [7, '周'], [4.345, '月'], [12, '年']], 60),
275
+ }.get(base_type, ([''], 1)) # 默认为空单位和基数1
276
+
277
+ x, i = abs(value), 0
278
+ while x >= base and i < len(units) - 1:
279
+ x /= base
280
+ i += 1
281
+ if isinstance(units[i], list):
282
+ base = units[i][0]
283
+ units[i] = units[i][1]
284
+
285
+ x = f'{x:.{precision}g}' # 四舍五入到指定精度
286
+ prefix = '-' if value < 0 else '' # 负数处理
287
+ return f"{prefix}{x}{units[i]}"
288
+
289
+
290
+ class CvtType:
291
+ """ 这些系列的转换函数,转换失败统一报错为ValueError
292
+
293
+ 返回异常会比较好,否则返回None、False之类的,
294
+ 有时候可能就是要转换的值呢,会有歧义
295
+ """
296
+
297
+ @classmethod
298
+ def str2list(cls, arg):
299
+ try:
300
+ res = eval(arg)
301
+ except SyntaxError:
302
+ raise ValueError
303
+
304
+ if not isinstance(res, list):
305
+ raise ValueError
306
+
307
+ return res
308
+
309
+ @classmethod
310
+ def str2dict(cls, arg):
311
+ try:
312
+ res = eval(arg)
313
+ except SyntaxError:
314
+ raise ValueError
315
+
316
+ if not isinstance(res, dict):
317
+ raise ValueError
318
+
319
+ return res
320
+
321
+ @classmethod
322
+ def factory(cls, name):
323
+ """ 从字符串名称,映射到对应的转换函数 """
324
+ return {'int': int,
325
+ 'float': float,
326
+ 'str': str,
327
+ 'list': cls.str2list,
328
+ 'dict': cls.str2dict}.get(name, None)
329
+
330
+
331
+ def mod_minabs(x, m):
332
+ a = x % m
333
+ return a if a < m / 2 else a - m
334
+
335
+
336
+ class classproperty(property):
337
+ """ python - Using property() on classmethods - Stack Overflow
338
+ https://stackoverflow.com/questions/128573/using-property-on-classmethods
339
+ """
340
+
341
+ def __get__(self, obj, objtype=None):
342
+ return super(classproperty, self).__get__(objtype)
343
+
344
+ def __set__(self, obj, value):
345
+ super(classproperty, self).__set__(type(obj), value)
346
+
347
+ def __delete__(self, obj):
348
+ super(classproperty, self).__delete__(type(obj))
349
+
350
+
351
+ def decode_bitflags(n, flags, return_type=dict):
352
+ """ 解析一个位标记的功能
353
+
354
+ :param int n: 一个整数标记
355
+ :param list|tuple flags: 一个对应明文数组
356
+ flags[0]对应2**0的明文,flags[1]对应2**1的明文,以此类推
357
+ :param type return_type: 返回的数据类型
358
+ 默认dict,记录所有key的bool结果
359
+ 可以设set,只返回真的标记结果
360
+
361
+ >>> decode_bitflags(18, ('superscript', 'italic', 'serifed', 'monospaced', 'bold'))
362
+ {'superscript': 2, 'italic': 0, 'serifed': 0, 'monospaced': 16, 'bold': 0}
363
+ >>> decode_bitflags(18, ('superscript', 'italic', 'serifed', 'monospaced', 'bold'), set)
364
+ {'superscript', 'monospaced'}
365
+ """
366
+ if return_type == dict:
367
+ return {x: n & (2 << i) for i, x in enumerate(flags)}
368
+ elif return_type == set:
369
+ return {x for i, x in enumerate(flags) if (n & (2 << i))}
370
+ else:
371
+ raise ValueError
372
+
373
+
374
+ def xl_format_g(x, p=3):
375
+ """ 普通format的g模式不太满足我的需求,做了点修改
376
+
377
+ 注:g是比较方便的一种数值格式化方法,会比较智能地判断是否整数显示,或者普通显示、科学计数法显示
378
+
379
+ :param x: 数值x
380
+ """
381
+ s = f'{x:g}'
382
+ if 'e' in s:
383
+ # 如果变成了科学计数法,明确保留3位有效数字
384
+ return '{:.{}g}'.format(x, p=3)
385
+ else:
386
+ # 否则返回默认的g格式
387
+ return s
388
+
389
+
390
+ class EmptyWith:
391
+ """ 空上下文类
392
+ """
393
+
394
+ def __enter__(self):
395
+ pass
396
+
397
+ def __exit__(self, exc_type, exc_val, exc_tb):
398
+ pass
399
+
400
+
401
+ def funcmsg(func):
402
+ """输出函数func所在的文件、函数名、函数起始行"""
403
+ # showdir(func)
404
+ if not hasattr(func, '__name__'): # 没有__name__属性表示这很可能是一个装饰器去处理原函数了
405
+ if hasattr(func, 'func'): # 我的装饰器常用func成员存储原函数对象
406
+ func = func.func
407
+ else:
408
+ return f'装饰器:{type(func)},无法定位'
409
+ return f'函数名:{func.__name__},来自文件:{func.__code__.co_filename},所在行号={func.__code__.co_firstlineno}'
410
+
411
+
412
+ def set_global_var(name, value):
413
+ """ 设置某个全局变量,一般用在一些特殊的需要跨作用域进行调试的场景
414
+ 切勿!切勿!切勿用于正式功能,否则会导致难以维护控制的功能代码
415
+
416
+ 为了避免和某些关键的全局变量名冲突,这里的变量命令统一会加上pyxllib_的前缀
417
+ """
418
+ g = globals()
419
+ name = f'pyxllib_{name}'
420
+ g[name] = value
421
+
422
+
423
+ def get_global_var(name, default_value=None):
424
+ """ 获取某个全局变量的值 """
425
+ g = globals()
426
+ name = f'pyxllib_{name}'
427
+ if name not in g:
428
+ g[name] = default_value
429
+ return g[name]
430
+
431
+
432
+ def convert_to_json_compatible(d, custom_converter=None):
433
+ """ 递归地将字典等类型转换为JSON兼容格式。对于非标准JSON类型,使用自定义转换器或默认转换为字符串。
434
+
435
+ :param d: 要转换的字典或列表。
436
+ :param custom_converter: 自定义转换函数,用于处理非标准JSON类型的值。
437
+ :return: 转换后的字典或列表。
438
+
439
+ todo 这个函数想法是好的,但总感觉精确性中,总容易有些问题的,需要更多的考察测试
440
+ """
441
+ if custom_converter is None:
442
+ custom_converter = str
443
+
444
+ if isinstance(d, dict): # defaultdict呢?
445
+ return {k: convert_to_json_compatible(v, custom_converter) for k, v in d.items()}
446
+ elif isinstance(d, list):
447
+ return [convert_to_json_compatible(v, custom_converter) for v in d]
448
+ elif isinstance(d, (int, float, str, bool)) or d is None:
449
+ return d
450
+ else:
451
+ return custom_converter(d)