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/map.py
ADDED
|
@@ -0,0 +1,933 @@
|
|
|
1
|
+
"""字典工具类,对应 Java cn.hutool.core.map.MapUtil / BiMap / MapWrapper。
|
|
2
|
+
|
|
3
|
+
包含:
|
|
4
|
+
- MapUtil : 静态工具方法集合
|
|
5
|
+
- MapWrapper : MutableMapping 包装基类
|
|
6
|
+
- BiMap : 双向字典(键↔值)
|
|
7
|
+
- FuncKeyDict : 自定义键函数字典
|
|
8
|
+
- DictUtil : 字典工具方法集合(兼容旧接口)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections import OrderedDict
|
|
14
|
+
from typing import (
|
|
15
|
+
Any,
|
|
16
|
+
Callable,
|
|
17
|
+
Dict,
|
|
18
|
+
List,
|
|
19
|
+
Optional,
|
|
20
|
+
Sequence,
|
|
21
|
+
Tuple,
|
|
22
|
+
Type,
|
|
23
|
+
TypeVar,
|
|
24
|
+
Union,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from ._base import DefaultParam
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"BiMap",
|
|
31
|
+
"DictUtil",
|
|
32
|
+
"FuncKeyDict",
|
|
33
|
+
"MapUtil",
|
|
34
|
+
"MapWrapper",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
_K = TypeVar("_K")
|
|
38
|
+
_V = TypeVar("_V")
|
|
39
|
+
_DEFAULT_PARAM = DefaultParam()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ====================================================================
|
|
43
|
+
# MapUtil
|
|
44
|
+
# ====================================================================
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MapUtil:
|
|
48
|
+
"""Map工具类,对应 Java cn.hutool.core.map.MapUtil"""
|
|
49
|
+
|
|
50
|
+
# ----------------------------------------------------------------
|
|
51
|
+
# 判断
|
|
52
|
+
# ----------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def is_empty(m: dict) -> bool:
|
|
56
|
+
"""字典是否为空"""
|
|
57
|
+
return m is None or len(m) == 0
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def is_not_empty(m: dict) -> bool:
|
|
61
|
+
"""字典是否为非空"""
|
|
62
|
+
return not MapUtil.is_empty(m)
|
|
63
|
+
|
|
64
|
+
# ----------------------------------------------------------------
|
|
65
|
+
# 创建
|
|
66
|
+
# ----------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def new_hash_map(*args, **kwargs) -> dict:
|
|
70
|
+
"""新建HashMap"""
|
|
71
|
+
return dict(*args, **kwargs)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def new_linked_hash_map(*args, **kwargs) -> OrderedDict:
|
|
75
|
+
"""新建LinkedHashMap(有序字典)"""
|
|
76
|
+
return OrderedDict(*args, **kwargs)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def of(key, value) -> dict:
|
|
80
|
+
"""创建单键值对字典
|
|
81
|
+
|
|
82
|
+
:param key: 键
|
|
83
|
+
:param value: 值
|
|
84
|
+
:return: 单键值对字典
|
|
85
|
+
"""
|
|
86
|
+
return {key: value}
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def of_entries(*entries) -> dict:
|
|
90
|
+
"""通过键值对元组创建字典
|
|
91
|
+
|
|
92
|
+
:param entries: (key, value) 元组序列
|
|
93
|
+
:return: 字典
|
|
94
|
+
"""
|
|
95
|
+
return dict(entries)
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def of_array(array: list) -> dict:
|
|
99
|
+
"""通过数组创建字典,如 [k1, v1, k2, v2, ...]
|
|
100
|
+
|
|
101
|
+
:param array: 交替排列的键值列表,长度必须为偶数
|
|
102
|
+
:return: 字典
|
|
103
|
+
"""
|
|
104
|
+
if array is None:
|
|
105
|
+
return {}
|
|
106
|
+
if len(array) % 2 != 0:
|
|
107
|
+
raise ValueError("数组长度必须为偶数")
|
|
108
|
+
result = {}
|
|
109
|
+
for i in range(0, len(array), 2):
|
|
110
|
+
result[array[i]] = array[i + 1]
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def create_map(map_type: Optional[Type] = None) -> dict:
|
|
115
|
+
"""根据类型创建字典实例
|
|
116
|
+
|
|
117
|
+
:param map_type: 字典类型
|
|
118
|
+
:return: 字典实例
|
|
119
|
+
"""
|
|
120
|
+
if map_type is None:
|
|
121
|
+
return {}
|
|
122
|
+
try:
|
|
123
|
+
return map_type()
|
|
124
|
+
except Exception:
|
|
125
|
+
return {}
|
|
126
|
+
|
|
127
|
+
# ----------------------------------------------------------------
|
|
128
|
+
# 转换
|
|
129
|
+
# ----------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def to_list_map(dict_list: Sequence[Dict[_K, _V]]) -> Dict[_K, List[_V]]:
|
|
133
|
+
"""行转列,合并相同的键,值合并为列表
|
|
134
|
+
|
|
135
|
+
例如: [{'a':1,'b':1}, {'a':2,'b':2}] -> {'a':[1,2], 'b':[1,2]}
|
|
136
|
+
|
|
137
|
+
:param dict_list: 字典列表
|
|
138
|
+
:return: 键到列表的映射
|
|
139
|
+
"""
|
|
140
|
+
result: Dict[_K, List[_V]] = {}
|
|
141
|
+
if dict_list is None:
|
|
142
|
+
return result
|
|
143
|
+
for item in dict_list:
|
|
144
|
+
if item is None:
|
|
145
|
+
continue
|
|
146
|
+
for key, value in item.items():
|
|
147
|
+
result.setdefault(key, []).append(value)
|
|
148
|
+
return result
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
def to_dict_list(list_dict: Dict[_K, Sequence[_V]]) -> List[Dict[_K, _V]]:
|
|
152
|
+
"""列转行
|
|
153
|
+
|
|
154
|
+
例如: {'a':[1,2], 'b':[1,2]} -> [{'a':1,'b':1}, {'a':2,'b':2}]
|
|
155
|
+
|
|
156
|
+
:param list_dict: 键到列表的映射
|
|
157
|
+
:return: 字典列表
|
|
158
|
+
"""
|
|
159
|
+
result: List[Dict[_K, _V]] = []
|
|
160
|
+
if MapUtil.is_empty(list_dict):
|
|
161
|
+
return result
|
|
162
|
+
keys = list(list_dict.keys())
|
|
163
|
+
max_length = max(len(list_dict[key]) for key in keys)
|
|
164
|
+
for i in range(max_length):
|
|
165
|
+
item: Dict[_K, _V] = {}
|
|
166
|
+
for key in keys:
|
|
167
|
+
if i < len(list_dict[key]):
|
|
168
|
+
item[key] = list_dict[key][i]
|
|
169
|
+
result.append(item)
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def join(m: dict, separator: str = "&", kv_separator: str = "=") -> str:
|
|
174
|
+
"""字典转字符串连接
|
|
175
|
+
|
|
176
|
+
:param m: 字典
|
|
177
|
+
:param separator: 键值对之间的分隔符
|
|
178
|
+
:param kv_separator: 键与值之间的分隔符
|
|
179
|
+
:return: 连接后的字符串
|
|
180
|
+
"""
|
|
181
|
+
if MapUtil.is_empty(m):
|
|
182
|
+
return ""
|
|
183
|
+
return separator.join(f"{k}{kv_separator}{v}" for k, v in m.items())
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
def sort_join(
|
|
187
|
+
params: dict,
|
|
188
|
+
separator: str = "&",
|
|
189
|
+
kv_separator: str = "=",
|
|
190
|
+
) -> str:
|
|
191
|
+
"""字典按键排序后转字符串连接
|
|
192
|
+
|
|
193
|
+
:param params: 字典
|
|
194
|
+
:param separator: 键值对之间的分隔符
|
|
195
|
+
:param kv_separator: 键与值之间的分隔符
|
|
196
|
+
:return: 排序后连接的字符串
|
|
197
|
+
"""
|
|
198
|
+
if MapUtil.is_empty(params):
|
|
199
|
+
return ""
|
|
200
|
+
return separator.join(f"{k}{kv_separator}{params[k]}" for k in sorted(params.keys()))
|
|
201
|
+
|
|
202
|
+
# ----------------------------------------------------------------
|
|
203
|
+
# 操作
|
|
204
|
+
# ----------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def filter(m: dict, *keys) -> dict:
|
|
208
|
+
"""保留指定key的键值对,返回新字典
|
|
209
|
+
|
|
210
|
+
:param m: 字典
|
|
211
|
+
:param keys: 要保留的键
|
|
212
|
+
:return: 新字典
|
|
213
|
+
"""
|
|
214
|
+
if MapUtil.is_empty(m):
|
|
215
|
+
return {}
|
|
216
|
+
return {k: m[k] for k in keys if k in m}
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def filter_by_func(m: dict, filter_func: Callable) -> dict:
|
|
220
|
+
"""按条件过滤字典
|
|
221
|
+
|
|
222
|
+
filter_func 接收 (key, value) 参数,返回 True 保留。
|
|
223
|
+
|
|
224
|
+
:param m: 字典
|
|
225
|
+
:param filter_func: 过滤函数 (key, value) -> bool
|
|
226
|
+
:return: 过滤后的新字典
|
|
227
|
+
"""
|
|
228
|
+
if MapUtil.is_empty(m):
|
|
229
|
+
return {}
|
|
230
|
+
return {k: v for k, v in m.items() if filter_func(k, v)}
|
|
231
|
+
|
|
232
|
+
@staticmethod
|
|
233
|
+
def map_values(m: dict, map_func: Callable) -> dict:
|
|
234
|
+
"""映射所有值
|
|
235
|
+
|
|
236
|
+
:param m: 字典
|
|
237
|
+
:param map_func: 映射函数 value -> new_value
|
|
238
|
+
:return: 值映射后的新字典
|
|
239
|
+
"""
|
|
240
|
+
if MapUtil.is_empty(m):
|
|
241
|
+
return {}
|
|
242
|
+
return {k: map_func(v) for k, v in m.items()}
|
|
243
|
+
|
|
244
|
+
@staticmethod
|
|
245
|
+
def sort(m: dict) -> OrderedDict:
|
|
246
|
+
"""按键排序,返回新OrderedDict
|
|
247
|
+
|
|
248
|
+
:param m: 字典
|
|
249
|
+
:return: 排序后的有序字典
|
|
250
|
+
"""
|
|
251
|
+
if MapUtil.is_empty(m):
|
|
252
|
+
return OrderedDict()
|
|
253
|
+
return OrderedDict(sorted(m.items()))
|
|
254
|
+
|
|
255
|
+
@staticmethod
|
|
256
|
+
def sort_by_value(m: dict, reverse: bool = False) -> OrderedDict:
|
|
257
|
+
"""按值排序
|
|
258
|
+
|
|
259
|
+
:param m: 字典
|
|
260
|
+
:param reverse: 是否降序
|
|
261
|
+
:return: 排序后的有序字典
|
|
262
|
+
"""
|
|
263
|
+
if MapUtil.is_empty(m):
|
|
264
|
+
return OrderedDict()
|
|
265
|
+
return OrderedDict(sorted(m.items(), key=lambda item: item[1], reverse=reverse))
|
|
266
|
+
|
|
267
|
+
@staticmethod
|
|
268
|
+
def inverse(m: dict) -> dict:
|
|
269
|
+
"""键值互换
|
|
270
|
+
|
|
271
|
+
互换键值对不检查值是否有重复,后加入的元素替换先加入的元素。
|
|
272
|
+
|
|
273
|
+
:param m: 字典
|
|
274
|
+
:return: 键值互换后的字典
|
|
275
|
+
"""
|
|
276
|
+
if MapUtil.is_empty(m):
|
|
277
|
+
return {}
|
|
278
|
+
result = MapUtil.create_map(type(m))
|
|
279
|
+
for k, v in m.items():
|
|
280
|
+
result[v] = k
|
|
281
|
+
return result
|
|
282
|
+
|
|
283
|
+
@staticmethod
|
|
284
|
+
def remove_any(m: dict, *keys) -> dict:
|
|
285
|
+
"""移除指定key,返回新字典
|
|
286
|
+
|
|
287
|
+
:param m: 字典
|
|
288
|
+
:param keys: 要移除的键
|
|
289
|
+
:return: 移除后的新字典
|
|
290
|
+
"""
|
|
291
|
+
if MapUtil.is_empty(m):
|
|
292
|
+
return {}
|
|
293
|
+
remove_set = set(keys)
|
|
294
|
+
return {k: v for k, v in m.items() if k not in remove_set}
|
|
295
|
+
|
|
296
|
+
@staticmethod
|
|
297
|
+
def empty_if_null(m: Optional[dict]) -> dict:
|
|
298
|
+
"""None转空字典
|
|
299
|
+
|
|
300
|
+
:param m: 字典,可能为None
|
|
301
|
+
:return: 原字典或空字典
|
|
302
|
+
"""
|
|
303
|
+
return {} if m is None else m
|
|
304
|
+
|
|
305
|
+
@staticmethod
|
|
306
|
+
def default_if_empty(m: dict, default: dict) -> dict:
|
|
307
|
+
"""如果为空返回默认
|
|
308
|
+
|
|
309
|
+
:param m: 字典
|
|
310
|
+
:param default: 默认字典
|
|
311
|
+
:return: 非空的原字典或默认字典
|
|
312
|
+
"""
|
|
313
|
+
return default if MapUtil.is_empty(m) else m
|
|
314
|
+
|
|
315
|
+
# ----------------------------------------------------------------
|
|
316
|
+
# 取值
|
|
317
|
+
# ----------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
@staticmethod
|
|
320
|
+
def get_str(m: dict, key: str, default: str = "") -> str:
|
|
321
|
+
"""获取字符串值
|
|
322
|
+
|
|
323
|
+
:param m: 字典
|
|
324
|
+
:param key: 键
|
|
325
|
+
:param default: 默认值
|
|
326
|
+
:return: 字符串值
|
|
327
|
+
"""
|
|
328
|
+
if MapUtil.is_empty(m):
|
|
329
|
+
return default
|
|
330
|
+
value = m.get(key)
|
|
331
|
+
if value is None:
|
|
332
|
+
return default
|
|
333
|
+
return str(value)
|
|
334
|
+
|
|
335
|
+
@staticmethod
|
|
336
|
+
def get_int(m: dict, key: str, default: int = 0) -> int:
|
|
337
|
+
"""获取int值
|
|
338
|
+
|
|
339
|
+
:param m: 字典
|
|
340
|
+
:param key: 键
|
|
341
|
+
:param default: 默认值
|
|
342
|
+
:return: int值
|
|
343
|
+
"""
|
|
344
|
+
if MapUtil.is_empty(m):
|
|
345
|
+
return default
|
|
346
|
+
value = m.get(key)
|
|
347
|
+
if value is None:
|
|
348
|
+
return default
|
|
349
|
+
try:
|
|
350
|
+
return int(value)
|
|
351
|
+
except (ValueError, TypeError):
|
|
352
|
+
return default
|
|
353
|
+
|
|
354
|
+
@staticmethod
|
|
355
|
+
def get_float(m: dict, key: str, default: float = 0.0) -> float:
|
|
356
|
+
"""获取float值
|
|
357
|
+
|
|
358
|
+
:param m: 字典
|
|
359
|
+
:param key: 键
|
|
360
|
+
:param default: 默认值
|
|
361
|
+
:return: float值
|
|
362
|
+
"""
|
|
363
|
+
if MapUtil.is_empty(m):
|
|
364
|
+
return default
|
|
365
|
+
value = m.get(key)
|
|
366
|
+
if value is None:
|
|
367
|
+
return default
|
|
368
|
+
try:
|
|
369
|
+
return float(value)
|
|
370
|
+
except (ValueError, TypeError):
|
|
371
|
+
return default
|
|
372
|
+
|
|
373
|
+
@staticmethod
|
|
374
|
+
def get_bool(m: dict, key: str, default: bool = False) -> bool:
|
|
375
|
+
"""获取bool值
|
|
376
|
+
|
|
377
|
+
:param m: 字典
|
|
378
|
+
:param key: 键
|
|
379
|
+
:param default: 默认值
|
|
380
|
+
:return: bool值
|
|
381
|
+
"""
|
|
382
|
+
if MapUtil.is_empty(m):
|
|
383
|
+
return default
|
|
384
|
+
value = m.get(key)
|
|
385
|
+
if value is None:
|
|
386
|
+
return default
|
|
387
|
+
if isinstance(value, bool):
|
|
388
|
+
return value
|
|
389
|
+
if isinstance(value, str):
|
|
390
|
+
return value.lower() in ("true", "1", "yes", "on")
|
|
391
|
+
return bool(value)
|
|
392
|
+
|
|
393
|
+
@staticmethod
|
|
394
|
+
def get(m: dict, key: str, default=None, cast_type=None) -> Any:
|
|
395
|
+
"""获取值,可选类型转换
|
|
396
|
+
|
|
397
|
+
:param m: 字典
|
|
398
|
+
:param key: 键
|
|
399
|
+
:param default: 默认值
|
|
400
|
+
:param cast_type: 类型转换函数(如 int, float, str)
|
|
401
|
+
:return: 转换后的值
|
|
402
|
+
"""
|
|
403
|
+
if MapUtil.is_empty(m):
|
|
404
|
+
return default
|
|
405
|
+
value = m.get(key)
|
|
406
|
+
if value is None:
|
|
407
|
+
return default
|
|
408
|
+
if cast_type is not None:
|
|
409
|
+
try:
|
|
410
|
+
return cast_type(value)
|
|
411
|
+
except (ValueError, TypeError):
|
|
412
|
+
return default
|
|
413
|
+
return value
|
|
414
|
+
|
|
415
|
+
@staticmethod
|
|
416
|
+
def get_first_not_null(m: dict, *keys) -> Any:
|
|
417
|
+
"""获取第一个非None的值
|
|
418
|
+
|
|
419
|
+
:param m: 字典
|
|
420
|
+
:param keys: 候选键
|
|
421
|
+
:return: 第一个非None的值,或None
|
|
422
|
+
"""
|
|
423
|
+
if MapUtil.is_empty(m):
|
|
424
|
+
return None
|
|
425
|
+
for key in keys:
|
|
426
|
+
value = m.get(key)
|
|
427
|
+
if value is not None:
|
|
428
|
+
return value
|
|
429
|
+
return None
|
|
430
|
+
|
|
431
|
+
# ----------------------------------------------------------------
|
|
432
|
+
# 批量操作
|
|
433
|
+
# ----------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
@staticmethod
|
|
436
|
+
def group_by_field(
|
|
437
|
+
dict_list: Sequence[Dict[_K, _V]],
|
|
438
|
+
field_key: _K,
|
|
439
|
+
) -> Dict[_V, List[Dict[_K, _V]]]:
|
|
440
|
+
"""按字段分组
|
|
441
|
+
|
|
442
|
+
:param dict_list: 字典列表
|
|
443
|
+
:param field_key: 分组依据的键
|
|
444
|
+
:return: 分组后的字典
|
|
445
|
+
"""
|
|
446
|
+
result: Dict[_V, List[Dict[_K, _V]]] = {}
|
|
447
|
+
if dict_list is None:
|
|
448
|
+
return result
|
|
449
|
+
for item in dict_list:
|
|
450
|
+
group_key = item.get(field_key)
|
|
451
|
+
result.setdefault(group_key, []).append(item)
|
|
452
|
+
return result
|
|
453
|
+
|
|
454
|
+
@staticmethod
|
|
455
|
+
def flat(m: dict) -> List:
|
|
456
|
+
"""将字典展开为列表 [k1, v1, k2, v2, ...]
|
|
457
|
+
|
|
458
|
+
:param m: 字典
|
|
459
|
+
:return: 展开后的列表
|
|
460
|
+
"""
|
|
461
|
+
if MapUtil.is_empty(m):
|
|
462
|
+
return []
|
|
463
|
+
result = []
|
|
464
|
+
for k, v in m.items():
|
|
465
|
+
result.append(k)
|
|
466
|
+
result.append(v)
|
|
467
|
+
return result
|
|
468
|
+
|
|
469
|
+
@staticmethod
|
|
470
|
+
def is_sub_map(sub: dict, full: dict) -> bool:
|
|
471
|
+
"""判断sub是否为full的子集
|
|
472
|
+
|
|
473
|
+
:param sub: 子字典
|
|
474
|
+
:param full: 完整字典
|
|
475
|
+
:return: 是否为子集
|
|
476
|
+
"""
|
|
477
|
+
if MapUtil.is_empty(sub):
|
|
478
|
+
return True
|
|
479
|
+
if MapUtil.is_empty(full):
|
|
480
|
+
return False
|
|
481
|
+
for k, v in sub.items():
|
|
482
|
+
if k not in full or full[k] != v:
|
|
483
|
+
return False
|
|
484
|
+
return True
|
|
485
|
+
|
|
486
|
+
@staticmethod
|
|
487
|
+
def contains_key(m: dict, key: Any) -> bool:
|
|
488
|
+
"""字典是否包含指定键
|
|
489
|
+
|
|
490
|
+
:param m: 字典
|
|
491
|
+
:param key: 键
|
|
492
|
+
:return: 是否包含
|
|
493
|
+
"""
|
|
494
|
+
if MapUtil.is_empty(m):
|
|
495
|
+
return False
|
|
496
|
+
return key in m
|
|
497
|
+
|
|
498
|
+
@staticmethod
|
|
499
|
+
def contains_value(m: dict, value: Any) -> bool:
|
|
500
|
+
"""字典是否包含指定值
|
|
501
|
+
|
|
502
|
+
:param m: 字典
|
|
503
|
+
:param value: 值
|
|
504
|
+
:return: 是否包含
|
|
505
|
+
"""
|
|
506
|
+
if MapUtil.is_empty(m):
|
|
507
|
+
return False
|
|
508
|
+
return value in m.values()
|
|
509
|
+
|
|
510
|
+
@staticmethod
|
|
511
|
+
def merge(m1: dict, m2: dict) -> dict:
|
|
512
|
+
"""合并两个字典,返回新字典(m2 覆盖 m1)
|
|
513
|
+
|
|
514
|
+
:param m1: 第一个字典
|
|
515
|
+
:param m2: 第二个字典(优先级更高)
|
|
516
|
+
:return: 合并后的新字典
|
|
517
|
+
"""
|
|
518
|
+
result = {}
|
|
519
|
+
if m1:
|
|
520
|
+
result.update(m1)
|
|
521
|
+
if m2:
|
|
522
|
+
result.update(m2)
|
|
523
|
+
return result
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
# ====================================================================
|
|
527
|
+
# MapWrapper
|
|
528
|
+
# ====================================================================
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
class MapWrapper(OrderedDict):
|
|
532
|
+
"""字典包装类,通过包装一个已有字典实现特定功能。
|
|
533
|
+
|
|
534
|
+
对应 Java cn.hutool.core.map.MapWrapper。
|
|
535
|
+
继承 OrderedDict,可作为完整 dict 使用。
|
|
536
|
+
"""
|
|
537
|
+
|
|
538
|
+
def __class_getitem__(cls, item):
|
|
539
|
+
return cls
|
|
540
|
+
|
|
541
|
+
def __init__(self, data: Optional[Dict[_K, _V]] = None, /, **kwargs):
|
|
542
|
+
super().__init__()
|
|
543
|
+
if data is not None:
|
|
544
|
+
self.update(data)
|
|
545
|
+
if kwargs:
|
|
546
|
+
self.update(kwargs)
|
|
547
|
+
|
|
548
|
+
# ------ 基础工具 ------
|
|
549
|
+
|
|
550
|
+
def size(self) -> int:
|
|
551
|
+
"""获取字典大小
|
|
552
|
+
|
|
553
|
+
:return: 字典大小
|
|
554
|
+
"""
|
|
555
|
+
return len(self)
|
|
556
|
+
|
|
557
|
+
def is_empty(self) -> bool:
|
|
558
|
+
"""字典是否为空
|
|
559
|
+
|
|
560
|
+
:return: 字典是否为空
|
|
561
|
+
"""
|
|
562
|
+
return self.size() == 0
|
|
563
|
+
|
|
564
|
+
def is_not_empty(self) -> bool:
|
|
565
|
+
"""字典是否非空
|
|
566
|
+
|
|
567
|
+
:return: 字典是否非空
|
|
568
|
+
"""
|
|
569
|
+
return not self.is_empty()
|
|
570
|
+
|
|
571
|
+
# ------ 序列化 ------
|
|
572
|
+
|
|
573
|
+
def __repr__(self):
|
|
574
|
+
items = ", ".join(f"{k!r}: {v!r}" for k, v in self.items())
|
|
575
|
+
return f"{self.__class__.__name__}({{{items}}})"
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
# ====================================================================
|
|
579
|
+
# BiMap (双向字典)
|
|
580
|
+
# ====================================================================
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
class BiMap(MapWrapper[_K, _V]):
|
|
584
|
+
"""双向Map,维护两个字典实现正向和反向查找。
|
|
585
|
+
|
|
586
|
+
对应 Java cn.hutool.core.map.BiMap。
|
|
587
|
+
互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素。
|
|
588
|
+
"""
|
|
589
|
+
|
|
590
|
+
def __init__(self, data: Optional[dict] = None, /, **kwargs) -> None:
|
|
591
|
+
"""初始化
|
|
592
|
+
|
|
593
|
+
:param data: 被包装的字典
|
|
594
|
+
"""
|
|
595
|
+
self._inverse: Optional[Dict[_V, _K]] = None
|
|
596
|
+
super().__init__(data, **kwargs)
|
|
597
|
+
|
|
598
|
+
# ------ 内部维护反向字典 ------
|
|
599
|
+
|
|
600
|
+
def _build_inverse(self) -> Dict[_V, _K]:
|
|
601
|
+
"""构建反向字典
|
|
602
|
+
|
|
603
|
+
:return: 反向字典
|
|
604
|
+
"""
|
|
605
|
+
return {v: k for k, v in self.items()}
|
|
606
|
+
|
|
607
|
+
def _ensure_inverse(self) -> Dict[_V, _K]:
|
|
608
|
+
"""确保反向字典已构建
|
|
609
|
+
|
|
610
|
+
:return: 反向字典
|
|
611
|
+
"""
|
|
612
|
+
if self._inverse is None:
|
|
613
|
+
self._inverse = self._build_inverse()
|
|
614
|
+
return self._inverse
|
|
615
|
+
|
|
616
|
+
def _invalidate_inverse(self) -> None:
|
|
617
|
+
"""标记反向字典需要重建"""
|
|
618
|
+
self._inverse = None
|
|
619
|
+
|
|
620
|
+
# ------ dict 协议重写 ------
|
|
621
|
+
|
|
622
|
+
def __setitem__(self, key: _K, value: _V) -> None:
|
|
623
|
+
old_value = self.get(key, None)
|
|
624
|
+
inv = self._inverse
|
|
625
|
+
if inv is not None:
|
|
626
|
+
if old_value is not None and old_value in inv:
|
|
627
|
+
del inv[old_value]
|
|
628
|
+
inv[value] = key
|
|
629
|
+
super().__setitem__(key, value)
|
|
630
|
+
|
|
631
|
+
def __delitem__(self, key: _K) -> None:
|
|
632
|
+
old_value = self.get(key, None)
|
|
633
|
+
super().__delitem__(key)
|
|
634
|
+
if self._inverse is not None and old_value is not None:
|
|
635
|
+
if old_value in self._inverse:
|
|
636
|
+
del self._inverse[old_value]
|
|
637
|
+
|
|
638
|
+
# ------ update / setdefault / pop ------
|
|
639
|
+
|
|
640
|
+
def update(self, __m=None, **kwargs: _V) -> None: # type: ignore[override]
|
|
641
|
+
"""更新字典
|
|
642
|
+
|
|
643
|
+
:param __m: 字典或者有两个元素的可迭代对象
|
|
644
|
+
"""
|
|
645
|
+
if __m is not None:
|
|
646
|
+
if isinstance(__m, dict):
|
|
647
|
+
for k, v in __m.items():
|
|
648
|
+
self[k] = v
|
|
649
|
+
else:
|
|
650
|
+
for k, v in __m:
|
|
651
|
+
self[k] = v
|
|
652
|
+
for k, v in kwargs.items():
|
|
653
|
+
self[k] = v
|
|
654
|
+
|
|
655
|
+
def setdefault(self, key: _K, default: _V = None) -> _V:
|
|
656
|
+
"""添加默认值,如果存在键则不添加
|
|
657
|
+
|
|
658
|
+
:param key: 键
|
|
659
|
+
:param default: 默认值
|
|
660
|
+
:return: 值
|
|
661
|
+
"""
|
|
662
|
+
if key in self:
|
|
663
|
+
return self[key]
|
|
664
|
+
self[key] = default
|
|
665
|
+
return default
|
|
666
|
+
|
|
667
|
+
def pop(self, key: _K, *args) -> _V:
|
|
668
|
+
"""弹出值
|
|
669
|
+
|
|
670
|
+
:param key: 键
|
|
671
|
+
:return: 值
|
|
672
|
+
"""
|
|
673
|
+
value = super().pop(key, *args)
|
|
674
|
+
if self._inverse is not None and value is not _DEFAULT_PARAM:
|
|
675
|
+
if value in self._inverse:
|
|
676
|
+
del self._inverse[value]
|
|
677
|
+
return value
|
|
678
|
+
|
|
679
|
+
def popitem(self, last: bool = True) -> Tuple[_K, _V]:
|
|
680
|
+
"""弹出键值对
|
|
681
|
+
|
|
682
|
+
:return: 键值对
|
|
683
|
+
"""
|
|
684
|
+
k, v = super().popitem(last=last)
|
|
685
|
+
if self._inverse is not None and v in self._inverse:
|
|
686
|
+
del self._inverse[v]
|
|
687
|
+
return k, v
|
|
688
|
+
|
|
689
|
+
def clear(self) -> None:
|
|
690
|
+
super().clear()
|
|
691
|
+
self._inverse = None
|
|
692
|
+
|
|
693
|
+
# ------ 双向查找接口 ------
|
|
694
|
+
|
|
695
|
+
def get(self, key: _K, default=None) -> Optional[_V]: # type: ignore[override]
|
|
696
|
+
"""获取值
|
|
697
|
+
|
|
698
|
+
:param key: 键
|
|
699
|
+
:param default: 默认值
|
|
700
|
+
:return: 值
|
|
701
|
+
"""
|
|
702
|
+
return super().get(key, default)
|
|
703
|
+
|
|
704
|
+
def get_key(self, value: _V, default=None) -> Optional[_K]:
|
|
705
|
+
"""根据值获得键
|
|
706
|
+
|
|
707
|
+
:param value: 值
|
|
708
|
+
:param default: 默认值
|
|
709
|
+
:return: 键
|
|
710
|
+
"""
|
|
711
|
+
return self._ensure_inverse().get(value, default)
|
|
712
|
+
|
|
713
|
+
def get_inverse(self) -> Dict[_V, _K]:
|
|
714
|
+
"""获取反向字典(值->键)
|
|
715
|
+
|
|
716
|
+
:return: 反向字典
|
|
717
|
+
"""
|
|
718
|
+
return self._ensure_inverse()
|
|
719
|
+
|
|
720
|
+
def reset_inverse(self) -> None:
|
|
721
|
+
"""重置反转的字典"""
|
|
722
|
+
self._inverse = None
|
|
723
|
+
|
|
724
|
+
# ------ 其他 ------
|
|
725
|
+
|
|
726
|
+
def keys(self): # type: ignore[override]
|
|
727
|
+
"""获取所有键"""
|
|
728
|
+
return super().keys()
|
|
729
|
+
|
|
730
|
+
def values(self): # type: ignore[override]
|
|
731
|
+
"""获取所有值"""
|
|
732
|
+
return super().values()
|
|
733
|
+
|
|
734
|
+
def items(self): # type: ignore[override]
|
|
735
|
+
"""获取所有键值对"""
|
|
736
|
+
return super().items()
|
|
737
|
+
|
|
738
|
+
def copy(self) -> BiMap[_K, _V]:
|
|
739
|
+
"""浅拷贝
|
|
740
|
+
|
|
741
|
+
:return: BiMap副本
|
|
742
|
+
"""
|
|
743
|
+
return BiMap(OrderedDict(self.items()))
|
|
744
|
+
|
|
745
|
+
def __copy__(self) -> BiMap[_K, _V]:
|
|
746
|
+
return self.copy()
|
|
747
|
+
|
|
748
|
+
def __repr__(self):
|
|
749
|
+
items = ", ".join(f"{k!r}: {v!r}" for k, v in self.items())
|
|
750
|
+
return f"BiMap({{{items}}})"
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
# ====================================================================
|
|
754
|
+
# FuncKeyDict
|
|
755
|
+
# ====================================================================
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
class FuncKeyDict(MapWrapper[_K, _V]):
|
|
759
|
+
"""自定义函数Key风格的字典。
|
|
760
|
+
|
|
761
|
+
所有键在存入和读取时都会经过 key_func 处理,例如统一转小写。
|
|
762
|
+
"""
|
|
763
|
+
|
|
764
|
+
def __init__(
|
|
765
|
+
self,
|
|
766
|
+
data: Optional[Dict[_K, _V]] = None,
|
|
767
|
+
key_func: Optional[Callable[[_K], _K]] = None,
|
|
768
|
+
/,
|
|
769
|
+
**kwargs,
|
|
770
|
+
):
|
|
771
|
+
"""初始化
|
|
772
|
+
|
|
773
|
+
注意提供的字典中不能有键值对,否则可能导致自定义key失效。
|
|
774
|
+
|
|
775
|
+
:param data: 提供的字典
|
|
776
|
+
:param key_func: 自定义键函数
|
|
777
|
+
"""
|
|
778
|
+
self._key_func = key_func
|
|
779
|
+
super().__init__(data, **kwargs)
|
|
780
|
+
|
|
781
|
+
def _transform_key(self, key: _K) -> _K:
|
|
782
|
+
"""根据函数自定义键
|
|
783
|
+
|
|
784
|
+
:param key: 原始键
|
|
785
|
+
:return: 自定义后的键
|
|
786
|
+
"""
|
|
787
|
+
if self._key_func is not None:
|
|
788
|
+
return self._key_func(key)
|
|
789
|
+
return key
|
|
790
|
+
|
|
791
|
+
def __getitem__(self, key: _K):
|
|
792
|
+
return super().__getitem__(self._transform_key(key))
|
|
793
|
+
|
|
794
|
+
def __setitem__(self, key: _K, value: _V):
|
|
795
|
+
super().__setitem__(self._transform_key(key), value)
|
|
796
|
+
|
|
797
|
+
def __delitem__(self, key: _K):
|
|
798
|
+
super().__delitem__(self._transform_key(key))
|
|
799
|
+
|
|
800
|
+
def __contains__(self, key: _K) -> bool: # type: ignore[override]
|
|
801
|
+
return super().__contains__(self._transform_key(key))
|
|
802
|
+
|
|
803
|
+
def get(self, key: _K, default=None): # type: ignore[override]
|
|
804
|
+
return super().get(self._transform_key(key), default)
|
|
805
|
+
|
|
806
|
+
def pop(self, key: _K, *args):
|
|
807
|
+
return super().pop(self._transform_key(key), *args)
|
|
808
|
+
|
|
809
|
+
def setdefault(self, key: _K, default: _V = None) -> _V:
|
|
810
|
+
return super().setdefault(self._transform_key(key), default)
|
|
811
|
+
|
|
812
|
+
@property
|
|
813
|
+
def key_func(self) -> Optional[Callable[[_K], _K]]:
|
|
814
|
+
"""获取键函数"""
|
|
815
|
+
return self._key_func
|
|
816
|
+
|
|
817
|
+
@key_func.setter
|
|
818
|
+
def key_func(self, func: Callable[[_K], _K]) -> None:
|
|
819
|
+
"""设置键函数"""
|
|
820
|
+
self._key_func = func
|
|
821
|
+
|
|
822
|
+
def __repr__(self):
|
|
823
|
+
items = ", ".join(f"{k!r}: {v!r}" for k, v in self.items())
|
|
824
|
+
return f"FuncKeyDict({{{items}}})"
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
# ====================================================================
|
|
828
|
+
# DictUtil (兼容旧接口,委托给 MapUtil)
|
|
829
|
+
# ====================================================================
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
class DictUtil:
|
|
833
|
+
"""字典相关工具类,兼容旧接口。
|
|
834
|
+
|
|
835
|
+
部分方法直接委托给 MapUtil,部分保留独立实现。
|
|
836
|
+
"""
|
|
837
|
+
|
|
838
|
+
@staticmethod
|
|
839
|
+
def is_empty(value: dict) -> bool:
|
|
840
|
+
"""字典是否为空
|
|
841
|
+
|
|
842
|
+
:param value: 字典
|
|
843
|
+
:return: 是否为空
|
|
844
|
+
"""
|
|
845
|
+
return MapUtil.is_empty(value)
|
|
846
|
+
|
|
847
|
+
@staticmethod
|
|
848
|
+
def is_not_empty(value: dict) -> bool:
|
|
849
|
+
"""字典是否为非空
|
|
850
|
+
|
|
851
|
+
:param value: 字典
|
|
852
|
+
:return: 是否为非空
|
|
853
|
+
"""
|
|
854
|
+
return MapUtil.is_not_empty(value)
|
|
855
|
+
|
|
856
|
+
@staticmethod
|
|
857
|
+
def empty_if_none(value: Union[dict, None]) -> dict:
|
|
858
|
+
"""如果提供的集合为None, 返回一个空集合,否则返回原集合
|
|
859
|
+
|
|
860
|
+
:param value: 提供的集合,可能为None
|
|
861
|
+
:return: 原集合,若为None返回空集合
|
|
862
|
+
"""
|
|
863
|
+
return MapUtil.empty_if_null(value)
|
|
864
|
+
|
|
865
|
+
@staticmethod
|
|
866
|
+
def default_if_empty(value: dict, default: dict) -> dict:
|
|
867
|
+
"""如果给定字典为空,返回默认字典
|
|
868
|
+
|
|
869
|
+
:param value: 字典
|
|
870
|
+
:param default: 默认字典
|
|
871
|
+
:return: 非空(empty)的原字典或默认字典
|
|
872
|
+
"""
|
|
873
|
+
return MapUtil.default_if_empty(value, default)
|
|
874
|
+
|
|
875
|
+
@staticmethod
|
|
876
|
+
def new_dict(is_ordered: bool = False, /, *args, **kwargs) -> dict:
|
|
877
|
+
"""新建一个字典
|
|
878
|
+
|
|
879
|
+
DictUtil.new_dict({'a': 1}, zip(['b'], [2]), [('c', 3)], d=4)
|
|
880
|
+
|
|
881
|
+
:param is_ordered: 字典的Key是否有序
|
|
882
|
+
:return: 字典
|
|
883
|
+
"""
|
|
884
|
+
res = OrderedDict() if is_ordered else dict()
|
|
885
|
+
for arg in args:
|
|
886
|
+
res.update(arg)
|
|
887
|
+
if kwargs:
|
|
888
|
+
res.update(**kwargs)
|
|
889
|
+
return res
|
|
890
|
+
|
|
891
|
+
@staticmethod
|
|
892
|
+
def create_dict(dict_type: Optional[Type] = None) -> dict:
|
|
893
|
+
"""根据类型创建字典
|
|
894
|
+
|
|
895
|
+
:param dict_type: 字典类型
|
|
896
|
+
:return: 字典实例
|
|
897
|
+
"""
|
|
898
|
+
return MapUtil.create_map(dict_type)
|
|
899
|
+
|
|
900
|
+
@staticmethod
|
|
901
|
+
def to_list_dict(dict_list: Sequence[Dict[_K, _V]]) -> Dict[_K, List[_V]]:
|
|
902
|
+
"""行转列,合并相同的键,值合并为列表
|
|
903
|
+
|
|
904
|
+
传入: [{'a': 1, 'b': 1, 'c': 1}, {'a': 2, 'b': 2}, {'a': 3, 'b': 3}, {'a': 4}]
|
|
905
|
+
结果: {'a': [1,2,3,4], 'b': [1,2,3], 'c': [1]}
|
|
906
|
+
|
|
907
|
+
:param dict_list: 字典列表
|
|
908
|
+
:return: 字典
|
|
909
|
+
"""
|
|
910
|
+
return MapUtil.to_list_map(dict_list)
|
|
911
|
+
|
|
912
|
+
@staticmethod
|
|
913
|
+
def to_dict_list(list_dict: Dict[_K, Sequence[_V]]) -> List[Dict[_K, _V]]:
|
|
914
|
+
"""列转行
|
|
915
|
+
|
|
916
|
+
传入: {'a': [1,2,3,4], 'b': [1,2,3], 'c': [1]}
|
|
917
|
+
结果: [{'a': 1, 'b': 1, 'c': 1}, {'a': 2, 'b': 2}, {'a': 3, 'b': 3}, {'a': 4}]
|
|
918
|
+
|
|
919
|
+
:param list_dict: 列表字典
|
|
920
|
+
:return: 字典列表
|
|
921
|
+
"""
|
|
922
|
+
return MapUtil.to_dict_list(list_dict)
|
|
923
|
+
|
|
924
|
+
@staticmethod
|
|
925
|
+
def inverse(data: Dict[_K, _V]) -> Dict[_V, _K]:
|
|
926
|
+
"""字典的键和值互换
|
|
927
|
+
|
|
928
|
+
互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素。
|
|
929
|
+
|
|
930
|
+
:param data: 字典
|
|
931
|
+
:return: 互换后的字典
|
|
932
|
+
"""
|
|
933
|
+
return MapUtil.inverse(data)
|