如何在Python中不使用ipaddress模块检测IP地址是否为保留地址
IPv4保留地址检测的性能优化实践
需求:不使用ipaddress模块,判断给定IPv4地址是否属于保留地址范围。
保留地址原始列表
根据IPv4保留地址定义,列表如下:
RESERVED_IPV4 = [ '0.0.0.0/8', '10.0.0.0/8', '100.64.0.0/10', '127.0.0.0/8', '169.254.0.0/16', '172.16.0.0/12', '192.0.0.0/24', '192.0.2.0/24', '192.88.99.0/24', '192.168.0.0/16', '198.18.0.0/15', '198.51.100.0/24', '203.0.113.0/24', '224.0.0.0/4', '233.252.0.0/24', '240.0.0.0/4', '255.255.255.255/32' ]
转换为int类型起止范围
已将保留地址段转换为整数形式的起始、结束IP对:
RESERVED_IPV4 = [ (0, 16777215), (167772160, 184549375), (1681915904, 1686110207), (2130706432, 2147483647), (2851995648, 2852061183), (2886729728, 2887778303), (3221225472, 3221225727), (3221225984, 3221226239), (3227017984, 3227018239), (3232235520, 3232301055), (3323068416, 3323199487), (3325256704, 3325256959), (3405803776, 3405804031), (3758096384, 4294967295) ]
方法1:朴素范围检查
将每个地址段转为range对象,检查IP是否在任一范围内:
RESERVED_IPV4_RANGES = [range(*e) for e in RESERVED_IPV4] def is_reserved_range(ip): return any(ip in e for e in RESERVED_IPV4_RANGES)
性能测试:
import random n = random.randrange(2**32) # 生成随机IP整数 %timeit is_reserved_range(n) # 1.92 µs ± 56 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
这种方法效率较低,因为ip in range的判断需要额外计算。
方法2:二进制前缀匹配
由于同一保留地址段内的IP共享相同二进制前缀,因此可以通过检查IP的二进制字符串是否以任一保留前缀开头来判断:
生成前缀
def longest_common_prefix(a, b): short = min(len(a), len(b)) for i in range(short): if a[:i] != b[:i]: break return a[:i-1] if i else '' RESERVED_IPV4_PREFIXES = tuple(longest_common_prefix(f'{a:032b}', f'{b:032b}') for a, b in RESERVED_IPV4)
生成的前缀列表:
('00000000', '00001010', '0110010001', '01111111', '1010100111111110', '101011000001', '110000000000000000000000', '110000000000000000000010', '110000000101100001100011', '1100000010101000', '110001100001001', '110001100011001101100100', '110010110000000001110001', '111')
检测函数
def is_reserved_prefix(ip): return f'{ip:032b}'.startswith(RESERVED_IPV4_PREFIXES)
性能测试:
%timeit is_reserved_prefix(n) # 734 ns ± 6.59 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
该方法效率显著高于朴素范围检查。
方法3:直接整数范围比较
尝试直接比较IP整数是否在起止范围内:
%timeit any(a <= n <= b for a, b in RESERVED_IPV4) # 1.71 µs ± 6.31 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
效率仍低于前缀匹配法。
方法4:位运算掩码匹配
尝试通过位运算实现前缀匹配,预先计算每个地址段的掩码:
RESERVED_IPV4_MASKS = [] for a, b in RESERVED_IPV4: count = b - a + 1 x = count.bit_length() - 1 y = 32 - x mask = ((1<<y)-1)<<x RESERVED_IPV4_MASKS.append((a, mask)) RESERVED_IPV4_MASKS = tuple(RESERVED_IPV4_MASKS)
实现1:使用any函数
def is_reserved_mask(ip): return any(ip & mask == start for start, mask in RESERVED_IPV4_MASKS)
性能测试:
%timeit is_reserved_mask(n) # 2.12 µs ± 53.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
实现2:使用for循环提前返回
def is_reserved_mask(ip): for start, mask in RESERVED_IPV4_MASKS: if ip & mask == start: return True return False
性能测试:
%timeit is_reserved_mask(n) # 1.3 µs ± 97.4 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
疑惑点
- 位运算理论上应该比字符串操作更高效,但实际测试中掩码匹配法的效率低于二进制前缀匹配法。
- 使用
any函数的实现效率居然低于手动for循环提前返回的版本,不符合预期。
内容的提问来源于stack exchange,提问作者Ξένη Γήινος
相关产品推荐
相关产品推荐

