Python ipaddress 模块详解:IPv4/IPv6 地址、网络与接口的创建、检查与运算
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
本篇技术指南基于 CPython 标准库 Lib/ipaddress.py 及其官方 API 参考 Doc/library/ipaddress.rst,系统讲解ipaddress模块的全部能力:用工厂函数便捷创建对象、用 IPv4/IPv6 地址/网络/接口对象做子网归属判定、主机遍历、掩码换算、子网切分与聚合,以及地址文本校验与反向 DNS 生成等。读完本文,你将掌握一套既兼容 IPv4 又兼容 IPv6 的纯 Python 地址编程范式,可直接用于防火墙规则判定、配置校验、DHCP 地址池计算、安全扫描与日志归并等真实场景。配套的入门导读可参考 Doc/howto/ipaddress.rst(面向不熟悉 IP 网络术语的读者),本文则为完整 API 参考。
模块定位:创建、操作与检查的三大对象模型
ipaddress自 Python 3.3 起加入标准库(见 Doc/library/ipaddress.rst),提供对 IPv4 与 IPv6 地址、网络的**创建(create)、操作(manipulate)与运算(operate)**能力。常见的典型任务包括:检查两台主机是否位于同一子网、遍历某个子网内的全部主机、判断一段字符串是否为合法的 IP 地址或网络定义等。
模块源码是一个纯 Python 实现、零外部依赖的单文件(Lib/ipaddress.py,约 2400 行),其开头声明为 "A fast, lightweight IPv4/IPv6 manipulation library in Python"。从源码结构(Lib/ipaddress.py)可以梳理出清晰的继承层级:
- 异常层:
AddressValueError(ValueError)、NetmaskValueError(ValueError)(第 17、21 行),用于把“地址格式错误”与“掩码格式错误”区分开。 - 地址层:
_BaseAddress→IPv4Address(Lib/ipaddress.py)与IPv6Address(Lib/ipaddress.py)。 - 网络层:
_BaseNetwork→IPv4Network(Lib/ipaddress.py)与IPv6Network(Lib/ipaddress.py)。 - 接口层:
IPv4Interface(继承IPv4Address,Lib/ipaddress.py)与IPv6Interface(继承IPv6Address,Lib/ipaddress.py),相当于“带网络信息的地址”。
一个值得注意的设计意图是:IPv4Address与IPv6Address共享大量公共属性,IPv6 特有语义的属性在 IPv4 对象上也会以合理方式实现,从而让“一段代码同时正确处理两个 IP 版本”成为可能。所有地址、网络、接口对象都是**可哈希(hashable)**的,可直接用作字典键或放入集合。
便捷工厂函数
模块提供三个工厂函数,自动依据输入判定应返回 IPv4 还是 IPv6 对象。从源码看(Lib/ipaddress.py),它们的实现模式一致:先尝试用对应的 IPv4 构造器,捕获AddressValueError/NetmaskValueError后换 IPv6 构造器再试,两者都失败则抛出合并后的ValueError。
>>> import ipaddress >>> ipaddress.ip_address('192.168.0.1') IPv4Address('192.168.0.1') >>> ipaddress.ip_address('2001:db8::') IPv6Address('2001:db8::')ip_address(address):返回IPv4Address或IPv6Address。地址可以是字符串或整数,小于2**32的整数默认按 IPv4 处理;非法输入抛ValueError。ip_network(address, strict=True):返回IPv4Network或IPv6Network。strict参数原样透传给对应网络构造器;若输入不合法,或网络带主机位且strict=True,抛ValueError。>>> ipaddress.ip_network('192.168.0.0/28') IPv4Network('192.168.0.0/28')ip_interface(address):返回IPv4Interface或IPv6Interface,用于描述“位于某个网络上的一个接口地址”。
这些便利函数的共同代价是:因为函数不知道调用者本意是 IPv4 还是 IPv6 格式,失败时的报错信息只能给出笼统描述。需要更精确的错误信息时,应当直接调用对应版本的具体类构造器(详见下文“自定义异常”一节),这正是AddressValueError、NetmaskValueError存在的意义。
IP 地址对象
合法输入形式
IPv4Address(address)(见 Lib/ipaddress.py)接受三种形式的输入,非法输入抛AddressValueError:
- 十进制点分字符串:四个取值 0–255 的十进制整数用点分隔,如
192.168.0.1,每个整数代表地址中的一个字节(octet)。不允许前导零,以免与八进制记法混淆(详见下方版本说明); - 可放入 32 位的整数;
- 长度 4 的
bytes打包对象(最高有效字节在前)。
>>> ipaddress.IPv4Address('192.168.0.1') IPv4Address('192.168.0.1') >>> ipaddress.IPv4Address(3232235521) IPv4Address('192.168.0.1') >>> ipaddress.IPv4Address(b'\xC0\xA8\x00\x01') IPv4Address('192.168.0.1')关于前导零的演进史值得特别注意:3.8 之前曾容忍前导零(包括形似八进制的歧义写法),3.9.5 起前导零一律视为错误,IPv4 字符串的解析严格程度与 glibc 的socket.inet_pton保持一致。
IPv6Address(address)(见 Lib/ipaddress.py)接受三种形式,非法输入抛AddressValueError:
- 字符串:八组各 16 bit 的十六进制组,以冒号分隔(exploded全展开记法),或采用 RFC 4291 定义的多种压缩(compressed简写)规则,如
"0000:0000:0000:0000:0000:0abc:0007:0def"可压缩为"::abc:7:def"。字符串可选携带作用域区 ID 后缀%scope_id(见 RFC 4007):区 ID 必须非空且不能含%,例如fe80::1234%1表示本节点第一条链路上的fe80::1234; - 可放入 128 位的整数;
- 长度 16 的
bytes打包对象,大端序。
>>> ipaddress.IPv6Address('2001:db8::1000') IPv6Address('2001:db8::1000') >>> ipaddress.IPv6Address('ff02::5678%1') IPv6Address('ff02::5678%1')通用属性(IPv4/IPv6 共用)
| 属性 | 说明 |
|---|---|
version | 版本号:IPv4 为4,IPv6 为6(自 3.14 起同时以类属性形式暴露) |
max_prefixlen | 地址总位数:IPv4 为32,IPv6 为128(自 3.14 起同时以类属性形式暴露);前缀即决定地址是否属于某网络时被比较的“高位位数” |
compressed/exploded | 地址的字符串表示。IPv4 没有针对零字节的简写记法,故两属性恒等于str(addr);同时暴露二者是为了让显示代码可统一处理两种版本 |
packed | 二进制表示:IPv4 为 4 字节、IPv6 为 16 字节的bytes(最高有效字节在前) |
reverse_pointer | 反向 DNS PTR 记录名(仅是可用于 PTR 查询的名字,不是解析出的主机名),自 3.5 起提供 |
is_multicast | 是否多播保留地址(IPv4 见 RFC 3171,IPv6 见 RFC 2373) |
is_private | 按 IANA 特殊地址注册表判定是否“全局不可达” |
is_global | 与is_private相反 |
is_unspecified | 是否未指定地址(IPv4 见 RFC 5735,IPv6 见 RFC 2373) |
is_reserved | 是否 IETF 保留地址。IPv4 仅指240.0.0.0/4保留块;IPv6 指地址空间中Reserved by IETF的全部地址。注意 IPv6 的fec0::/10(旧 Site-Local)不在其中(参见is_site_local与 RFC 3879) |
is_loopback | 是否回环地址(IPv4 见 RFC 3330,IPv6 见 RFC 2373) |
is_link_local | 是否链路本地保留地址(RFC 3927) |
ipv6_mapped | 返回表示“IPv4 映射的 IPv6 地址”的IPv4Address对象(RFC 4291),自 3.13 起提供 |
关于is_private/is_global的语义,有两点官方明示的例外:
- 共享地址空间
100.64.0.0/10上两者都为False(其余地址上两者恰好相反); - 对 IPv4-mapped IPv6 地址,其判定由底层 IPv4 语义决定,恒满足
address.is_private == address.ipv4_mapped.is_private(is_global同理)。
3.13 版本对is_private做过一次修正(同时影响is_global),修正了一批误判:
- IPv4:
192.0.0.0/24视为私有时段从原来的仅192.0.0.0/29扩展为整段,但192.0.0.9/32与192.0.0.10/32例外(192.0.0.9即任播地址); - IPv6:
64:ff9b:1::/48、2002::/16视为私有;2001::/23整体视为私有,但其中2001:1::1/128、2001:1::2/128、2001:3::/32、2001:4:112::/48、2001:20::/28、2001:30::/28例外,不视为私有。
从实现上可以印证:上述常量直接编码在源码的_IPv6Constants._private_networks与_private_networks_exceptions两个列表中(Lib/ipaddress.py),网络对象的is_private则要求网络地址与广播地址同时命中私有区间、且同时不属于例外区间(Lib/ipaddress.py)。其反向指针、loopback、multicast 等输出示例:
>>> ipaddress.ip_address("127.0.0.1").reverse_pointer '1.0.0.127.in-addr.arpa' >>> ipaddress.ip_address("2001:db8::1").reverse_pointer '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa'IPv6 特有属性
is_site_local:是否站点本地保留地址。站点本地地址空间已被 RFC 3879 废弃,判断是否属于 RFC 4193 的唯一本地地址应改用is_private。ipv4_mapped:对位于::FFFF:0:0/96(RFC 4291)的 IPv4-mapped 地址,返回内嵌的 IPv4 地址;其余地址返回None。scope_id:RFC 4007 作用域地址的区 ID(字符串形式);未指定时返回None。test_ipaddress.py中有专门针对 IPv6 scoped 地址比较与字符串化的测试(Lib/test/test_ipaddress.py)。sixtofour:对2002::/16内的 6to4 地址(RFC 3056),返回内嵌 IPv4 地址;否则None。teredo:对2001::/32内的 Teredo 地址(RFC 4380),返回内嵌的(server, client)地址对;否则None。
字符串与整数互转
与socket等网络接口互操作时,需用内建str()/int()转换:
>>> str(ipaddress.IPv4Address('192.168.0.1')) '192.168.0.1' >>> int(ipaddress.IPv4Address('192.168.0.1')) 3232235521 >>> str(ipaddress.IPv6Address('::1')) '::1' >>> int(ipaddress.IPv6Address('::1')) 1注意:IPv6 作用域地址转整数时会丢弃%scope_id。反向包装可由v4_int_to_packed/v6_int_to_packed完成(见模块级函数一节)。
地址运算符
比较运算符只能在同版本对象间使用(IPv4 对 IPv4,IPv6 对 IPv6)。带不同 scope zone ID 的相同 IPv6 地址不相等:
>>> IPv4Address('127.0.0.2') > IPv4Address('127.0.0.1') True >>> IPv4Address('127.0.0.2') == IPv4Address('127.0.0.1') False >>> IPv6Address('fe80::1234') == IPv6Address('fe80::1234%1') False >>> IPv6Address('fe80::1234%1') != IPv6Address('fe80::1234%2') True算术运算符:地址支持整数加减,越界时抛AddressValueError:
>>> IPv4Address('127.0.0.2') + 3 IPv4Address('127.0.0.5') >>> IPv4Address('127.0.0.2') - 3 IPv4Address('126.255.255.255') >>> IPv4Address('255.255.255.255') + 1 Traceback (most recent call last): ... ipaddress.AddressValueError: 4294967296 (>= 2**32) is not permitted as an IPv4 address自定义格式化:__format__
自 3.9 起,地址对象支持format()、str.format()与 f-string 的格式化协议。格式符fmt取值如下:'s'(默认,等价于str())、'b'(零填充二进制串)、'X'/'x'(大小写十六进制)、'n'(IPv4 等价于'b',IPv6 等价于'x')。二进制与十六进制形式还支持'#'前缀符与'_'千分位分组符。
>>> format(ipaddress.IPv4Address('192.168.0.1')) '192.168.0.1' >>> '{:#b}'.format(ipaddress.IPv4Address('192.168.0.1')) '0b11000000101010000000000000000001' >>> f'{ipaddress.IPv6Address("2001:db8::1000"):s}' '2001:db8::1000' >>> format(ipaddress.IPv6Address('2001:db8::1000'), '_X') '2001_0DB8_0000_0000_0000_0000_0000_1000' >>> '{:#_n}'.format(ipaddress.IPv6Address('2001:db8::1000')) '0x2001_0db8_0000_0000_0000_0000_0000_1000'test_format在两个版本的地址测试类中均有对应覆盖(Lib/test/test_ipaddress.py、Lib/test/test_ipaddress.py)。
IP 网络对象
IPv4Network/IPv6Network用于定义并检查 IP 网段。一个网络定义由**掩码(mask)与网络地址(network address)**组成,二者共同划定一段区间:区间内任一地址与掩码做“按位与(binary AND)”后都等于网络地址。例如掩码255.255.255.0、网络地址192.168.1.0的网络,包含192.168.1.0至192.168.1.255闭区间内全部地址。
网络对象继承地址对象的全部属性(version、is_*系列等),且这些布尔属性“对整个网络成立”当且仅当网络地址与广播地址同时成立。地址对象与网络对象均为可哈希对象。
前缀、网络掩码与主机掩码的换算
三种等价的掩码记法贯穿本模块全部 API:
- 前缀(prefix)
/nbits:表示掩码高位置 1 的位数。IPv4 的/24等价于网络掩码255.255.255.0,IPv6 等价于ffff:ff00::; - 网络掩码(net mask):高位连续置 1 的地址形式;
- 主机掩码(host mask):网络掩码的逻辑反,常用于思科访问控制列表,IPv4 中
/24的主机掩码为0.0.0.255。
构造形式与strict语义
IPv4Network(address, strict=True)的address可以是以下四种:
- “IP 地址 + 可选掩码”的字符串(以
/分隔):掩码要么是单个数字(即前缀),要么是点分 IPv4 字符串——后者若首字段非零按网络掩码解释,若首字段为零则按主机掩码解释(唯一例外是全零掩码,按网络掩码处理);不带掩码时默认/32。因此下面三种写法等价:192.168.1.0/24 == 192.168.1.0/255.255.255.0 == 192.168.1.0/0.0.0.255 - 可放入 32 位的整数:等价于网络地址为该整数、掩码
/32的单地址网络; - 长度 4 的大端
bytes:解释同整数形式; - 二元组
(地址描述, 掩码)(3.5 起支持):地址描述可以是字符串、32 位整数、4 字节打包整数或现成的IPv4Address对象;掩码是前缀长度整数(如24)或前缀掩码字符串(如255.255.255.0)。
地址非法抛AddressValueError,掩码非法抛NetmaskValueError。strict=True时若所给地址带主机位则抛ValueError;否则主机位会被掩掉以得到规范网络地址。例如ip_network('192.168.1.1/24')在默认strict=True下会报错,需显式strict=False或改用接口对象。
未另行说明时,接受其它网络/地址对象的网络方法,在参数 IP 版本不兼容时会抛TypeError。
IPv6Network(address, strict=True)的输入形式与之基本平行,但有两点差异:
- 字符串形式只接受前缀长度数字,当前不支持展开的网络掩码:
2001:db00::0/24合法,而2001:db00::0/ffff:ff00::不合法;缺省前缀视为/128; - 二元组中的掩码仅支持整数前缀长度。
网络核心属性
| 属性 | 说明 |
|---|---|
network_address | 网络地址,与前缀长度共同唯一确定一个网络 |
broadcast_address | 广播地址,发往该地址的数据包应被网络上每个主机接收 |
netmask/hostmask | 网络掩码 / 主机掩码,均为地址对象 |
prefixlen | 前缀长度(比特数) |
num_addresses | 网络内地址总数 |
with_prefixlen/compressed/exploded | 掩码用前缀记法的字符串表示;with_prefixlen与compressed恒等于str(network),exploded用全展开形式 |
with_netmask | 网络掩码记法的字符串表示 |
with_hostmask | 主机掩码记法的字符串表示 |
底层实现值得留意:broadcast_address与hostmask都用functools.cached_property惰性计算并缓存(Lib/ipaddress.py),前者是network_address | hostmask,后者是netmask ^ 全1;num_addresses则是广播地址与网络地址之差加一(Lib/ipaddress.py)。由于网络较大时(如 IPv6/64)遍历会不可行,这些属性是 O(1) 计算,绝不遍历。
网络方法
hosts():返回“可用主机”迭代器——IPv4 下排除网络地址与广播地址本身;/31网络把网络地址与广播地址也纳入可用;/32只返回该单主机(Lib/ipaddress.py)。
>>> list(ip_network('192.0.2.0/29').hosts()) [IPv4Address('192.0.2.1'), ..., IPv4Address('192.0.2.6')] >>> list(ip_network('192.0.2.0/31').hosts()) [IPv4Address('192.0.2.0'), IPv4Address('192.0.2.1')] >>> list(ip_network('192.0.2.1/32').hosts()) [IPv4Address('192.0.2.1')]IPv6 的hosts()语义略有不同(Lib/ipaddress.py):排除的是Subnet-Router anycast 地址(网络地址本身);/127网络把该 anycast 地址也纳入;/128返回单个主机。
overlaps(other):两者任一全部/部分包含对方即返回True。其实现是把“对方网络地址或广播地址落在己方”与“己方地址落在对方”四种情形各判一次(Lib/ipaddress.py)。
address_exclude(network):从本网络中剔除给定子网,返回剩余部分的网络对象迭代器;若给定网络未完全包含在本网络内则抛ValueError。其实现基于二分递归,用能覆盖差集的最小网段表示“减法”结果:
>>> n1 = ip_network('192.0.2.0/28') >>> n2 = ip_network('192.0.2.1/32') >>> list(n1.address_exclude(n2)) [IPv4Network('192.0.2.8/29'), IPv4Network('192.0.2.4/30'), IPv4Network('192.0.2.2/31'), IPv4Network('192.0.2.0/32')]subnets(prefixlen_diff=1, new_prefix=None):把当前网络切分为若干子网。prefixlen_diff表示前缀长度增加的位数;new_prefix直接指定子网期望前缀(必须比当前前缀长)。二者必须且只能设置其一。返回迭代器:
>>> list(ip_network('192.0.2.0/24').subnets()) [IPv4Network('192.0.2.0/25'), IPv4Network('192.0.2.128/25')] >>> list(ip_network('192.0.2.0/24').subnets(prefixlen_diff=2)) [IPv4Network('192.0.2.0/26'), IPv4Network('192.0.2.64/26'), IPv4Network('192.0.2.128/26'), IPv4Network('192.0.2.192/26')] >>> list(ip_network('192.0.2.0/24').subnets(new_prefix=26)) [IPv4Network('192.0.2.0/26'), IPv4Network('192.0.2.64/26'), IPv4Network('192.0.2.128/26'), IPv4Network('192.0.2.192/26')] >>> ip_network('192.0.2.0/24').subnets(new_prefix=23) Traceback (most recent call last): ... ValueError: new prefix must be longersupernet(prefixlen_diff=1, new_prefix=None):求包含当前网络的最小上层网络。prefixlen_diff为前缀减少位数,new_prefix直接指定期望前缀(必须比当前前缀短)。同样二选一。返回单个网络对象:
>>> ip_network('192.0.2.0/24').supernet() IPv4Network('192.0.2.0/23') >>> ip_network('192.0.2.0/24').supernet(prefixlen_diff=2) IPv4Network('192.0.0.0/22') >>> ip_network('192.0.2.0/24').supernet(new_prefix=20) IPv4Network('192.0.0.0/20')subnet_of(other)/supernet_of(other)(均自 3.7 起):判断本网络是否是另一网络的子网 / 超网:
>>> a = ip_network('192.168.1.0/24') >>> b = ip_network('192.168.1.128/30') >>> b.subnet_of(a) True >>> a.supernet_of(b) Truecompare_networks(other):仅比较网络地址(不看主机位),返回-1/0/1。3.7 起标记为 deprecated——它使用与<、==、>完全一致的排序与比较算法,直接用这些运算符即可:
>>> ip_network('192.0.2.1/32').compare_networks(ip_network('192.0.2.2/32')) -1 >>> ip_network('192.0.2.1/32').compare_networks(ip_network('192.0.2.0/32')) 1 >>> ip_network('192.0.2.1/32').compare_networks(ip_network('192.0.2.1/32')) 0next_network(next_prefix=None)(自 3.16 起新增,见 Lib/ipaddress.py):求与当前网络“紧邻的下一个”指定前缀大小网络。next_prefix缺省时沿用当前前缀;它必须在1与max_prefixlen之间,否则抛ValueError;若地址空间耗尽则同样抛ValueError。这非常适合做地址池的顺序分配:
>>> IPv4Network('192.0.2.0/24').next_network() IPv4Network('192.0.3.0/24') >>> IPv4Network('192.0.2.0/24').next_network(next_prefix=25) IPv4Network('192.0.3.0/25') >>> IPv4Network('192.0.2.0/24').next_network(next_prefix=23) IPv4Network('192.0.4.0/23') >>> IPv4Network('192.0.80.0/22').next_network(next_prefix=18) IPv4Network('192.0.128.0/18') >>> IPv4Network('255.255.255.0/24').next_network() Traceback (most recent call last): ... ValueError: out of address space, cannot make another /24 networksubnet_of/supernet_of等在 Lib/test/test_ipaddress.py 等测试方法中有系统覆盖;测试还专门验证了跨版本混用(v4 与 v6)时subnet_of等应安全返回False或抛TypeError(见 Lib/test/test_ipaddress.py)。
网络运算符、迭代与容器行为
网络对象支持逻辑比较运算符,排序规则是先按网络地址、再按网络掩码。==的实现要求版本、网络地址与掩码三者都一致(Lib/ipaddress.py)。
迭代网络对象会得到网络内全部地址(含网络地址与广播地址这些不可用地址;只想要可用主机请用hosts())。其实现是range(network, broadcast+1)的整数步进生成(Lib/ipaddress.py):
>>> for addr in IPv4Network('192.0.2.0/28'): ... addr ... IPv4Address('192.0.2.0') ... IPv4Address('192.0.2.15')网络还是地址的容器:支持下标索引与in成员测试(__getitem__与__contains__实现见 Lib/ipaddress.py)。成员测试的判定规则是:目标地址与网络掩码按位与的结果等于网络地址;版本不同时永远为False。
>>> IPv4Network('192.0.2.0/28')[0] IPv4Address('192.0.2.0') >>> IPv4Network('192.0.2.0/28')[15] IPv4Address('192.0.2.15') >>> IPv4Address('192.0.2.6') in IPv4Network('192.0.2.0/28') True >>> IPv4Address('192.0.3.6') in IPv4Network('192.0.2.0/28') False接口对象:地址 + 网络信息的组合
IPv4Interface(address)与IPv6Interface(address)描述“挂在某网络上的一张接口”。其address参数的语义与对应网络构造器相同,但任意主机地址总是被接受(不会因带主机位而报错)。IPv4Interface是IPv4Address的子类,因此继承地址类全部属性;额外提供如下属性(IPv6 对应类 Lib/ipaddress.py 语义相同):
ip:剥离网络信息后的纯地址对象:>>> interface = IPv4Interface('192.0.2.5/24') >>> interface.ip IPv4Address('192.0.2.5')network:该接口所属网络:>>> interface.network IPv4Network('192.0.2.0/24')with_prefixlen/with_netmask/with_hostmask:三种掩码记法的接口字符串:>>> interface.with_prefixlen '192.0.2.5/24' >>> interface.with_netmask '192.0.2.5/255.255.255.0' >>> interface.with_hostmask '192.0.2.5/0.0.0.255'
接口的比较语义比地址、网络更精细(详见 Doc/library/ipaddress.rst):
- 判等(
==/!=):IP 地址与网络都必须相同才相等;接口对象与任何地址对象或网络对象都永不相等的; - 排序(
</>等):同版本的接口与地址对象可以比较,地址对象总是排在接口对象之前;两个接口对象先按各自网络比较,网络相同时再按 IP 地址比较。
>>> IPv4Interface('192.0.2.5/24') == IPv4Address('192.0.2.5') False >>> IPv4Interface('192.0.2.5/24') == IPv4Interface('192.0.2.5/24') True其它模块级函数
| 函数 | 功能 |
|---|---|
v4_int_to_packed(address) | 把 IPv4 整数地址转为 4 字节大端bytes;负数或超 32 位抛ValueError |
v6_int_to_packed(address) | 把 IPv6 整数地址转为 16 字节大端bytes;越界抛ValueError |
summarize_address_range(first, last) | 把[first, last]闭区间内的连续地址聚合成尽量少且对齐的网段,返回迭代器 |
collapse_addresses(addresses) | 将一批网络对象合并为尽可能少的相邻网段,返回迭代器;输入混入不同版本抛TypeError |
get_mixed_type_key(obj) | 为地址与网络混排的排序提供 key 函数 |
用法示例:
>>> ipaddress.ip_address(3221225985) IPv4Address('192.0.2.1') >>> ipaddress.v4_int_to_packed(3221225985) b'\xc0\x00\x02\x01' >>> [ipaddr for ipaddr in ipaddress.summarize_address_range( ... ipaddress.IPv4Address('192.0.2.0'), ... ipaddress.IPv4Address('192.0.2.130'))] [IPv4Network('192.0.2.0/25'), IPv4Network('192.0.2.128/31'), IPv4Network('192.0.2.130/32')] >>> [ipaddr for ipaddr in ... ipaddress.collapse_addresses([ipaddress.IPv4Network('192.0.2.0/25'), ... ipaddress.IPv4Network('192.0.2.128/25')])] [IPv4Network('192.0.2.0/24')]summarize_address_range在first/last非地址对象或版本不同时抛TypeError,last不大于first或版本号非法时抛ValueError。这类“区间聚合/反聚合”功能在配置防火墙白名单、生成 ACL 汇总条目时非常实用。
get_mixed_type_key的典型用法是配合sorted()。由于地址与网络“本质不同”,IPv4Address('192.0.2.0') <= IPv4Network('192.0.2.0/24')这种比较默认没有意义、会抛异常;但你仍然可以借助该函数强制排序:
>>> sorted([IPv4Network('192.0.2.0/24'), IPv4Address('192.0.2.5')], ... key=ipaddress.get_mixed_type_key) [IPv4Address('192.0.2.5'), IPv4Network('192.0.2.0/24')](行为示意:地址按数值排序在前,网络随后。规则细节以官方文档及 doctest 为准。)
自定义异常与更精确的错误定位
模块为支持类构造器更具体的错误报告,定义了继承自ValueError的两个异常:
AddressValueError:与地址取值相关的错误;NetmaskValueError:与网络掩码相关的错误。
工厂函数由于需同时尝试 IPv4 与 IPv6,其合并后的ValueError信息往往较为笼统(例如'...' does not appear to be an IPv4 or IPv6 address,见 Lib/ipaddress.py);而直接调用版本化构造器可以精确得到“地址段坏”还是“掩码段坏”。例如IPv4Address('192.168.1.256')抛AddressValueError,IPv4Network('192.168.1.0/33')抛NetmaskValueError。写代码时可据此分别捕获并给出不同的用户提示。二者定义在 Lib/ipaddress.py,构造器地址/掩码错误消息的专测集中在 Lib/test/test_ipaddress.py 的FactoryFunctionErrors及网络测试类中。
版本演进要点速查
把散落在文档中的versionadded/versionchanged汇总,便于升级适配:
- 3.3:模块加入标准库;
ip_address/ip_network/ip_interface工厂函数、地址/网络/接口对象齐备。 - 3.4:
is_global属性加入。 - 3.5:
reverse_pointer加入;网络构造器新增“二元组 (地址, 掩码)”形式。 - 3.7:
subnet_of/supernet_of加入;compare_networks标记为 deprecated。 - 3.8:容忍 IPv4 字符串前导零(含形似八进制的写法)。
- 3.9:
__format__格式化协议加入;v4_int_to_packed等示例行为定型。 - 3.9.5:IPv4 字符串不再容忍前导零,解析严格程度对齐 glibc
inet_pton。 - 3.13:修正
is_private/is_global的一批误判(见前文列表);IPv4Address.ipv6_mapped加入。 - 3.14:
version与max_prefixlen同时以类属性形式暴露。 - 3.16:
next_network()加入。
测试与进一步研读
仓库为ipaddress准备了约 2900 行的单元测试 Lib/test/test_ipaddress.py。测试按“v4/v6 公共测试混入 + 版本化测试类”组织:CommonTestMixin_v4/CommonTestMixin_v6(Lib/test/test_ipaddress.py)承载两版本共享的断言逻辑,AddressTestCase_v4/v6(L196/L316)、InterfaceTestCase_v4/v6(L669/L832)、NetworkTestCase_v4/v6(L673/L836)分别针对地址/接口/网络三类对象,另有ComparisonTests(L902)与IpaddrUnitTest(L1083)负责跨类比较与历史回归。安装并构建好本仓库的解释器后,可运行:
./python -m test test_ipaddress若只运行部分用例,可加-m指定测试方法名,例如:
./python -m test test_ipaddress -m test_next_network阅读源码时建议沿“文档 → 实现 → 测试”的闭环:先在 Doc/library/ipaddress.rst 查阅某 API 的官方语义,再在 Lib/ipaddress.py 中看具体算法(前缀解析_split_optional_netmask、_make_netmask、子网切分、summarize_address_range的区间对齐逻辑等),最后到 Lib/test/test_ipaddress.py 找对应断言来确认边界行为。这样既能获得权威定义,又能理解每个方法为何这样实现,从而在防火墙规则校验、网络扫描、DHCP/地址池管理、配置模板校验等业务中可靠地使用这套 API。
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考