我正在嘗試將整數格式化為 IPv6 地址。
這里的IPv6地址是指代表0到2^128-1之間的整數(340282366920938463463374607431768211455)的字串,格式為32個十六進制數字,用冒號(':')分隔成8個欄位,每個欄位4位。
現在我知道了str(ipaddress.IPv6Address(n))and int(ipaddress.IPv6Address(s)),但是我想以學習的名義寫自己的函式,而且我已經寫好了,我正在努力改進它們。
我正在尋找一種使用 f-strings 或 將整數格式化為 IPv6 格式的方法str.format,我目前使用這個單行:
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千位分隔符并找到了這個語法:“{:,d}”
In [226]: "{:,d}".format(1234567890)
Out[226]: '1,234,567,890'
它與我所尋求的非常接近,但不幸的是它不是方法,首先它處理整數 ( d),其次它每 3 個字符而不是 4 個字符插入一個分隔符,最后它使用逗號而不是冒號,并且我不能在不使語法無效的情況下改變它......
那么在將整數格式化為 32 位十六進制字串時每 4 位插入一個冒號的正確 f-string 語法是什么?最好沒有 f 弦嵌套。我正在尋找單線。
通過 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)
字串插值比字串切片快得多。
uj5u.com熱心網友回復:
但是正則運算式很慢:
請注意,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 個字符替換為:.
uj5u.com熱心網友回復:
我設法做到了。我正在尋找的格式說明符是"{:_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}'
但它確實在一行中完成,我嘗試將它與具有范圍和 str.join 的生成器運算式一起使用,但這樣做會進一步減慢速度......
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/364713.html
上一篇:根據最近的記錄加入pyspark
