Files
personal-experiment/utils.py
T
2026-03-06 17:07:57 +08:00

347 lines
8.6 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
工具模块
提供各种工具函数和验证器类
"""
import socket
import re
from abc import ABC, abstractmethod
from typing import Optional, List
class Validator(ABC):
"""验证器抽象基类"""
@abstractmethod
def validate(self, value: str) -> bool:
"""
验证值
Args:
value: 要验证的值
Returns:
是否有效
"""
pass
@abstractmethod
def get_error_message(self, value: str) -> str:
"""
获取错误消息
Args:
value: 无效的值
Returns:
错误消息
"""
pass
class IPAddressValidator(Validator):
"""IP地址验证器"""
IPV4_PATTERN = r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
IPV6_PATTERN = r'^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::$|^:(?::)+(?:[0-9a-fA-F]{1,4})?$'
def validate(self, value: str) -> bool:
"""验证IP地址"""
return bool(
re.match(self.IPV4_PATTERN, value) or
re.match(self.IPV6_PATTERN, value)
)
def get_error_message(self, value: str) -> str:
"""获取错误消息"""
return f"无效的IP地址: {value}"
class HostnameValidator(Validator):
"""主机名验证器"""
def validate(self, value: str) -> bool:
"""验证主机名"""
if len(value) > 253:
return False
hostname = value.rstrip('.')
pattern = r"^(?!-)[A-Z0-9-]{1,63}(?<!-)$"
return all(
re.match(pattern, label, re.IGNORECASE)
for label in hostname.split('.')
)
def get_error_message(self, value: str) -> str:
"""获取错误消息"""
return f"无效的主机名: {value}"
class RangeValidator(Validator):
"""范围验证器"""
def __init__(self, min_val: float, max_val: float):
"""
初始化范围验证器
Args:
min_val: 最小值
max_val: 最大值
"""
self.min_val = min_val
self.max_val = max_val
def validate(self, value: str) -> bool:
"""验证数值是否在范围内"""
try:
num = float(value)
return self.min_val <= num <= self.max_val
except ValueError:
return False
def get_error_message(self, value: str) -> str:
"""获取错误消息"""
return f"值 {value} 不在范围 [{self.min_val}, {self.max_val}] 内"
class NetworkUtils:
"""网络工具类(静态方法集合)"""
@staticmethod
def resolve_hostname(hostname: str) -> Optional[str]:
"""
解析主机名为IP地址
Args:
hostname: 主机名
Returns:
IP地址,解析失败返回None
"""
try:
return socket.gethostbyname(hostname)
except socket.gaierror:
return None
@staticmethod
def is_port_open(host: str, port: int, timeout: float = 2.0) -> bool:
"""
检查端口是否开放
Args:
host: 主机地址
port: 端口号
timeout: 超时时间(秒)
Returns:
端口是否开放
"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
sock.close()
return result == 0
except Exception:
return False
@staticmethod
def validate_ip(ip: str) -> bool:
"""
验证IP地址
Args:
ip: IP地址
Returns:
是否有效
"""
validator = IPAddressValidator()
return validator.validate(ip)
@staticmethod
def validate_hostname(hostname: str) -> bool:
"""
验证主机名
Args:
hostname: 主机名
Returns:
是否有效
"""
validator = HostnameValidator()
return validator.validate(hostname)
class Formatter:
"""格式化工具类"""
@staticmethod
def format_bytes(bytes_size: int) -> str:
"""
格式化字节大小
Args:
bytes_size: 字节数
Returns:
格式化后的字符串
"""
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if bytes_size < 1024.0:
return f"{bytes_size:.2f} {unit}"
bytes_size /= 1024.0
return f"{bytes_size:.2f} PB"
@staticmethod
def format_duration(seconds: float) -> str:
"""
格式化时长
Args:
seconds: 秒数
Returns:
格式化后的字符串
"""
if seconds < 60:
return f"{seconds:.1f}秒"
elif seconds < 3600:
return f"{seconds / 60:.1f}分钟"
elif seconds < 86400:
return f"{seconds / 3600:.1f}小时"
else:
return f"{seconds / 86400:.1f}天"
@staticmethod
def format_percentage(value: float, total: float) -> str:
"""
格式化百分比
Args:
value: 数值
total: 总数
Returns:
格式化后的百分比字符串
"""
if total == 0:
return "0.00%"
return f"{(value / total * 100):.2f}%"
class MathUtils:
"""数学工具类"""
@staticmethod
def safe_divide(numerator: float, denominator: float, default: float = 0.0) -> float:
"""
安全除法
Args:
numerator: 分子
denominator: 分母
default: 除零时的默认值
Returns:
除法结果或默认值
"""
try:
return numerator / denominator if denominator != 0 else default
except Exception:
return default
@staticmethod
def clamp(value: float, min_val: float, max_val: float) -> float:
"""
限制数值范围
Args:
value: 数值
min_val: 最小值
max_val: 最大值
Returns:
限制后的数值
"""
return max(min_val, min(value, max_val))
@staticmethod
def average(values: List[float]) -> float:
"""
计算平均值
Args:
values: 数值列表
Returns:
平均值
"""
if not values:
return 0.0
return sum(values) / len(values)
class ValidatorFactory:
"""验证器工厂类"""
@staticmethod
def create_ip_validator() -> IPAddressValidator:
"""创建IP地址验证器"""
return IPAddressValidator()
@staticmethod
def create_hostname_validator() -> HostnameValidator:
"""创建主机名验证器"""
return HostnameValidator()
@staticmethod
def create_range_validator(min_val: float, max_val: float) -> RangeValidator:
"""创建范围验证器"""
return RangeValidator(min_val, max_val)
# 保留向后兼容的函数接口
def validate_ip_address(ip: str) -> bool:
"""验证IP地址(向后兼容)"""
return NetworkUtils.validate_ip(ip)
def validate_hostname(hostname: str) -> bool:
"""验证主机名(向后兼容)"""
return NetworkUtils.validate_hostname(hostname)
def resolve_hostname(hostname: str) -> Optional[str]:
"""解析主机名(向后兼容)"""
return NetworkUtils.resolve_hostname(hostname)
def is_port_open(host: str, port: int, timeout: float = 2.0) -> bool:
"""检查端口(向后兼容)"""
return NetworkUtils.is_port_open(host, port, timeout)
def format_bytes(bytes_size: int) -> str:
"""格式化字节(向后兼容)"""
return Formatter.format_bytes(bytes_size)
def format_duration(seconds: float) -> str:
"""格式化时长(向后兼容)"""
return Formatter.format_duration(seconds)
def safe_divide(numerator: float, denominator: float, default: float = 0.0) -> float:
"""安全除法(向后兼容)"""
return MathUtils.safe_divide(numerator, denominator, default)
def clamp(value: float, min_val: float, max_val: float) -> float:
"""限制范围(向后兼容)"""
return MathUtils.clamp(value, min_val, max_val)