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.
Files changed (89) hide show
  1. hutool/__init__.py +174 -0
  2. hutool/cache/__init__.py +7 -0
  3. hutool/cache/cache_util.py +47 -0
  4. hutool/cache/fifo_cache.py +87 -0
  5. hutool/cache/lfu_cache.py +129 -0
  6. hutool/cache/lru_cache.py +93 -0
  7. hutool/cache/timed_cache.py +115 -0
  8. hutool/captcha/__init__.py +3 -0
  9. hutool/captcha/captcha_util.py +215 -0
  10. hutool/core/__init__.py +23 -0
  11. hutool/core/_base.py +61 -0
  12. hutool/core/bean.py +214 -0
  13. hutool/core/codec.py +111 -0
  14. hutool/core/coll.py +635 -0
  15. hutool/core/date.py +1024 -0
  16. hutool/core/exceptions.py +66 -0
  17. hutool/core/io/__init__.py +0 -0
  18. hutool/core/io/data_size_util.py +79 -0
  19. hutool/core/io/file_name_util.py +111 -0
  20. hutool/core/io/file_util.py +650 -0
  21. hutool/core/io/io_util.py +133 -0
  22. hutool/core/io/path_util.py +247 -0
  23. hutool/core/io/resource_util.py +137 -0
  24. hutool/core/map.py +933 -0
  25. hutool/core/math_util.py +105 -0
  26. hutool/core/net.py +288 -0
  27. hutool/core/text/__init__.py +0 -0
  28. hutool/core/text/csv_util.py +54 -0
  29. hutool/core/text/str_builder.py +224 -0
  30. hutool/core/text/unicode_util.py +58 -0
  31. hutool/core/tree.py +242 -0
  32. hutool/core/util/__init__.py +63 -0
  33. hutool/core/util/array_util.py +503 -0
  34. hutool/core/util/boolean_util.py +124 -0
  35. hutool/core/util/charset_util.py +60 -0
  36. hutool/core/util/class_util.py +136 -0
  37. hutool/core/util/coordinate_util.py +186 -0
  38. hutool/core/util/credit_code_util.py +110 -0
  39. hutool/core/util/desensitized_util.py +194 -0
  40. hutool/core/util/enum_util.py +94 -0
  41. hutool/core/util/escape_util.py +97 -0
  42. hutool/core/util/hash_util.py +243 -0
  43. hutool/core/util/hex_util.py +140 -0
  44. hutool/core/util/id_util.py +147 -0
  45. hutool/core/util/idcard_util.py +300 -0
  46. hutool/core/util/number_util.py +720 -0
  47. hutool/core/util/object_util.py +294 -0
  48. hutool/core/util/page_util.py +61 -0
  49. hutool/core/util/phone_util.py +140 -0
  50. hutool/core/util/random_util.py +112 -0
  51. hutool/core/util/re_util.py +231 -0
  52. hutool/core/util/reflect_util.py +135 -0
  53. hutool/core/util/runtime_util.py +89 -0
  54. hutool/core/util/str_util.py +2320 -0
  55. hutool/core/util/system_util.py +62 -0
  56. hutool/core/util/url_util.py +232 -0
  57. hutool/core/util/version_util.py +41 -0
  58. hutool/core/util/xml_util.py +158 -0
  59. hutool/core/util/zip_util.py +126 -0
  60. hutool/cron/__init__.py +4 -0
  61. hutool/cron/cron_pattern.py +123 -0
  62. hutool/cron/cron_util.py +115 -0
  63. hutool/crypto/__init__.py +5 -0
  64. hutool/crypto/digest_util.py +167 -0
  65. hutool/crypto/secure_util.py +311 -0
  66. hutool/crypto/sign_util.py +74 -0
  67. hutool/dfa/__init__.py +3 -0
  68. hutool/dfa/sensitive_util.py +114 -0
  69. hutool/extra/__init__.py +6 -0
  70. hutool/extra/emoji_util.py +90 -0
  71. hutool/extra/pinyin_util.py +44 -0
  72. hutool/extra/qr_code_util.py +58 -0
  73. hutool/extra/template_util.py +41 -0
  74. hutool/http/__init__.py +6 -0
  75. hutool/http/html_util.py +88 -0
  76. hutool/http/http_request.py +188 -0
  77. hutool/http/http_response.py +139 -0
  78. hutool/http/http_util.py +237 -0
  79. hutool/json_util.py +251 -0
  80. hutool/jwt_util.py +57 -0
  81. hutool/setting/__init__.py +5 -0
  82. hutool/setting/props_util.py +79 -0
  83. hutool/setting/setting_util.py +80 -0
  84. hutool/setting/yaml_util.py +45 -0
  85. hutool_python-1.0.0.dist-info/LICENSE +127 -0
  86. hutool_python-1.0.0.dist-info/METADATA +438 -0
  87. hutool_python-1.0.0.dist-info/RECORD +89 -0
  88. hutool_python-1.0.0.dist-info/WHEEL +5 -0
  89. 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)