hutool-python 1.0.0__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.
- hutool/__init__.py +174 -0
- hutool/cache/__init__.py +7 -0
- hutool/cache/cache_util.py +47 -0
- hutool/cache/fifo_cache.py +87 -0
- hutool/cache/lfu_cache.py +129 -0
- hutool/cache/lru_cache.py +93 -0
- hutool/cache/timed_cache.py +115 -0
- hutool/captcha/__init__.py +3 -0
- hutool/captcha/captcha_util.py +215 -0
- hutool/core/__init__.py +23 -0
- hutool/core/_base.py +61 -0
- hutool/core/bean.py +214 -0
- hutool/core/codec.py +111 -0
- hutool/core/coll.py +635 -0
- hutool/core/date.py +1024 -0
- hutool/core/exceptions.py +66 -0
- hutool/core/io/__init__.py +0 -0
- hutool/core/io/data_size_util.py +79 -0
- hutool/core/io/file_name_util.py +111 -0
- hutool/core/io/file_util.py +650 -0
- hutool/core/io/io_util.py +133 -0
- hutool/core/io/path_util.py +247 -0
- hutool/core/io/resource_util.py +137 -0
- hutool/core/map.py +933 -0
- hutool/core/math_util.py +105 -0
- hutool/core/net.py +288 -0
- hutool/core/text/__init__.py +0 -0
- hutool/core/text/csv_util.py +54 -0
- hutool/core/text/str_builder.py +224 -0
- hutool/core/text/unicode_util.py +58 -0
- hutool/core/tree.py +242 -0
- hutool/core/util/__init__.py +63 -0
- hutool/core/util/array_util.py +503 -0
- hutool/core/util/boolean_util.py +124 -0
- hutool/core/util/charset_util.py +60 -0
- hutool/core/util/class_util.py +136 -0
- hutool/core/util/coordinate_util.py +186 -0
- hutool/core/util/credit_code_util.py +110 -0
- hutool/core/util/desensitized_util.py +194 -0
- hutool/core/util/enum_util.py +94 -0
- hutool/core/util/escape_util.py +97 -0
- hutool/core/util/hash_util.py +243 -0
- hutool/core/util/hex_util.py +140 -0
- hutool/core/util/id_util.py +147 -0
- hutool/core/util/idcard_util.py +300 -0
- hutool/core/util/number_util.py +720 -0
- hutool/core/util/object_util.py +294 -0
- hutool/core/util/page_util.py +61 -0
- hutool/core/util/phone_util.py +140 -0
- hutool/core/util/random_util.py +112 -0
- hutool/core/util/re_util.py +231 -0
- hutool/core/util/reflect_util.py +135 -0
- hutool/core/util/runtime_util.py +89 -0
- hutool/core/util/str_util.py +2320 -0
- hutool/core/util/system_util.py +62 -0
- hutool/core/util/url_util.py +232 -0
- hutool/core/util/version_util.py +41 -0
- hutool/core/util/xml_util.py +158 -0
- hutool/core/util/zip_util.py +126 -0
- hutool/cron/__init__.py +4 -0
- hutool/cron/cron_pattern.py +123 -0
- hutool/cron/cron_util.py +115 -0
- hutool/crypto/__init__.py +5 -0
- hutool/crypto/digest_util.py +167 -0
- hutool/crypto/secure_util.py +311 -0
- hutool/crypto/sign_util.py +74 -0
- hutool/dfa/__init__.py +3 -0
- hutool/dfa/sensitive_util.py +114 -0
- hutool/extra/__init__.py +6 -0
- hutool/extra/emoji_util.py +90 -0
- hutool/extra/pinyin_util.py +44 -0
- hutool/extra/qr_code_util.py +58 -0
- hutool/extra/template_util.py +41 -0
- hutool/http/__init__.py +6 -0
- hutool/http/html_util.py +88 -0
- hutool/http/http_request.py +188 -0
- hutool/http/http_response.py +139 -0
- hutool/http/http_util.py +237 -0
- hutool/json_util.py +251 -0
- hutool/jwt_util.py +57 -0
- hutool/setting/__init__.py +5 -0
- hutool/setting/props_util.py +79 -0
- hutool/setting/setting_util.py +80 -0
- hutool/setting/yaml_util.py +45 -0
- hutool_python-1.0.0.dist-info/LICENSE +127 -0
- hutool_python-1.0.0.dist-info/METADATA +438 -0
- hutool_python-1.0.0.dist-info/RECORD +89 -0
- hutool_python-1.0.0.dist-info/WHEEL +5 -0
- hutool_python-1.0.0.dist-info/top_level.txt +1 -0
hutool/core/coll.py
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
"""
|
|
2
|
+
集合与列表工具类
|
|
3
|
+
|
|
4
|
+
对应 Java cn.hutool.core.collection.CollUtil 和 ListUtil
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import copy
|
|
8
|
+
import random
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
Callable,
|
|
12
|
+
Iterable,
|
|
13
|
+
List,
|
|
14
|
+
Optional,
|
|
15
|
+
Sequence,
|
|
16
|
+
TypeVar,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T")
|
|
20
|
+
K = TypeVar("K")
|
|
21
|
+
V = TypeVar("V")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CollUtil:
|
|
25
|
+
"""集合工具类,对应 Java cn.hutool.core.collection.CollUtil"""
|
|
26
|
+
|
|
27
|
+
# ── 判断 ──────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def is_empty(coll) -> bool:
|
|
31
|
+
"""集合是否为空,None 也视为空。支持 list/tuple/set/dict/frozenset"""
|
|
32
|
+
if coll is None:
|
|
33
|
+
return True
|
|
34
|
+
return len(coll) == 0
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def is_not_empty(coll) -> bool:
|
|
38
|
+
"""集合是否为非空"""
|
|
39
|
+
return not CollUtil.is_empty(coll)
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def has_null(coll: Sequence) -> bool:
|
|
43
|
+
"""集合中是否有 None 元素"""
|
|
44
|
+
if coll is None:
|
|
45
|
+
return True
|
|
46
|
+
return any(element is None for element in coll)
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def contains(coll: Iterable, element: Any) -> bool:
|
|
50
|
+
"""是否包含指定元素"""
|
|
51
|
+
if coll is None:
|
|
52
|
+
return False
|
|
53
|
+
return element in coll
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def contains_any(coll: Iterable, *elements) -> bool:
|
|
57
|
+
"""是否包含任意一个"""
|
|
58
|
+
if coll is None or not elements:
|
|
59
|
+
return False
|
|
60
|
+
if isinstance(coll, (set, frozenset)):
|
|
61
|
+
return any(e in coll for e in elements)
|
|
62
|
+
coll_set = set(coll) if not isinstance(coll, set) else coll
|
|
63
|
+
return any(e in coll_set for e in elements)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def contains_all(coll: Iterable, *elements) -> bool:
|
|
67
|
+
"""是否包含全部"""
|
|
68
|
+
if coll is None:
|
|
69
|
+
return False
|
|
70
|
+
if not elements:
|
|
71
|
+
return True
|
|
72
|
+
coll_set = set(coll) if not isinstance(coll, set) else coll
|
|
73
|
+
return all(e in coll_set for e in elements)
|
|
74
|
+
|
|
75
|
+
# ── 创建 ──────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def new_array_list(*args) -> list:
|
|
79
|
+
"""新建 ArrayList(Python list),可传入初始元素"""
|
|
80
|
+
return list(args) if args else []
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def new_hash_set(*args) -> set:
|
|
84
|
+
"""新建 HashSet(Python set),可传入初始元素"""
|
|
85
|
+
return set(args) if args else set()
|
|
86
|
+
|
|
87
|
+
# ── 转换 ──────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def to_list(iterable: Iterable) -> list:
|
|
91
|
+
"""转为列表"""
|
|
92
|
+
if iterable is None:
|
|
93
|
+
return []
|
|
94
|
+
if isinstance(iterable, list):
|
|
95
|
+
return list(iterable)
|
|
96
|
+
return list(iterable)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def to_set(iterable: Iterable) -> set:
|
|
100
|
+
"""转为集合"""
|
|
101
|
+
if iterable is None:
|
|
102
|
+
return set()
|
|
103
|
+
return set(iterable)
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def to_map(
|
|
107
|
+
items: Sequence,
|
|
108
|
+
key_func: Callable,
|
|
109
|
+
value_func: Optional[Callable] = None,
|
|
110
|
+
) -> dict:
|
|
111
|
+
"""列表转 Map,通过 key_func 提取键,value_func 提取值(默认值为元素本身)
|
|
112
|
+
|
|
113
|
+
:param items: 待转换的列表
|
|
114
|
+
:param key_func: 键提取函数
|
|
115
|
+
:param value_func: 值提取函数,默认为元素本身
|
|
116
|
+
:return: 字典
|
|
117
|
+
"""
|
|
118
|
+
if items is None:
|
|
119
|
+
return {}
|
|
120
|
+
if value_func is None:
|
|
121
|
+
def value_func(x):
|
|
122
|
+
return x
|
|
123
|
+
return {key_func(item): value_func(item) for item in items}
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def group_by(coll: Iterable, key_func: Callable) -> dict:
|
|
127
|
+
"""按条件分组,返回 {key: [items]}
|
|
128
|
+
|
|
129
|
+
:param coll: 待分组集合
|
|
130
|
+
:param key_func: 分组键提取函数
|
|
131
|
+
:return: 分组字典
|
|
132
|
+
"""
|
|
133
|
+
result: dict = {}
|
|
134
|
+
if coll is None:
|
|
135
|
+
return result
|
|
136
|
+
for item in coll:
|
|
137
|
+
key = key_func(item)
|
|
138
|
+
result.setdefault(key, []).append(item)
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def partition(coll: List[T], size: int) -> List[List[T]]:
|
|
143
|
+
"""将列表按指定大小分割为子列表
|
|
144
|
+
|
|
145
|
+
:param coll: 待分割列表
|
|
146
|
+
:param size: 每个子列表的最大大小
|
|
147
|
+
:return: 分割后的二维列表
|
|
148
|
+
:raises ValueError: size 小于等于 0
|
|
149
|
+
"""
|
|
150
|
+
if coll is None:
|
|
151
|
+
return []
|
|
152
|
+
if size <= 0:
|
|
153
|
+
raise ValueError(f"size 必须大于 0,当前值: {size}")
|
|
154
|
+
return [coll[i : i + size] for i in range(0, len(coll), size)]
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def zip_list(keys: list, values: list) -> dict:
|
|
158
|
+
"""两个列表合并为字典
|
|
159
|
+
|
|
160
|
+
:param keys: 键列表
|
|
161
|
+
:param values: 值列表
|
|
162
|
+
:return: 合并后的字典
|
|
163
|
+
"""
|
|
164
|
+
if keys is None or values is None:
|
|
165
|
+
return {}
|
|
166
|
+
return dict(zip(keys, values))
|
|
167
|
+
|
|
168
|
+
# ── 操作 ──────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def add_all(coll: list, *elements) -> list:
|
|
172
|
+
"""添加所有元素到列表,返回新列表
|
|
173
|
+
|
|
174
|
+
:param coll: 原列表
|
|
175
|
+
:param elements: 要添加的元素
|
|
176
|
+
:return: 新列表
|
|
177
|
+
"""
|
|
178
|
+
result = list(coll) if coll else []
|
|
179
|
+
result.extend(elements)
|
|
180
|
+
return result
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def remove_null(coll: list) -> list:
|
|
184
|
+
"""移除所有 None 元素,返回新列表
|
|
185
|
+
|
|
186
|
+
:param coll: 原列表
|
|
187
|
+
:return: 不含 None 的新列表
|
|
188
|
+
"""
|
|
189
|
+
if coll is None:
|
|
190
|
+
return []
|
|
191
|
+
return [item for item in coll if item is not None]
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def filter(coll: List[T], filter_func: Callable[[T], bool]) -> List[T]:
|
|
195
|
+
"""过滤集合
|
|
196
|
+
|
|
197
|
+
:param coll: 待过滤列表
|
|
198
|
+
:param filter_func: 过滤函数,返回 True 保留
|
|
199
|
+
:return: 过滤后的新列表
|
|
200
|
+
"""
|
|
201
|
+
if coll is None:
|
|
202
|
+
return []
|
|
203
|
+
return [item for item in coll if filter_func(item)]
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def map_list(coll: List[T], map_func: Callable[[T], V]) -> List[V]:
|
|
207
|
+
"""映射集合
|
|
208
|
+
|
|
209
|
+
:param coll: 待映射列表
|
|
210
|
+
:param map_func: 映射函数
|
|
211
|
+
:return: 映射后的新列表
|
|
212
|
+
"""
|
|
213
|
+
if coll is None:
|
|
214
|
+
return []
|
|
215
|
+
return [map_func(item) for item in coll]
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def flat_map(coll: Iterable, map_func: Callable) -> list:
|
|
219
|
+
"""flatMap — 先映射再展平
|
|
220
|
+
|
|
221
|
+
:param coll: 待处理集合
|
|
222
|
+
:param map_func: 映射函数,返回可迭代对象
|
|
223
|
+
:return: 展平后的新列表
|
|
224
|
+
"""
|
|
225
|
+
if coll is None:
|
|
226
|
+
return []
|
|
227
|
+
result: list = []
|
|
228
|
+
for item in coll:
|
|
229
|
+
mapped = map_func(item)
|
|
230
|
+
if mapped is not None:
|
|
231
|
+
result.extend(mapped)
|
|
232
|
+
return result
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def distinct(coll: List[T]) -> List[T]:
|
|
236
|
+
"""去重,保持顺序
|
|
237
|
+
|
|
238
|
+
:param coll: 原列表
|
|
239
|
+
:return: 去重后的新列表
|
|
240
|
+
"""
|
|
241
|
+
if coll is None:
|
|
242
|
+
return []
|
|
243
|
+
seen: set = set()
|
|
244
|
+
result: List[T] = []
|
|
245
|
+
for item in coll:
|
|
246
|
+
try:
|
|
247
|
+
if item not in seen:
|
|
248
|
+
seen.add(item)
|
|
249
|
+
result.append(item)
|
|
250
|
+
except TypeError:
|
|
251
|
+
# 不可哈希元素退化为 O(n) 线性查找
|
|
252
|
+
if item not in result:
|
|
253
|
+
result.append(item)
|
|
254
|
+
return result
|
|
255
|
+
|
|
256
|
+
@staticmethod
|
|
257
|
+
def sort(
|
|
258
|
+
coll: List[T],
|
|
259
|
+
key_func: Optional[Callable] = None,
|
|
260
|
+
reverse: bool = False,
|
|
261
|
+
) -> List[T]:
|
|
262
|
+
"""排序,返回新列表
|
|
263
|
+
|
|
264
|
+
:param coll: 待排序列表
|
|
265
|
+
:param key_func: 排序键函数
|
|
266
|
+
:param reverse: 是否降序
|
|
267
|
+
:return: 排序后的新列表
|
|
268
|
+
"""
|
|
269
|
+
if coll is None:
|
|
270
|
+
return []
|
|
271
|
+
return sorted(coll, key=key_func, reverse=reverse)
|
|
272
|
+
|
|
273
|
+
@staticmethod
|
|
274
|
+
def reverse(coll: List[T]) -> List[T]:
|
|
275
|
+
"""反转,返回新列表
|
|
276
|
+
|
|
277
|
+
:param coll: 原列表
|
|
278
|
+
:return: 反转后的新列表
|
|
279
|
+
"""
|
|
280
|
+
if coll is None:
|
|
281
|
+
return []
|
|
282
|
+
return list(reversed(coll))
|
|
283
|
+
|
|
284
|
+
@staticmethod
|
|
285
|
+
def shuffle(coll: List[T]) -> List[T]:
|
|
286
|
+
"""随机打乱,返回新列表
|
|
287
|
+
|
|
288
|
+
:param coll: 原列表
|
|
289
|
+
:return: 打乱后的新列表
|
|
290
|
+
"""
|
|
291
|
+
if coll is None:
|
|
292
|
+
return []
|
|
293
|
+
result = list(coll)
|
|
294
|
+
random.shuffle(result)
|
|
295
|
+
return result
|
|
296
|
+
|
|
297
|
+
@staticmethod
|
|
298
|
+
def min_val(coll: Iterable) -> Any:
|
|
299
|
+
"""取最小值
|
|
300
|
+
|
|
301
|
+
:param coll: 可迭代集合
|
|
302
|
+
:return: 最小值
|
|
303
|
+
:raises ValueError: 集合为空
|
|
304
|
+
"""
|
|
305
|
+
if coll is None:
|
|
306
|
+
raise ValueError("集合为 None")
|
|
307
|
+
items = list(coll)
|
|
308
|
+
if not items:
|
|
309
|
+
raise ValueError("集合为空")
|
|
310
|
+
return min(items)
|
|
311
|
+
|
|
312
|
+
@staticmethod
|
|
313
|
+
def max_val(coll: Iterable) -> Any:
|
|
314
|
+
"""取最大值
|
|
315
|
+
|
|
316
|
+
:param coll: 可迭代集合
|
|
317
|
+
:return: 最大值
|
|
318
|
+
:raises ValueError: 集合为空
|
|
319
|
+
"""
|
|
320
|
+
if coll is None:
|
|
321
|
+
raise ValueError("集合为 None")
|
|
322
|
+
items = list(coll)
|
|
323
|
+
if not items:
|
|
324
|
+
raise ValueError("集合为空")
|
|
325
|
+
return max(items)
|
|
326
|
+
|
|
327
|
+
@staticmethod
|
|
328
|
+
def count(coll: Iterable, predicate: Callable) -> int:
|
|
329
|
+
"""统计满足条件的元素个数
|
|
330
|
+
|
|
331
|
+
:param coll: 可迭代集合
|
|
332
|
+
:param predicate: 判断条件
|
|
333
|
+
:return: 满足条件的元素数量
|
|
334
|
+
"""
|
|
335
|
+
if coll is None:
|
|
336
|
+
return 0
|
|
337
|
+
return sum(1 for item in coll if predicate(item))
|
|
338
|
+
|
|
339
|
+
@staticmethod
|
|
340
|
+
def find_first(
|
|
341
|
+
coll: Iterable,
|
|
342
|
+
predicate: Callable,
|
|
343
|
+
) -> Optional[T]:
|
|
344
|
+
"""查找第一个满足条件的元素
|
|
345
|
+
|
|
346
|
+
:param coll: 可迭代集合
|
|
347
|
+
:param predicate: 判断条件
|
|
348
|
+
:return: 第一个满足条件的元素,未找到返回 None
|
|
349
|
+
"""
|
|
350
|
+
if coll is None:
|
|
351
|
+
return None
|
|
352
|
+
for item in coll:
|
|
353
|
+
if predicate(item):
|
|
354
|
+
return item
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
@staticmethod
|
|
358
|
+
def find_last(
|
|
359
|
+
coll: List[T],
|
|
360
|
+
predicate: Callable[[T], bool],
|
|
361
|
+
) -> Optional[T]:
|
|
362
|
+
"""查找最后一个满足条件的元素
|
|
363
|
+
|
|
364
|
+
:param coll: 列表
|
|
365
|
+
:param predicate: 判断条件
|
|
366
|
+
:return: 最后一个满足条件的元素,未找到返回 None
|
|
367
|
+
"""
|
|
368
|
+
if coll is None:
|
|
369
|
+
return None
|
|
370
|
+
for item in reversed(coll):
|
|
371
|
+
if predicate(item):
|
|
372
|
+
return item
|
|
373
|
+
return None
|
|
374
|
+
|
|
375
|
+
@staticmethod
|
|
376
|
+
def any_match(coll: Iterable, predicate: Callable) -> bool:
|
|
377
|
+
"""是否任意一个匹配
|
|
378
|
+
|
|
379
|
+
:param coll: 可迭代集合
|
|
380
|
+
:param predicate: 判断条件
|
|
381
|
+
:return: 是否有任一元素满足条件
|
|
382
|
+
"""
|
|
383
|
+
if coll is None:
|
|
384
|
+
return False
|
|
385
|
+
return any(predicate(item) for item in coll)
|
|
386
|
+
|
|
387
|
+
@staticmethod
|
|
388
|
+
def all_match(coll: Iterable, predicate: Callable) -> bool:
|
|
389
|
+
"""是否全部匹配
|
|
390
|
+
|
|
391
|
+
:param coll: 可迭代集合
|
|
392
|
+
:param predicate: 判断条件
|
|
393
|
+
:return: 是否所有元素都满足条件
|
|
394
|
+
"""
|
|
395
|
+
if coll is None:
|
|
396
|
+
return True
|
|
397
|
+
return all(predicate(item) for item in coll)
|
|
398
|
+
|
|
399
|
+
@staticmethod
|
|
400
|
+
def none_match(coll: Iterable, predicate: Callable) -> bool:
|
|
401
|
+
"""是否无匹配
|
|
402
|
+
|
|
403
|
+
:param coll: 可迭代集合
|
|
404
|
+
:param predicate: 判断条件
|
|
405
|
+
:return: 是否没有元素满足条件
|
|
406
|
+
"""
|
|
407
|
+
return not CollUtil.any_match(coll, predicate)
|
|
408
|
+
|
|
409
|
+
# ── 工具 ──────────────────────────────────────────────
|
|
410
|
+
|
|
411
|
+
@staticmethod
|
|
412
|
+
def join(coll: Iterable, separator: str = ",") -> str:
|
|
413
|
+
"""用连接符连接集合元素
|
|
414
|
+
|
|
415
|
+
:param coll: 可迭代集合
|
|
416
|
+
:param separator: 分隔符
|
|
417
|
+
:return: 连接后的字符串
|
|
418
|
+
"""
|
|
419
|
+
if coll is None:
|
|
420
|
+
return ""
|
|
421
|
+
return separator.join(str(item) for item in coll)
|
|
422
|
+
|
|
423
|
+
@staticmethod
|
|
424
|
+
def get_first(coll: Iterable) -> Optional[T]:
|
|
425
|
+
"""获取第一个元素
|
|
426
|
+
|
|
427
|
+
:param coll: 可迭代集合
|
|
428
|
+
:return: 第一个元素,为空时返回 None
|
|
429
|
+
"""
|
|
430
|
+
if coll is None:
|
|
431
|
+
return None
|
|
432
|
+
for item in coll:
|
|
433
|
+
return item
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
@staticmethod
|
|
437
|
+
def get_last(coll: List[T]) -> Optional[T]:
|
|
438
|
+
"""获取最后一个元素
|
|
439
|
+
|
|
440
|
+
:param coll: 列表
|
|
441
|
+
:return: 最后一个元素,为空时返回 None
|
|
442
|
+
"""
|
|
443
|
+
if coll is None or len(coll) == 0:
|
|
444
|
+
return None
|
|
445
|
+
return coll[-1]
|
|
446
|
+
|
|
447
|
+
@staticmethod
|
|
448
|
+
def sub_list(coll: List[T], start: int, end: int) -> List[T]:
|
|
449
|
+
"""获取子列表
|
|
450
|
+
|
|
451
|
+
:param coll: 原列表
|
|
452
|
+
:param start: 起始索引(包含)
|
|
453
|
+
:param end: 结束索引(不包含)
|
|
454
|
+
:return: 子列表
|
|
455
|
+
"""
|
|
456
|
+
if coll is None:
|
|
457
|
+
return []
|
|
458
|
+
return coll[start:end]
|
|
459
|
+
|
|
460
|
+
@staticmethod
|
|
461
|
+
def page(coll: List[T], page_num: int, page_size: int) -> List[T]:
|
|
462
|
+
"""分页,page_num 从 1 开始
|
|
463
|
+
|
|
464
|
+
:param coll: 原列表
|
|
465
|
+
:param page_num: 页码(从 1 开始)
|
|
466
|
+
:param page_size: 每页大小
|
|
467
|
+
:return: 当前页数据
|
|
468
|
+
:raises ValueError: 参数非法
|
|
469
|
+
"""
|
|
470
|
+
if coll is None:
|
|
471
|
+
return []
|
|
472
|
+
if page_num < 1:
|
|
473
|
+
raise ValueError(f"page_num 必须 >= 1,当前值: {page_num}")
|
|
474
|
+
if page_size < 1:
|
|
475
|
+
raise ValueError(f"page_size 必须 >= 1,当前值: {page_size}")
|
|
476
|
+
start = (page_num - 1) * page_size
|
|
477
|
+
end = start + page_size
|
|
478
|
+
return coll[start:end]
|
|
479
|
+
|
|
480
|
+
@staticmethod
|
|
481
|
+
def for_each(coll: Iterable, consumer: Callable) -> None:
|
|
482
|
+
"""遍历集合并对每个元素执行操作
|
|
483
|
+
|
|
484
|
+
:param coll: 可迭代集合
|
|
485
|
+
:param consumer: 消费函数
|
|
486
|
+
"""
|
|
487
|
+
if coll is None:
|
|
488
|
+
return
|
|
489
|
+
for item in coll:
|
|
490
|
+
consumer(item)
|
|
491
|
+
|
|
492
|
+
@staticmethod
|
|
493
|
+
def empty_if_none(coll) -> list:
|
|
494
|
+
"""None 转空列表
|
|
495
|
+
|
|
496
|
+
:param coll: 可能为 None 的集合
|
|
497
|
+
:return: 原集合或空列表
|
|
498
|
+
"""
|
|
499
|
+
if coll is None:
|
|
500
|
+
return []
|
|
501
|
+
return list(coll)
|
|
502
|
+
|
|
503
|
+
@staticmethod
|
|
504
|
+
def default_if_empty(coll: list, default: list) -> list:
|
|
505
|
+
"""如果为空则返回默认值
|
|
506
|
+
|
|
507
|
+
:param coll: 可能为空的列表
|
|
508
|
+
:param default: 默认列表
|
|
509
|
+
:return: 原列表或默认列表
|
|
510
|
+
"""
|
|
511
|
+
if CollUtil.is_empty(coll):
|
|
512
|
+
return default if default is not None else []
|
|
513
|
+
return coll
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
class ListUtil:
|
|
517
|
+
"""列表工具类"""
|
|
518
|
+
|
|
519
|
+
@staticmethod
|
|
520
|
+
def sub(lst: List[T], start: int, end: int) -> List[T]:
|
|
521
|
+
"""获取子列表,支持负索引
|
|
522
|
+
|
|
523
|
+
:param lst: 原列表
|
|
524
|
+
:param start: 起始索引(包含),支持负数
|
|
525
|
+
:param end: 结束索引(不包含),支持负数
|
|
526
|
+
:return: 子列表
|
|
527
|
+
"""
|
|
528
|
+
if lst is None:
|
|
529
|
+
return []
|
|
530
|
+
return lst[start:end]
|
|
531
|
+
|
|
532
|
+
@staticmethod
|
|
533
|
+
def page(lst: List[T], page_num: int, page_size: int) -> List[T]:
|
|
534
|
+
"""分页
|
|
535
|
+
|
|
536
|
+
:param lst: 原列表
|
|
537
|
+
:param page_num: 页码(从 1 开始)
|
|
538
|
+
:param page_size: 每页大小
|
|
539
|
+
:return: 当前页数据
|
|
540
|
+
"""
|
|
541
|
+
return CollUtil.page(lst, page_num, page_size)
|
|
542
|
+
|
|
543
|
+
@staticmethod
|
|
544
|
+
def empty_if_none(lst: Optional[List[T]]) -> List[T]:
|
|
545
|
+
"""None 转空列表
|
|
546
|
+
|
|
547
|
+
:param lst: 可能为 None 的列表
|
|
548
|
+
:return: 原列表或空列表
|
|
549
|
+
"""
|
|
550
|
+
if lst is None:
|
|
551
|
+
return []
|
|
552
|
+
return lst
|
|
553
|
+
|
|
554
|
+
@staticmethod
|
|
555
|
+
def default_if_empty(
|
|
556
|
+
lst: Optional[List[T]],
|
|
557
|
+
default: List[T],
|
|
558
|
+
) -> List[T]:
|
|
559
|
+
"""如果为空则返回默认值
|
|
560
|
+
|
|
561
|
+
:param lst: 可能为空的列表
|
|
562
|
+
:param default: 默认列表
|
|
563
|
+
:return: 原列表或默认列表
|
|
564
|
+
"""
|
|
565
|
+
return CollUtil.default_if_empty(lst, default)
|
|
566
|
+
|
|
567
|
+
@staticmethod
|
|
568
|
+
def to_tree(
|
|
569
|
+
lst: List[dict],
|
|
570
|
+
id_field: str = "id",
|
|
571
|
+
parent_field: str = "parentId",
|
|
572
|
+
children_field: str = "children",
|
|
573
|
+
) -> List[dict]:
|
|
574
|
+
"""列表转树结构
|
|
575
|
+
|
|
576
|
+
将扁平的、通过 parentId 互相引用的字典列表转换为嵌套树形结构。
|
|
577
|
+
根节点的 parent_field 值为 None / 0 / 空字符串 / 不存在。
|
|
578
|
+
|
|
579
|
+
:param lst: 扁平字典列表
|
|
580
|
+
:param id_field: 主键字段名
|
|
581
|
+
:param parent_field: 父级引用字段名
|
|
582
|
+
:param children_field: 子节点列表字段名
|
|
583
|
+
:return: 树形结构的根节点列表(深拷贝,不修改原数据)
|
|
584
|
+
"""
|
|
585
|
+
if not lst:
|
|
586
|
+
return []
|
|
587
|
+
|
|
588
|
+
# 深拷贝避免修改原始数据
|
|
589
|
+
items = copy.deepcopy(lst)
|
|
590
|
+
|
|
591
|
+
# 建立 id -> node 映射,同时初始化 children
|
|
592
|
+
node_map: dict = {}
|
|
593
|
+
for item in items:
|
|
594
|
+
item.setdefault(children_field, [])
|
|
595
|
+
node_map[item[id_field]] = item
|
|
596
|
+
|
|
597
|
+
# 根节点判定:parent 为 None、0、"" 或字段不存在
|
|
598
|
+
root_values = {None, 0, ""}
|
|
599
|
+
|
|
600
|
+
roots: List[dict] = []
|
|
601
|
+
for item in items:
|
|
602
|
+
parent_val = item.get(parent_field)
|
|
603
|
+
if parent_val in root_values or parent_val not in node_map:
|
|
604
|
+
# 是根节点(parent 为空、或 parent 引用不存在的节点也视为根)
|
|
605
|
+
roots.append(item)
|
|
606
|
+
else:
|
|
607
|
+
# 挂到父节点的 children
|
|
608
|
+
node_map[parent_val][children_field].append(item)
|
|
609
|
+
|
|
610
|
+
return roots
|
|
611
|
+
|
|
612
|
+
@staticmethod
|
|
613
|
+
def sort_by_property(
|
|
614
|
+
lst: List,
|
|
615
|
+
prop_name: str,
|
|
616
|
+
reverse: bool = False,
|
|
617
|
+
) -> List:
|
|
618
|
+
"""按属性排序
|
|
619
|
+
|
|
620
|
+
适用于元素为字典或对象的列表。字典按键取值,对象按 getattr 取值。
|
|
621
|
+
|
|
622
|
+
:param lst: 待排序列表
|
|
623
|
+
:param prop_name: 属性名
|
|
624
|
+
:param reverse: 是否降序
|
|
625
|
+
:return: 排序后的新列表
|
|
626
|
+
"""
|
|
627
|
+
if lst is None:
|
|
628
|
+
return []
|
|
629
|
+
|
|
630
|
+
def _key(item):
|
|
631
|
+
if isinstance(item, dict):
|
|
632
|
+
return item.get(prop_name)
|
|
633
|
+
return getattr(item, prop_name, None)
|
|
634
|
+
|
|
635
|
+
return sorted(lst, key=_key, reverse=reverse)
|