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/math_util.py
ADDED
|
@@ -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})"
|