登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Python ipaddress.collapse_addresses 怎样合并连续网段

来源:17golang原创

时间:2026-11-01 10:04:44 334浏览 收藏

如果一组网段来自配置文件、访问控制列表或地址池,直接把它们原样输出,往往会留下重复项和可以继续合并的相邻块。Python 标准库的 ipaddress.collapse_addresses() 可以把同一 IP 版本的网络对象交给 CIDR 规则统一折叠,返回更紧凑的网段迭代器。

最小用法是先用 ip_network() 把输入转换为网络对象,再调用 collapse_addresses()。IPv4 和 IPv6 要分批处理;函数只会合并满足前缀对齐条件的网络,不会因为两个网段“看起来相邻”就强行扩大范围。

官方文档:https://docs.python.org/3/library/ipaddress.html#ipaddress.collapse_addresses

先分清它合并的是什么

collapse_addresses() 处理的是 IPv4Network 或 IPv6Network 对象组成的可迭代对象,不是任意字符串列表。它返回一个迭代器,里面仍然是网络对象。

“连续网段”也有一个容易忽略的限制:两个网络不仅要覆盖连续的地址范围,还要能组成一个合法的更大 CIDR 网络。例如 192.0.2.0/25 和 192.0.2.128/25 正好组成 192.0.2.0/24;如果边界没有按更大前缀对齐,函数就会保留为多个结果。

同版本网络对象经过规范化后进入连续网段合并流程的结构示意图
图1:从网络对象、IP 版本分组到合并入口的静态结构示意图,不是运行截图。

最小写法:输入转换后直接折叠

下面的例子把字符串解析成网络对象,再把返回迭代器转换为列表。注释只标出输入类型、惰性返回值和最终展示这几个关键点。

from ipaddress import collapse_addresses, ip_network


def collapse_cidrs(values: list[str]) -> list[str]:
    # 先把文本统一解析为网络对象,strict=False 会屏蔽误写的主机位
    networks = [ip_network(value, strict=False) for value in values]
    # 函数返回迭代器;物化后更适合交给接口或配置渲染层
    collapsed = collapse_addresses(networks)
    # 用 str() 输出规范化后的 CIDR 文本
    return [str(network) for network in collapsed]


values = [
    "192.0.2.128/25",
    "192.0.2.0/25",
    "192.0.2.0/25",  # 重复输入不会让结果重复扩大
]
print(collapse_cidrs(values))
['192.0.2.0/24']

输入顺序不影响这个示例的折叠结果,重复的 /25 也不会把地址范围扩展到 192.0.3.0/24。如果业务还需要保留原始输入顺序,应在合并前另行保存原始列表。

为什么有些相邻网段仍然不能合并

合并的目标必须是一个合法的更大网络。下面的函数把几个边界样例放在一起,便于观察“可合并”“仅相邻”和“重复覆盖”的区别。

from ipaddress import collapse_addresses, ip_network


def show_case(label: str, values: list[str]) -> None:
    # strict=False 只负责把带主机位的输入归一化,不改变合并规则
    networks = [ip_network(value, strict=False) for value in values]
    result = [str(network) for network in collapse_addresses(networks)]
    print(f"{label}: {result}")


show_case("两个对齐的 /25", ["192.0.2.0/25", "192.0.2.128/25"])
show_case("两个未形成更大块的 /26", ["192.0.2.0/26", "192.0.2.128/26"])
show_case("大网覆盖小网", ["198.51.100.0/24", "198.51.100.0/25"])
两个对齐的 /25: ['192.0.2.0/24']
两个未形成更大块的 /26: ['192.0.2.0/26', '192.0.2.128/26']
大网覆盖小网: ['198.51.100.0/24']

第二组虽然都在同一个 /24 范围内,但中间还有一段没有覆盖,不能直接变成更大的连续块。判断时不要只比较一个网段的结束地址和另一个网段的起始地址,还要检查它们是否能按同一层级的 CIDR 边界配对。

IPv4 和 IPv6 要分开折叠

官方接口明确要求输入中的网络对象不能混合 IP 版本。实际项目常把两种地址都读进同一个列表,因此最好在调用前按 version 分组,或者在发现混合输入时给出业务层错误。

from collections.abc import Iterable
from ipaddress import IPv4Network, IPv6Network, collapse_addresses, ip_network


def collapse_by_version(values: Iterable[str]) -> dict[int, list[str]]:
    # 解析阶段统一得到网络对象,同时保留 Python 的版本属性
    networks = [ip_network(value, strict=False) for value in values]
    groups: dict[int, list[IPv4Network | IPv6Network]] = {4: [], 6: []}
    for network in networks:
        # 用 version 分组,避免把 IPv4 和 IPv6 一起传给 collapse_addresses
        groups[network.version].append(network)

    result: dict[int, list[str]] = {}
    for version, group in groups.items():
        if group:
            # 每组单独折叠,并把迭代器转换成稳定的文本结果
            result[version] = [str(network) for network in collapse_addresses(group)]
    return result


print(collapse_by_version([
    "192.0.2.0/25",
    "192.0.2.128/25",
    "2001:db8::/65",
    "2001:db8:0:0:8000::/65",
]))
{4: ['192.0.2.0/24'], 6: ['2001:db8::/64']}

如果不想分组,也可以在进入函数前直接拒绝同时出现版本 4 和版本 6 的输入。这样错误会更早、更容易定位,避免网络策略层悄悄丢掉其中一类地址。

输出接入配置前要固定边界

collapse_addresses() 返回的是迭代器,所以调用一次后就会被消费。要把结果交给多个下游步骤,先转换成列表;要对文本排序,也应排序网络对象或统一的字符串结果,而不是在多个地方重复遍历原迭代器。

from ipaddress import collapse_addresses, ip_network


def compact_for_config(values: list[str]) -> list[str]:
    # 解析并折叠后立刻物化,后续渲染、统计都复用同一份结果
    collapsed = list(collapse_addresses(ip_network(value, strict=False) for value in values))
    # 网络对象支持按网络地址和前缀进行排序,输出顺序更适合配置 diff
    collapsed.sort()
    return [network.with_prefixlen for network in collapsed]


print(compact_for_config([
    "203.0.113.128/25",
    "203.0.113.0/25",
    "198.51.100.0/24",
]))
['198.51.100.0/24', '203.0.113.0/24']

排序只是输出层的确定性处理,不会改变折叠规则。若下游把网段作为集合比较,建议把 with_prefixlen 结果作为规范化字符串保存,便于日志、缓存键或配置差异对比。

连续网段合并后形成更大 CIDR 网络以及保留未对齐网段的结果边界示意图
图2:可合并的对齐块与必须保留原样的边界结果示意图,不是运行截图。

值班排查时可以按这张清单处理

  1. 先确认列表元素是网络对象,而不是未解析的字符串或接口对象。
  2. 读取 network.version,混合 IPv4/IPv6 时按版本分组或提前拒绝。
  3. 对带主机位的输入决定是否使用 strict=False;这一步属于输入归一化,不是合并本身。
  4. 把返回迭代器物化一次,记录折叠前后的数量和最终 CIDR 文本。
  5. 用一对明确对齐的网络做回归样例,再用一个未对齐的相邻样例确认没有过度扩大范围。

常见问题

collapse_addresses() 能直接接受字符串吗?

不要这样用。先通过 ip_network() 把字符串解析为网络对象,再传入折叠函数。

IPv4 和 IPv6 可以放在同一个列表里吗?

不可以混合调用。先按 version 分组,分别折叠,或者在业务层拒绝混合输入。

两个网段地址连续就一定会合并吗?

不一定。它们还必须能组成符合边界对齐要求的更大 CIDR 网络;否则结果会保留多个网段。

为什么我第二次遍历结果为空?

返回值是迭代器,第一次遍历后已经消费。把它转成列表后再交给多个处理步骤。

归纳起来,collapse_addresses() 最适合做“同版本网络对象的 CIDR 归并”:先规范化输入,再分版本折叠,最后物化并固定输出。它负责地址范围的紧凑表达,不负责替你决定路由策略、访问权限或设备配置。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>