【问题标题】:Python 3 how to format integer into IPv6 address using f string?Python 3 如何使用 f 字符串将整数格式化为 IPv6 地址?
【发布时间】:2022-01-01 21:15:14
【问题描述】:

我正在尝试将整数格式化为 IPv6 地址。

此处的 IPv6 地址表示一个字符串,表示 0 到 2^128 - 1 之间的整数 (340282366920938463463374607431768211455),格式为 32 个十六进制数字,用冒号 (':') 分隔成 8 个字段,每个字段 4 个数字。

现在我知道str(ipaddress.IPv6Address(n))int(ipaddress.IPv6Address(s)),但是我想以学习的名义编写自己的函数,并且我已经编写了它们,我正在努力改进它们。

我正在寻找一种使用 f-strings 或 str.format 将整数格式化为 IPv6 格式的方法,我目前使用这个单行:

ipv6 = ':'.join(hex(n).removeprefix('0x').zfill(32)[i:i+4] for i in range(0, 32, 4))

而且速度很慢,因为它使用字符串切片。

我已经编写了代码来使用正则表达式缩短 IPv6 地址,并且我希望使用字符串格式的 one-liner 来替换上面提到的 one-liner。

我已经使用这个实现了第一部分(将整数格式化为 32 位十六进制,前导零且不带 '0x' 前缀):

"{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 字符串千位分隔符并找到了这个语法:“{:,d}”

In [226]: "{:,d}".format(1234567890)
Out[226]: '1,234,567,890'

它非常接近我所寻求的,但不幸的是它不是方法,首先它处理整数(d),其次它每 3 个字符而不是 4 个字符插入一个分隔符,最后它使用逗号而不是冒号,我不能在不使语法无效的情况下更改它...

那么在将整数格式化为 32 位十六进制字符串时,每 4 位插入一个冒号的正确 f 字符串语法是什么?最好没有 f 字符串嵌套。我正在寻找单线。

通过 f 字符串嵌套,我的意思是这样的:

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)

字符串插值比字符串切片快得多。

【问题讨论】:

  • 这个':'有什么问题。join([hex(3402823669209384634633746074317682).replace('0x',"").zfill(32)[i:i+4] for i in range( 0, 32, 4)])
  • "什么是正确的 f-string 语法以每 4 位插入一个冒号"There is none. 你必须使用多个 f-strings。

标签: python python-3.x format f-string


【解决方案1】:

但是正则表达式很慢:

请注意,如果您想减少所需时间,可以使用re.compile,请考虑以下示例

import timeit
timeit.timeit(stmt='re.sub("(....)","\\1:",s)',setup='import re;s = "c42d7a7d155b93f7658c20c9fea598ff"') # 2.398639300000468
timeit.timeit(stmt='pat.sub("\\1:",s)',setup='import re;s = "c42d7a7d155b93f7658c20c9fea598ff";pat=re.compile("(....)")') # 1.6385453000002599

我使用不同的模式,因为我假设输入始终是 32 个十六进制数字,用它们替换每 4 个字符,后跟 :

【讨论】:

    【解决方案2】:

    我已经做到了。我正在寻找的格式说明符是"{:_x}"

    它将整数格式化为其十六进制表示,并每四个十六进制数字插入一个下划线。那么用str.replace将下划线替换成冒号就很简单了。

    但不幸的是,单独使用 f-string 填充时无法插入分隔符,需要其他工具。

    但它最难的部分是用十六进制格式表示整数并每四位插入一个冒号。剩下的唯一工作就是填充。

    len(s)很便宜,字符串乘法也很便宜。

    使用简单的算术我得到了这个:

    ipv6 = "{:_x}".format(n).replace('_', ':'); l = len(ipv6)              
    if l != 39: ipv6 = '0000:' * ((39 - l) // 5) + '0' * (4 - l % 5) + ipv6
    

    这个:

    ((39 - l) // 5)
    

    计算应在左侧插入多少字段。

    39 - l 计算需要多少个字符才能使字符串的长度为 39。然后使用地板除法计算所需的字段数。

    这个:

    (4 - l % 5)
    

    计算应在最后一个字段中插入多少前导零,每个字段应有 4 位数字,每个字段的分隔符为 5 个字符长,l % 5 计算最左边字段的位数,并减去它从 4 得到所需的前导零的数量。

    它比字符串切片快得多。


    其实确实可以一行f个字符串,加上位移和mod操作,只是比我预想的更麻烦更慢:

    def format_ipv6(n):
        return f'{(n>>112)%65536:0>4x}:{(n>>96)%65536:0>4x}:{(n>>80)%65536:0>4x}:{(n>>64)%65536:0>4x}:{(n>>48)%65536:0>4x}:{(n>>32)%65536:0>4x}:{(n>>16)%65536:0>4x}:{(n)%65536:0>4x}'
    

    但它确实是在一行中完成的,我曾尝试将它与具有 range 和 str.join 的生成器表达式一起使用,但这样做会进一步减慢它的速度......

    【讨论】:

      猜你喜欢
      • 2012-06-29
      • 1970-01-01
      • 1970-01-01
      • 2021-07-09
      • 2011-05-04
      • 2018-05-01
      • 1970-01-01
      • 2010-10-25
      相关资源
      最近更新 更多