Python3如何使用f-string将整数格式化为标准IPv6地址?
问题:如何用Python f-string/str.format直接将整数格式化为带冒号分隔的标准IPv6字符串
我正尝试将整数格式化为IPv6地址。
此处的IPv6地址指代表0到2^128 - 1(340282366920938463463374607431768211455)区间整数的字符串,格式为32位十六进制数字,以冒号':'分隔为8组,每组4个字符。
我已知晓str(ipaddress.IPv6Address(n))和int(ipaddress.IPv6Address(s))的用法,但出于学习目的想要自己实现相关函数,目前已完成基础实现,正尝试优化。
我想找到用f-string或str.format将整数格式化为IPv6格式的方法,当前使用的单行实现如下:
ipv6 = ':'.join(hex(n).removeprefix('0x').zfill(32)[i:i+4] for i in range(0, 32, 4))
该实现使用字符串切片,运行速度较慢。
我已经通过正则表达式实现了IPv6地址的压缩功能,现在想要用字符串格式化的单行写法替换上述实现。
我已经完成了第一步的实现:将整数格式化为无前缀'0x'、补前导零到32位的十六进制字符串,代码如下:
"{0:0>32x}".format(n)
但我无法完成第二步的分隔逻辑。我搜索了Python中每N个字符插入分隔符的方法,大部分结果都不相关,仅找到两种可行方案:一种是我现在在用的自行实现的切片方法,另一种如下:
re.sub('([\da-fA-F]{4})', r'\1:', s, 7)
但正则表达式的运行速度很慢,性能测试结果如下:
In [221]: re.sub('([\da-fA-F]{4})', r'\1:', 'c42d7a7d155b93f7658c20c9fea598ff', 7) Out[221]: 'c42d:7a7d:155b:93f7:658c:20c9:fea5:98ff' In [222]: s = 'c42d7a7d155b93f7658c20c9fea598ff' In [223]: %timeit re.sub('([\da-fA-F]{4})', r'\1:', s, 7) 11.6 µs ± 993 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) In [224]: %timeit ':'.join(s[i:i+4] for i in range(0, 32, 4)) 2.11 µs ± 23.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
我在谷歌搜索的推荐关键词中看到了“Python f string thousand separator”,找到了如下语法:"{:,d}"
In [226]: "{:,d}".format(1234567890) Out[226]: '1,234,567,890'
该语法和我的需求很接近,但不符合要求:首先它针对整数(d格式符),其次每3个字符插入分隔符而非4个,且分隔符是逗号不能改为冒号,强行修改会导致语法错误。
请问将整数格式化为32位十六进制字符串、同时每4位插入一个冒号的正确f-string语法是什么?最好不需要嵌套f-string,且为单行实现。
我所指的f-string嵌套是类似如下的写法:
f'{f"{n:0>32x}"}'
我不知道该写法是否合法,但我不想要这种形式的实现。
目前我已经实现了如下写法:
"{0:0>32_x}".format(n)
In [248]: "{0:0>32_x}".format(260764824896579434326633182196140447999) Out[248]: 'c42d_7a7d_155b_93f7_658c_20c9_fea5_98ff'
但我无法将分隔符下划线改为冒号,我知道可以用str.replace实现,但还是希望能在一条格式化命令中完成。
性能对比结果如下:
In [253]: %timeit "{0:0>32_x}".format(260764824896579434326633182196140447999).replace('_', ':') 988 ns ± 7.72 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) In [254]: %timeit ':'.join([hex(260764824896579434326633182196140447999).replace('0x',"").zfill(32)[i:i+4] for i in range(0, 32, 4)]) 4.59 µs ± 493 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) In [255]: %timeit "{0:0>32_x}".format(260764824896579434326633182196140447999) 810 ns ± 48.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
字符串插值的性能远高于字符串切片。
解答
Python原生的字符串格式化语法(包括f-string和str.format)目前不支持自定义十六进制分组的分隔符,仅支持固定的下划线作为十六进制数字的分组符号,不存在纯单条格式化语句就能直接输出冒号分隔的IPv6字符串的语法。这是因为Python的格式化规范PEP 378中,仅规定了逗号、下划线两种可用于数字分组的分隔符,不支持自定义分隔符,所以无法直接在格式化参数中指定冒号作为分隔符。
你目前找到的"{0:0>32_x}".format(n).replace('_', ':')已经是最优的单行实现,性能比切片、正则方案高4~10倍,完全满足需求。对应的f-string写法如下,不属于你说的嵌套f-string,语法合法且非常简洁:
f"{n:0>32_x}".replace('_', ':')
如果一定要完全避免replace调用,只能退而求其次用你之前的切片拼接方案,但是性能会差很多。
内容的提问来源于stack exchange,提问作者Ξένη Γήινος

