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
@@ -0,0 +1,105 @@
1
+ """数学工具类,提供精确运算、坐标转换、位运算状态管理等功能"""
2
+
3
+ import math
4
+ from decimal import Decimal
5
+ from typing import Union
6
+
7
+ __all__ = ("BitStatusUtil", "MathUtil")
8
+
9
+
10
+ class MathUtil:
11
+ """数学工具类,提供精确数学运算和坐标转换功能"""
12
+
13
+ @staticmethod
14
+ def add(value1: Union[int, float, str, Decimal], value2: Union[int, float, str, Decimal]) -> Decimal:
15
+ """
16
+ 精确加法,使用Decimal避免浮点精度问题
17
+
18
+ :param value1: 加数1
19
+ :param value2: 加数2
20
+ :return: 精确的加法结果
21
+ """
22
+ d1 = Decimal(str(value1))
23
+ d2 = Decimal(str(value2))
24
+ return d1 + d2
25
+
26
+ @staticmethod
27
+ def point_to_radians(point: tuple) -> float:
28
+ """
29
+ 坐标点转弧度,根据(x, y)坐标计算对应的弧度值
30
+
31
+ :param point: 坐标点(x, y)
32
+ :return: 弧度值
33
+ """
34
+ if point is None or len(point) < 2:
35
+ raise ValueError("坐标点必须包含x和y两个分量")
36
+ x, y = float(point[0]), float(point[1])
37
+ return math.atan2(y, x)
38
+
39
+ @staticmethod
40
+ def radians_to_point(radians: float) -> tuple:
41
+ """
42
+ 弧度转坐标点,根据弧度计算单位圆上的(x, y)坐标
43
+
44
+ :param radians: 弧度值
45
+ :return: 坐标点(x, y)
46
+ """
47
+ x = math.cos(radians)
48
+ y = math.sin(radians)
49
+ return (x, y)
50
+
51
+
52
+ class BitStatusUtil:
53
+ """位运算状态工具类,使用位运算管理多状态组合"""
54
+
55
+ @staticmethod
56
+ def add(status: int, *values: int) -> int:
57
+ """
58
+ 添加状态位,将一个或多个状态值添加到当前状态中
59
+
60
+ :param status: 当前状态值
61
+ :param values: 要添加的一个或多个状态值
62
+ :return: 添加后的状态值
63
+ """
64
+ for value in values:
65
+ status |= value
66
+ return status
67
+
68
+ @staticmethod
69
+ def has(status: int, value: int) -> bool:
70
+ """
71
+ 是否包含指定状态
72
+
73
+ :param status: 当前状态值
74
+ :param value: 要检测的状态值
75
+ :return: 是否包含该状态
76
+ """
77
+ return (status & value) == value
78
+
79
+ @staticmethod
80
+ def remove(status: int, *values: int) -> int:
81
+ """
82
+ 移除状态位,从当前状态中移除一个或多个状态值
83
+
84
+ :param status: 当前状态值
85
+ :param values: 要移除的一个或多个状态值
86
+ :return: 移除后的状态值
87
+ """
88
+ for value in values:
89
+ status &= ~value
90
+ return status
91
+
92
+ @staticmethod
93
+ def to_binary_string(status: int) -> str:
94
+ """
95
+ 转二进制字符串
96
+
97
+ :param status: 状态值
98
+ :return: 二进制字符串表示
99
+ """
100
+ if status == 0:
101
+ return "0"
102
+ if status > 0:
103
+ return bin(status)[2:]
104
+ # 负数使用补码表示
105
+ return bin(status & 0xFFFFFFFF)[2:]
hutool/core/net.py ADDED
@@ -0,0 +1,288 @@
1
+ """网络工具类,提供端口检测、IP地址处理、子网掩码转换等功能"""
2
+
3
+ import ipaddress
4
+ import random
5
+ import socket
6
+ import uuid
7
+ from socket import AF_INET, SO_REUSEADDR, SOCK_DGRAM, SOCK_STREAM, SOL_SOCKET
8
+ from typing import Final, Optional
9
+
10
+ __all__ = ("Ipv4Util", "MaskBit", "NetUtil")
11
+
12
+
13
+ class MaskBit:
14
+ """掩码位和掩码之间的对应关系"""
15
+
16
+ MASK_BIT_MAP: Final[dict] = {
17
+ 1: "128.0.0.0",
18
+ 2: "192.0.0.0",
19
+ 3: "224.0.0.0",
20
+ 4: "240.0.0.0",
21
+ 5: "248.0.0.0",
22
+ 6: "252.0.0.0",
23
+ 7: "254.0.0.0",
24
+ 8: "255.0.0.0",
25
+ 9: "255.128.0.0",
26
+ 10: "255.192.0.0",
27
+ 11: "255.224.0.0",
28
+ 12: "255.240.0.0",
29
+ 13: "255.248.0.0",
30
+ 14: "255.252.0.0",
31
+ 15: "255.254.0.0",
32
+ 16: "255.255.0.0",
33
+ 17: "255.255.128.0",
34
+ 18: "255.255.192.0",
35
+ 19: "255.255.224.0",
36
+ 20: "255.255.240.0",
37
+ 21: "255.255.248.0",
38
+ 22: "255.255.252.0",
39
+ 23: "255.255.254.0",
40
+ 24: "255.255.255.0",
41
+ 25: "255.255.255.128",
42
+ 26: "255.255.255.192",
43
+ 27: "255.255.255.224",
44
+ 28: "255.255.255.240",
45
+ 29: "255.255.255.248",
46
+ 30: "255.255.255.252",
47
+ 31: "255.255.255.254",
48
+ 32: "255.255.255.255",
49
+ }
50
+
51
+ # 反向映射:掩码字符串 -> 掩码位
52
+ _MASK_TO_BIT: Final[dict] = {v: k for k, v in MASK_BIT_MAP.items()}
53
+
54
+ @staticmethod
55
+ def get(mask_bit: int) -> str:
56
+ """
57
+ 根据掩码位获取掩码
58
+
59
+ :param mask_bit: 掩码位,范围1-32
60
+ :return: 掩码字符串,如"255.255.255.0"
61
+ :raises KeyError: 掩码位不在合法范围内
62
+ """
63
+ return MaskBit.MASK_BIT_MAP[mask_bit]
64
+
65
+ @staticmethod
66
+ def get_mask_bit(mask: str) -> Optional[int]:
67
+ """
68
+ 根据掩码获取掩码位
69
+
70
+ :param mask: 掩码的点分十进制表示,如"255.255.255.0"
71
+ :return: 掩码位,如24;如果掩码不合法则返回None
72
+ """
73
+ return MaskBit._MASK_TO_BIT.get(mask)
74
+
75
+
76
+ class Ipv4Util:
77
+ """IPV4地址工具类"""
78
+
79
+ LOCAL_IP: Final[str] = "127.0.0.1"
80
+ # IP段的分割符
81
+ IP_SPLIT_MARK: Final[str] = "-"
82
+ # IP与掩码的分割符
83
+ IP_MASK_SPLIT_MARK: Final[str] = "/"
84
+ # 最大掩码位
85
+ IP_MASK_MAX: Final[int] = 32
86
+
87
+ @staticmethod
88
+ def format_ip_block(ip: str, mask: str) -> str:
89
+ """
90
+ 格式化IP段
91
+
92
+ :param ip: IP地址
93
+ :param mask: 掩码
94
+ :return: 返回xxx.xxx.xxx.xxx/mask的格式
95
+ """
96
+ return ip + Ipv4Util.IP_MASK_SPLIT_MARK + str(Ipv4Util.get_mask_bit_by_mask(mask))
97
+
98
+ @staticmethod
99
+ def get_mask_bit_by_mask(mask: str) -> int:
100
+ """
101
+ 根据子网掩码转换为掩码位
102
+
103
+ :param mask: 掩码的点分十进制表示,例如"255.255.255.0"
104
+ :return: 掩码位,例如24
105
+ :raises ValueError: 子网掩码非法
106
+ """
107
+ mask_bit = MaskBit.get_mask_bit(mask)
108
+ if mask_bit is None:
109
+ raise ValueError("Invalid netmask: " + mask)
110
+ return mask_bit
111
+
112
+
113
+ class NetUtil:
114
+ """网络工具类,提供端口检测、IP获取、地址转换等功能"""
115
+
116
+ # 本地IPv4地址
117
+ LOCAL_IP: Final[str] = Ipv4Util.LOCAL_IP
118
+ # 默认最小端口,1024
119
+ PORT_RANGE_MIN: Final[int] = 1024
120
+ # 默认最大端口,65535
121
+ PORT_RANGE_MAX: Final[int] = 0xFFFF
122
+
123
+ @staticmethod
124
+ def is_usable_local_port(port: int) -> bool:
125
+ """
126
+ 检测本地端口可用性
127
+
128
+ :param port: 被检测的端口
129
+ :return: 是否可用
130
+ """
131
+ if not NetUtil.is_valid_port(port):
132
+ return False
133
+
134
+ # 使用TCP协议检测
135
+ try:
136
+ with socket.socket(AF_INET, SOCK_STREAM) as s:
137
+ s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
138
+ s.bind((NetUtil.LOCAL_IP, port))
139
+ except OSError:
140
+ return False
141
+
142
+ # 使用UDP协议检测
143
+ try:
144
+ with socket.socket(AF_INET, SOCK_DGRAM) as s:
145
+ s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
146
+ s.bind((NetUtil.LOCAL_IP, port))
147
+ except OSError:
148
+ return False
149
+
150
+ return True
151
+
152
+ @staticmethod
153
+ def is_valid_port(port: int) -> bool:
154
+ """
155
+ 是否为有效的端口,此方法并不检查端口是否被占用
156
+
157
+ :param port: 端口号
158
+ :return: 是否有效
159
+ """
160
+ return 0 <= port <= NetUtil.PORT_RANGE_MAX
161
+
162
+ @staticmethod
163
+ def get_usable_local_port(min_port: int = PORT_RANGE_MIN, max_port: int = PORT_RANGE_MAX) -> int:
164
+ """
165
+ 查找指定范围内的可用端口
166
+
167
+ 此方法只检测给定范围内的随机一个端口,最多检测max_port-min_port次。
168
+
169
+ :param min_port: 端口最小值(包含)
170
+ :param max_port: 端口最大值(包含)
171
+ :return: 可用的端口
172
+ :raises RuntimeError: 在指定范围内未找到可用端口
173
+ """
174
+ max_port_exclude = max_port + 1
175
+ for _ in range(min_port, max_port_exclude):
176
+ random_port = random.randint(min_port, max_port)
177
+ if NetUtil.is_usable_local_port(random_port):
178
+ return random_port
179
+ raise RuntimeError(
180
+ f"Could not find an available port in the range [{min_port}, {max_port}] "
181
+ f"after {max_port - min_port} attempts"
182
+ )
183
+
184
+ @staticmethod
185
+ def get_local_ip() -> str:
186
+ """
187
+ 获取本机IP地址,通过UDP连接外部地址获取本机对外IP
188
+
189
+ :return: 本机IP地址,获取失败时返回127.0.0.1
190
+ """
191
+ try:
192
+ with socket.socket(AF_INET, SOCK_DGRAM) as s:
193
+ # 不实际发送数据,仅用于获取本机出口IP
194
+ s.connect(("8.8.8.8", 80))
195
+ return s.getsockname()[0]
196
+ except Exception:
197
+ return NetUtil.LOCAL_IP
198
+
199
+ @staticmethod
200
+ def get_localhost() -> str:
201
+ """
202
+ 获取本机主机名
203
+
204
+ :return: 主机名
205
+ """
206
+ return socket.gethostname()
207
+
208
+ @staticmethod
209
+ def get_mac_address() -> str:
210
+ """
211
+ 获取本机MAC地址
212
+
213
+ :return: MAC地址字符串,格式为xx:xx:xx:xx:xx:xx
214
+ """
215
+ mac = uuid.getnode()
216
+ return ":".join(f"{(mac >> (8 * i)) & 0xFF:02x}" for i in reversed(range(6)))
217
+
218
+ @staticmethod
219
+ def is_inner(ip: str) -> bool:
220
+ """
221
+ 判断是否为内网IP地址
222
+
223
+ 内网IP范围:
224
+ - 10.0.0.0 ~ 10.255.255.255(A类私有地址)
225
+ - 172.16.0.0 ~ 172.31.255.255(B类私有地址)
226
+ - 192.168.0.0 ~ 192.168.255.255(C类私有地址)
227
+ - 127.0.0.0 ~ 127.255.255.255(本地回环地址)
228
+
229
+ :param ip: IP地址字符串
230
+ :return: 是否为内网IP
231
+ """
232
+ if ip is None:
233
+ return False
234
+ try:
235
+ addr = ipaddress.ip_address(ip)
236
+ return addr.is_private
237
+ except ValueError:
238
+ return False
239
+
240
+ @staticmethod
241
+ def ipv4_to_long(ip: str) -> int:
242
+ """
243
+ 将IPv4地址转换为long整型数值
244
+
245
+ :param ip: IPv4地址字符串,如"192.168.1.1"
246
+ :return: 对应的long整型值
247
+ :raises ValueError: IP地址格式非法
248
+ """
249
+ try:
250
+ return int(ipaddress.IPv4Address(ip))
251
+ except ipaddress.AddressValueError as e:
252
+ raise ValueError(f"非法的IPv4地址: {ip}") from e
253
+
254
+ @staticmethod
255
+ def long_to_ipv4(long_ip: int) -> str:
256
+ """
257
+ 将long整型数值转换为IPv4地址
258
+
259
+ :param long_ip: long整型值
260
+ :return: IPv4地址字符串
261
+ :raises ValueError: 数值不在合法范围内
262
+ """
263
+ if long_ip < 0 or long_ip > 0xFFFFFFFF:
264
+ raise ValueError(f"非法的long型IP值: {long_ip}")
265
+ return str(ipaddress.IPv4Address(long_ip))
266
+
267
+ @staticmethod
268
+ def is_open(host: str, port: int, timeout: int = 2000) -> bool:
269
+ """
270
+ 检测远程主机端口是否可连接
271
+
272
+ :param host: 主机地址
273
+ :param port: 端口号
274
+ :param timeout: 超时时间,单位毫秒,默认2000ms
275
+ :return: 端口是否可连接
276
+ """
277
+ if host is None:
278
+ return False
279
+ if not NetUtil.is_valid_port(port):
280
+ return False
281
+
282
+ try:
283
+ with socket.socket(AF_INET, SOCK_STREAM) as s:
284
+ s.settimeout(timeout / 1000.0)
285
+ s.connect((host, port))
286
+ return True
287
+ except (socket.timeout, OSError):
288
+ return False
File without changes
@@ -0,0 +1,54 @@
1
+ import csv
2
+ import io
3
+ from typing import List
4
+
5
+
6
+ class CsvUtil:
7
+ """CSV工具类"""
8
+
9
+ @staticmethod
10
+ def read(path: str, charset: str = "utf-8") -> List[List[str]]:
11
+ """读取CSV文件为二维列表
12
+
13
+ :param path: CSV文件路径
14
+ :param charset: 文件编码,默认utf-8
15
+ :return: 二维列表,每个元素为一行的数据
16
+ """
17
+ with open(path, encoding=charset, newline="") as f:
18
+ reader = csv.reader(f)
19
+ return [row for row in reader]
20
+
21
+ @staticmethod
22
+ def read_string(csv_str: str) -> List[List[str]]:
23
+ """读取CSV字符串为二维列表
24
+
25
+ :param csv_str: CSV格式的字符串
26
+ :return: 二维列表,每个元素为一行的数据
27
+ """
28
+ f = io.StringIO(csv_str)
29
+ reader = csv.reader(f)
30
+ return [row for row in reader]
31
+
32
+ @staticmethod
33
+ def write(path: str, data: List[List[str]], charset: str = "utf-8") -> None:
34
+ """写入CSV文件
35
+
36
+ :param path: CSV文件路径
37
+ :param data: 二维列表数据
38
+ :param charset: 文件编码,默认utf-8
39
+ """
40
+ with open(path, "w", encoding=charset, newline="") as f:
41
+ writer = csv.writer(f)
42
+ writer.writerows(data)
43
+
44
+ @staticmethod
45
+ def write_string(data: List[List[str]]) -> str:
46
+ """将二维列表转为CSV字符串
47
+
48
+ :param data: 二维列表数据
49
+ :return: CSV格式的字符串
50
+ """
51
+ f = io.StringIO()
52
+ writer = csv.writer(f)
53
+ writer.writerows(data)
54
+ return f.getvalue()
@@ -0,0 +1,224 @@
1
+ from typing import List, Optional
2
+
3
+
4
+ class StrBuilder:
5
+ """字符串构建器,类似Java StringBuilder
6
+
7
+ 提供高效的字符串拼接操作,内部使用列表缓存各段字符串,
8
+ 最终在 to_string 时一次性拼接,避免频繁字符串拼接带来的性能问题。
9
+ """
10
+
11
+ def __init__(self, capacity: int = 16):
12
+ """初始化字符串构建器
13
+
14
+ :param capacity: 初始容量(提示性,不强制约束)
15
+ """
16
+ self._parts: List[str] = []
17
+ self._length: int = 0
18
+
19
+ def _to_str(self) -> str:
20
+ """内部方法:将当前所有片段拼接为完整字符串"""
21
+ return "".join(self._parts)
22
+
23
+ def append(self, *args: object) -> "StrBuilder":
24
+ """追加内容
25
+
26
+ 支持追加任意类型,会自动调用 str() 转换。
27
+ 可一次追加多个对象,也可追加列表/元组(展开其元素)。
28
+
29
+ :param args: 要追加的一个或多个对象
30
+ :return: self,支持链式调用
31
+ """
32
+ for arg in args:
33
+ if isinstance(arg, (list, tuple)):
34
+ for item in arg:
35
+ s = str(item)
36
+ self._parts.append(s)
37
+ self._length += len(s)
38
+ else:
39
+ s = str(arg)
40
+ self._parts.append(s)
41
+ self._length += len(s)
42
+ return self
43
+
44
+ def insert(self, index: int, obj: object) -> "StrBuilder":
45
+ """在指定位置插入内容
46
+
47
+ :param index: 插入位置索引
48
+ :param obj: 要插入的对象,会被转换为字符串
49
+ :return: self,支持链式调用
50
+ :raises IndexError: 索引越界
51
+ """
52
+ current = self._to_str()
53
+ if index < 0 or index > len(current):
54
+ raise IndexError(f"索引越界: {index}, 当前长度: {len(current)}")
55
+ s = str(obj)
56
+ self._parts = [current[:index], s, current[index:]]
57
+ self._length += len(s)
58
+ return self
59
+
60
+ def delete(self, start: int, end: int) -> "StrBuilder":
61
+ """删除指定范围的内容 [start, end)
62
+
63
+ :param start: 起始索引(包含)
64
+ :param end: 结束索引(不包含)
65
+ :return: self,支持链式调用
66
+ :raises IndexError: 索引越界
67
+ """
68
+ current = self._to_str()
69
+ if start < 0 or start > len(current):
70
+ raise IndexError(f"起始索引越界: {start}")
71
+ if end < 0 or end > len(current):
72
+ raise IndexError(f"结束索引越界: {end}")
73
+ if start > end:
74
+ raise ValueError(f"起始索引 {start} 大于结束索引 {end}")
75
+ deleted = current[start:end]
76
+ self._parts = [current[:start], current[end:]]
77
+ self._length -= len(deleted)
78
+ return self
79
+
80
+ def replace(self, start: int, end: int, string: str) -> "StrBuilder":
81
+ """替换指定范围的内容 [start, end)
82
+
83
+ :param start: 起始索引(包含)
84
+ :param end: 结束索引(不包含)
85
+ :param string: 替换内容
86
+ :return: self,支持链式调用
87
+ :raises IndexError: 索引越界
88
+ """
89
+ current = self._to_str()
90
+ if start < 0 or start > len(current):
91
+ raise IndexError(f"起始索引越界: {start}")
92
+ if end < 0 or end > len(current):
93
+ raise IndexError(f"结束索引越界: {end}")
94
+ if start > end:
95
+ raise ValueError(f"起始索引 {start} 大于结束索引 {end}")
96
+ removed_len = end - start
97
+ self._parts = [current[:start], string, current[end:]]
98
+ self._length = self._length - removed_len + len(string)
99
+ return self
100
+
101
+ def reverse(self) -> "StrBuilder":
102
+ """反转字符串内容
103
+
104
+ :return: self,支持链式调用
105
+ """
106
+ current = self._to_str()
107
+ self._parts = [current[::-1]]
108
+ return self
109
+
110
+ def to_string(self) -> str:
111
+ """转为字符串
112
+
113
+ :return: 当前构建器中的完整字符串
114
+ """
115
+ return self._to_str()
116
+
117
+ def length(self) -> int:
118
+ """获取当前字符串长度
119
+
120
+ :return: 字符串长度
121
+ """
122
+ return self._length
123
+
124
+ def is_empty(self) -> bool:
125
+ """判断构建器是否为空
126
+
127
+ :return: 为空返回 True,否则返回 False
128
+ """
129
+ return self._length == 0
130
+
131
+ def char_at(self, index: int) -> str:
132
+ """获取指定位置的字符
133
+
134
+ :param index: 字符索引
135
+ :return: 指定位置的字符
136
+ :raises IndexError: 索引越界
137
+ """
138
+ if index < 0 or index >= self._length:
139
+ raise IndexError(f"索引越界: {index}, 当前长度: {self._length}")
140
+ current = self._to_str()
141
+ return current[index]
142
+
143
+ def index_of(self, string: str, start: int = 0) -> int:
144
+ """查找子串首次出现的位置
145
+
146
+ :param string: 要查找的子串
147
+ :param start: 起始搜索位置,默认为0
148
+ :return: 子串首次出现的索引,未找到返回 -1
149
+ """
150
+ current = self._to_str()
151
+ return current.find(string, start)
152
+
153
+ def last_index_of(self, string: str) -> int:
154
+ """从后查找子串最后一次出现的位置
155
+
156
+ :param string: 要查找的子串
157
+ :return: 子串最后一次出现的索引,未找到返回 -1
158
+ """
159
+ current = self._to_str()
160
+ return current.rfind(string)
161
+
162
+ def substring(self, start: int, end: Optional[int] = None) -> str:
163
+ """获取子串
164
+
165
+ :param start: 起始索引(包含)
166
+ :param end: 结束索引(不包含),默认到末尾
167
+ :return: 截取的子串
168
+ """
169
+ current = self._to_str()
170
+ if end is None:
171
+ return current[start:]
172
+ return current[start:end]
173
+
174
+ def starts_with(self, prefix: str) -> bool:
175
+ """判断是否以指定字符串开头
176
+
177
+ :param prefix: 前缀字符串
178
+ :return: 是返回 True,否则返回 False
179
+ """
180
+ return self._to_str().startswith(prefix)
181
+
182
+ def ends_with(self, suffix: str) -> bool:
183
+ """判断是否以指定字符串结尾
184
+
185
+ :param suffix: 后缀字符串
186
+ :return: 是返回 True,否则返回 False
187
+ """
188
+ return self._to_str().endswith(suffix)
189
+
190
+ def contains(self, string: str) -> bool:
191
+ """判断是否包含指定字符串
192
+
193
+ :param string: 要查找的子串
194
+ :return: 包含返回 True,否则返回 False
195
+ """
196
+ return string in self._to_str()
197
+
198
+ def trim(self) -> "StrBuilder":
199
+ """去除首尾空白字符
200
+
201
+ :return: self,支持链式调用
202
+ """
203
+ current = self._to_str().strip()
204
+ self._parts = [current]
205
+ self._length = len(current)
206
+ return self
207
+
208
+ def clear(self) -> "StrBuilder":
209
+ """清空构建器
210
+
211
+ :return: self,支持链式调用
212
+ """
213
+ self._parts.clear()
214
+ self._length = 0
215
+ return self
216
+
217
+ def __str__(self) -> str:
218
+ return self._to_str()
219
+
220
+ def __len__(self) -> int:
221
+ return self._length
222
+
223
+ def __repr__(self) -> str:
224
+ return f"StrBuilder({self._to_str()!r})"