首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python IPv6验证器、解析器和转换器

Python IPv6验证器、解析器和转换器
EN

Code Review用户
提问于 2021-11-22 14:12:57
回答 1查看 420关注 0票数 1

这是一个简单的Python3程序,它验证字符串是否是有效的IPv6地址,将IPv6地址解析为十六进制和十进制值,并将整数转换为IPv6格式。

我正在编写一个webscraper,为了降低网络IO绑定的时间复杂性,我需要通过编程查找目标地址的DNS记录,并相应地更改主机文件,因为DNS被GFW毒害。

我使用这个API:'https://www.robtex.com/dns-lookup/{website}‘来获得使用XPaths的地址,并且刮到的结果包含IPv4和IPv6地址,我想区分IPv4和IPv6地址,并分别验证它们。

对于IPv4地址,我编写了以下简单正则表达式:

代码语言:javascript
复制
'^((25[0-5]|2[0-4]\d|1?[1-9]\d?|0)\.){3}(25[0-5]|2[0-4]\d|1?[1-9]\d?|0)它在一个步骤中完成了验证,但是IPv6是另一回事,在多次尝试使用regex解决这个问题之后,我放弃了,我搜索了一个regex来验证IPv6,在我意识到完美工作的regexes是我改变方法的很长时间之后。IPv6地址是介于0到2^ 128之间的整数(340282366920938463463374607431768211456),表示为32位十六进制数字,格式为由7个冒号分隔的4个十六进制数字的8个字段。如果没有缩短规则,那么IPv6地址可以很容易地通过regexes验证。有两条缩短规则一起使用,第一条规则对每个字段中的前导零进行修剪。现在,仅使用应用的第一条规则,仍然可以使用此正则表达式验证IPv6:'^([\da-fA-F]{1,4}\:){7}([\da-fA-F]{1,4})但是还有第二条规则,一次忽略连续的零字段,然后使用'::'代替它,例如,0:0:0:0:0:0:0:0 -> ::
0:0:0:0:0:0:0:1 –> ::1
fe80:0:0:0:0:0:0:1 –> fe80::1
fe80:0:0:0:0:1:0:1 -> fe80::1:0:1由于第二条规则,省略字段可能至少需要一个正则表达式的八个位置,因此总共至少需要8个正则表达式.所以我写了我自己的代码,下面是:import re
from ipaddress import ip_address, IPv6Address
from typing import Iterable

COLONS = {':', '::'}
DIGITS = frozenset('0123456789ABCDEFabcdef')
MAX_IPV6 = 2**128 - 1
ALL_ZEROS = '0:'*7 + '0'
LEADING = re.compile('^(0:)+')
TRAILING = re.compile('(:0)+正如您可以看到的,第一个函数验证输入并将输入分割成块,在同一个循环中,我可以使用regex来拆分输入,但这比手动方法要快得多,我想如果我使用regex,我至少需要另一个for循环来验证结果,在这种方法中,我可以节省一个循环的成本,如果输入无效,可以尽早返回。关于另外两个函数,在我写完第一个函数之后,我就忍不住了。所有函数都正常工作,我引入错误的可能性正好为0,一切都会返回我假设正确的内容,也就是说,像'1::'这样的地址被函数视为有效,我不知道这种语法是否存在,但我认为它确实存在。如何改进我的代码?更新我编写了一个新函数,它只进行验证,因此速度要快得多。为什么我一开始就写这些函数?为什么我不只是使用ipaddress库?嗯,我正在重新发明轮子,我有充分的理由去做它。库代码并不完美,首先,我需要验证IPv6地址,是的,这意味着字符串可能无效,如果字符串不是有效的IP地址,ipaddress.ip_address会引发ValueError,所以我必须使用thing子句.In [690]: ipaddress.ip_address('100')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
 in 
----> 1 ipaddress.ip_address('100')

C:\Program Files\Python39\lib\ipaddress.py in ip_address(address)
     51         pass
     52
---> 53     raise ValueError('%r does not appear to be an IPv4 or IPv6 address' %
     54                      address)
     55

ValueError: '100' does not appear to be an IPv4 or IPv6 address它同时验证IPv4和IPv6地址,所以我必须使用isinstance检查.我的自定义函数只验证IPv6地址,不引发异常,因此不需要任何额外的步骤。其次,它比我的功能慢得多:Python 3.9.7 (tags/v3.9.7:1016ef3, Aug 30 2021, 20:19:38) [MSC v.1929 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.28.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]:
   ...:     if compress:
   ...:         ipv6 = ':'.join(trim_left(i) for i in ipv6.split(':'))
   ...:         if ipv6 == ALL_ZEROS:
   ...:             return '::'
   ...:
   ...:         elif LEADING.match(ipv6):
   ...:             return LEADING.sub('::', ipv6)
   ...:
   ...:         elif TRAILING.search(ipv6):
   ...:             return TRAILING.sub('::', ipv6)
   ...:
   ...:         ipv6 = MIDDLE.sub(':', ipv6, 1)
   ...:
   ...:     return ipv6
   ...:
   ...:
   ...: def ipaddress_test(s):
   ...:     try:
   ...:         return isinstance(ip_address(s), IPv6Address)
   ...:     except ValueError:
   ...:         return False
   ...:
   ...: if __name__ == '__main__':
   ...:     test_cases = [
   ...:         (42540766411282592856904265327123268393, '2001:db8::ff00:42:8329'),
   ...:         (42540766411282592875278671431329809193, '2001:db8::ff00:0:42:8329'),
   ...:         (0, '::'),
   ...:         (1, '::1'),
   ...:         (5192296858534827628530496329220096, '1::'),
   ...:         (338288524927261089654018896841347694593, 'fe80::1'),
   ...:         (160289081533862935099527363545323831451, '7896:8ddf:4b26:f07f:a4cd:65de:ee90:809b'),
   ...:         (264029623924138153874706093713361856950, 'c6a2:4182:24b2:20f3:2d00:d2bb:3619:e9b6'),
   ...:         (155302777326544552126794348175886719955, '74d6:3a18:151d:948f:d13e:4d87:4fed:1bd3'),
   ...:         (152846031713612901234538066636429037612, '72fd:132e:fe1d:d05c:27d0:6001:a05f:902c'),
   ...:         (21824427460045008308753734783456952407, '106b:3b59:a20b:25dc:61b9:698e:d1e:c057'),
   ...:         (267115622348742355941753354636068900005, 'c8f4:98fa:50b3:e935:2bc9:25b0:593b:cca5'),
   ...:         (16777215, '::ff:ffff'),
   ...:         (3232235777, '::c0a8:101'),
   ...:         (4294967295, '::ffff:ffff'),
   ...:         (2155905152, '::8080:8080'),
   ...:         (18446744073709551615, '::ffff:ffff:ffff:ffff'),
   ...:         (18446744073709551616, '::1:0:0:0:0')
   ...:     ]
   ...:     for number, ip in test_cases:
   ...:         assert to_ipv6(number) == ip
   ...:         assert parse_ipv6(ip)['decimal'] == number

In [2]: ip_address('2001:0db8:0000:0000:ff00:0000:0042:8329')
Out[2]: IPv6Address('2001:db8::ff00:0:42:8329')

In [3]: type(ip_address('2001:0db8:0000:0000:ff00:0000:0042:8329')) == IPv6Address
Out[3]: True

In [4]: %timeit ipaddress_test('2001:0db8:0000:0000:ff00:0000:0042:8329')
12.2 µs ± 685 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [5]: %timeit is_ipv6('2001:0db8:0000:0000:ff00:0000:0042:8329')
6.01 µs ± 557 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [6]: %timeit ipaddress_test('2001:0db8:0000:0000:ff00:0000:0042:8329')
12.3 µs ± 657 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [7]: %timeit parse_ipv6('2001:0db8:0000:0000:ff00:0000:0042:8329')
15.2 µs ± 302 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [8]: %timeit parse_ipv6('2001:db8::ff00:0:42:8329')
14.6 µs ± 629 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [9]: %timeit is_ipv6('2001:db8::ff00:0:42:8329')
4.02 µs ± 561 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [10]: %timeit ipaddress_test('2001:db8::ff00:0:42:8329')
10.4 µs ± 204 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [11]: %%timeit
    ...: for number, ip in test_cases:
    ...:     assert is_ipv6(ip) == True
62.5 µs ± 5.24 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [12]: %%timeit
    ...: for number, ip in test_cases:
    ...:     assert ipaddress_test(ip) == True
169 µs ± 4.58 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [13]: is_ipv6('f:'*7)
Out[13]: False

In [14]: is_ipv6('f:'*8)
Out[14]: False

In [15]: is_ipv6('f:'*9)
Out[15]: False

In [16]: is_ipv6('f:'*7+':')
Out[16]: True

In [17]: is_ipv6('f:'*7+'f')
Out[17]: True

In [18]: %timeit is_ipv6('f:'*7)
2.57 µs ± 392 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [19]: %timeit is_ipv6('f:'*8)
2.9 µs ± 468 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [20]: %timeit is_ipv6('f:'*9)
3.06 µs ± 368 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [21]: %timeit is_ipv6('f:'*7+':')
2.76 µs ± 468 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [22]: ipaddress_test('f:'*7)
Out[22]: False

In [23]: ipaddress_test('f:'*8)
Out[23]: False

In [24]: ipaddress_test('f:'*9)
Out[24]: False

In [25]: ipaddress_test('f:'*7+'f')
Out[25]: True

In [26]: %timeit ipaddress_test('f:'*7)
5.88 µs ± 684 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [27]: %timeit ipaddress_test('f:'*8)
5.87 µs ± 753 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [28]: %timeit ipaddress_test('f:'*9)
5.14 µs ± 770 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [29]: %timeit ipaddress_test('f:'*7+'f')
11.1 µs ± 400 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [30]: ipaddress_test('f::')
Out[30]: True

In [31]: ipaddress_test('::f')
Out[31]: True

In [32]: ipaddress_test('f::f')
Out[32]: True

In [33]: ipaddress_test('f:::f')
Out[33]: False

In [34]: %timeit ipaddress_test('f::')
5.53 µs ± 639 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [35]: %timeit ipaddress_test('::f')
5.54 µs ± 552 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [36]: %timeit ipaddress_test('f:::f')
5.25 µs ± 581 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [37]: %timeit ipaddress_test('100')
4.52 µs ± 591 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [38]: is_ipv6('100')
Out[38]: False

In [39]: %timeit is_ipv6('100')
747 ns ± 34.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [40]: %timeit is_ipv6('f::')
741 ns ± 60.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [41]: %timeit is_ipv6('::f')
735 ns ± 43 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [42]: %timeit is_ipv6('f:::f')
864 ns ± 49.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [43]: %timeit is_ipv6('windows')
278 ns ± 38.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [44]: %timeit ipaddress_test('windows')
4.62 µs ± 749 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [45]:如您所见,库代码远不及我的函数的速度,特别是我的自定义函数指出无效输入比库代码快得多.我说清楚了吗?同样,IPv6Address不够快:In [107]: from ipaddress import AddressValueError

In [108]: IPv6Address('100')
---------------------------------------------------------------------------
AddressValueError                         Traceback (most recent call last)
 in 
----> 1 IPv6Address('100')

C:\Program Files\Python39\lib\ipaddress.py in __init__(self, address)
   1916         addr_str, self._scope_id = self._split_scope_id(addr_str)
   1917
-> 1918         self._ip = self._ip_int_from_string(addr_str)
   1919
   1920     def __str__(self):

C:\Program Files\Python39\lib\ipaddress.py in _ip_int_from_string(cls, ip_str)
   1629         if len(parts) < _min_parts:
   1630             msg = "At least %d parts expected in %r" % (_min_parts, ip_str)
-> 1631             raise AddressValueError(msg)
   1632
   1633         # If the address has an IPv4-style suffix, convert it to hexadecimal.

AddressValueError: At least 3 parts expected in '100'

In [109]: def IPv6Address_test(s):
     ...:     try:
     ...:         IPv6Address(s)
     ...:         return True
     ...:     except AddressValueError:
     ...:         return False

In [110]: %timeit IPv6Address_test('100')
2.13 µs ± 33.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [111]: IPv6Address_test('100::')
Out[111]: True

In [112]: IPv6Address_test('255.255.255.255')
Out[112]: False

In [113]: valid_long = [
     ...:     '2001:0db8:0000:0000:ff00:0000:0042:8329',
     ...:     '2001:db8::ff00:0:42:8329',
     ...:     'f:'*7+':',
     ...:     'f:'*7+'f',
     ...:     '7896:8ddf:4b26:f07f:a4cd:65de:ee90:809b',
     ...:     'c6a2:4182:24b2:20f3:2d00:d2bb:3619:e9b6',
     ...:     '74d6:3a18:151d:948f:d13e:4d87:4fed:1bd3',
     ...:     '72fd:132e:fe1d:d05c:27d0:6001:a05f:902c',
     ...:     '106b:3b59:a20b:25dc:61b9:698e:d1e:c057',
     ...:     'c8f4:98fa:50b3:e935:2bc9:25b0:593b:cca5',
     ...:     '2001:db8::ff00:42:8329',
     ...:     '2001:db8::ff00:0:42:8329'
     ...: ]

In [114]: valid_short = [
     ...:     '::',
     ...:     '::1',
     ...:     '1::',
     ...:     'fe80::1',
     ...:     '::ff:ffff',
     ...:     '::c0a8:101',
     ...:     '::ffff:ffff',
     ...:     '::8080:8080',
     ...:     '::ffff:ffff:ffff:ffff',
     ...:     '::1:0:0:0:0',
     ...:     'f::',
     ...:     '::f',
     ...:     'f::f'
     ...: ]

In [115]: invalid = [
     ...:     '100',
     ...:     'windows',
     ...:     'intelligence',
     ...:     'this is not an IPv6 address',
     ...:     'esperanza',
     ...:     'hispana',
     ...:     'esperanta',
     ...:     '255.255.255.255',
     ...:     '192.168.1.1',
     ...:     '127.0.0.1',
     ...:     '151.101.129.69',
     ...:     'f:::f',
     ...:     'f:'*7,
     ...:     'f:'*8,
     ...:     'f:'*9
     ...: ]

In [116]: %timeit for i in valid_long: assert is_ipv6(i) == True
65 µs ± 7.37 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [117]: %timeit for i in valid_long: assert IPv6Address_test(i) == True
122 µs ± 5.68 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [118]: %timeit for i in valid_short: assert is_ipv6(i) == True
19.8 µs ± 485 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [119]: %timeit for i in valid_short: assert IPv6Address_test(i) == True
59.4 µs ± 6.72 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [120]: %timeit for i in invalid: assert is_ipv6(i) == False
16.6 µs ± 650 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [121]: %timeit for i in invalid: assert IPv6Address_test(i) == False
38.3 µs ± 7.13 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [122]: %timeit is_ipv6('::')
526 ns ± 25.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [123]: is_ipv6('::')
Out[123]: True

In [124]: %timeit is_ipv6('::1')
739 ns ± 41.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [125]: %timeit is_ipv6('1::')
741 ns ± 33.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [126]: %timeit is_ipv6('100')
761 ns ± 43.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [127]: %timeit IPv6Address_test('::')
2.65 µs ± 468 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [128]: %timeit IPv6Address_test('::1')
3.38 µs ± 687 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [129]: %timeit IPv6Address_test('1::')
3.4 µs ± 684 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [130]: %timeit IPv6Address_test('100')
2.13 µs ± 41.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [131]: %timeit IPv6Address_test('g')
2.11 µs ± 29 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [132]: %timeit is_ipv6('g')
289 ns ± 54.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [133]:
```#qcStackCode#它在一个步骤中完成了验证,但是IPv6是另一回事,在多次尝试使用regex解决这个问题之后,我放弃了,我搜索了一个regex来验证IPv6,在我意识到完美工作的regexes是我改变方法的很长时间之后。IPv6地址是介于0到2^ 128之间的整数(340282366920938463463374607431768211456),表示为32位十六进制数字,格式为由7个冒号分隔的4个十六进制数字的8个字段。如果没有缩短规则,那么IPv6地址可以很容易地通过regexes验证。有两条缩短规则一起使用,第一条规则对每个字段中的前导零进行修剪。现在,仅使用应用的第一条规则,仍然可以使用此正则表达式验证IPv6:A2但是还有第二条规则,一次忽略连续的零字段,然后使用D3代替它,例如,A4由于第二条规则,省略字段可能至少需要一个正则表达式的八个位置,因此总共至少需要8个正则表达式.所以我写了我自己的代码,下面是:A5正如您可以看到的,第一个函数验证输入并将输入分割成块,在同一个循环中,我可以使用regex来拆分输入,但这比手动方法要快得多,我想如果我使用regex,我至少需要另一个for循环来验证结果,在这种方法中,我可以节省一个循环的成本,如果输入无效,可以尽早返回。关于另外两个函数,在我写完第一个函数之后,我就忍不住了。所有函数都正常工作,我引入错误的可能性正好为0,一切都会返回我假设正确的内容,也就是说,像D6这样的地址被函数视为有效,我不知道这种语法是否存在,但我认为它确实存在。如何改进我的代码?K17更新K28我编写了一个新函数,它只进行验证,因此速度要快得多。为什么我一开始就写这些函数?为什么我不只是使用D9库?嗯,我正在重新发明轮子,我有充分的理由去做它。库代码并不完美,首先,我需要验证IPv6地址,是的,这意味着字符串可能无效,如果字符串不是有效的IP地址,D10会引发D11,所以我必须使用thing子句.A12它同时验证IPv4和IPv6地址,所以我必须使用D13检查.我的自定义函数只验证IPv6地址,不引发异常,因此不需要任何额外的步骤。其次,它比我的功能慢得多:A14如您所见,库代码远不及我的函数的速度,特别是我的自定义函数指出无效输入比库代码快得多.我说清楚了吗?同样,D15不够快:A16A17但是还有第二条规则,一次忽略连续的零字段,然后使用D3代替它,例如,A4由于第二条规则,省略字段可能至少需要一个正则表达式的八个位置,因此总共至少需要8个正则表达式.所以我写了我自己的代码,下面是:A5正如您可以看到的,第一个函数验证输入并将输入分割成块,在同一个循环中,我可以使用regex来拆分输入,但这比手动方法要快得多,我想如果我使用regex,我至少需要另一个for循环来验证结果,在这种方法中,我可以节省一个循环的成本,如果输入无效,可以尽早返回。关于另外两个函数,在我写完第一个函数之后,我就忍不住了。所有函数都正常工作,我引入错误的可能性正好为0,一切都会返回我假设正确的内容,也就是说,像D6这样的地址被函数视为有效,我不知道这种语法是否存在,但我认为它确实存在。如何改进我的代码?K17更新K28我编写了一个新函数,它只进行验证,因此速度要快得多。为什么我一开始就写这些函数?为什么我不只是使用D9库?嗯,我正在重新发明轮子,我有充分的理由去做它。库代码并不完美,首先,我需要验证IPv6地址,是的,这意味着字符串可能无效,如果字符串不是有效的IP地址,D10会引发D11,所以我必须使用thing子句.A12它同时验证IPv4和IPv6地址,所以我必须使用D13检查.我的自定义函数只验证IPv6地址,不引发异常,因此不需要任何额外的步骤。其次,它比我的功能慢得多:A14如您所见,库代码远不及我的函数的速度,特别是我的自定义函数指出无效输入比库代码快得多.我说清楚了吗?同样,D15不够快:A16A17

它在一个步骤中完成了验证,但是IPv6是另一回事,在多次尝试使用regex解决这个问题之后,我放弃了,我搜索了一个regex来验证IPv6,在我意识到完美工作的regexes是我改变方法的很长时间之后。

IPv6地址是介于0到2^ 128之间的整数(340282366920938463463374607431768211456),表示为32位十六进制数字,格式为由7个冒号分隔的4个十六进制数字的8个字段。

如果没有缩短规则,那么IPv6地址可以很容易地通过regexes验证。

有两条缩短规则一起使用,第一条规则对每个字段中的前导零进行修剪。

现在,仅使用应用的第一条规则,仍然可以使用此正则表达式验证IPv6:

A2

但是还有第二条规则,一次忽略连续的零字段,然后使用D3代替它,例如,

A4

由于第二条规则,省略字段可能至少需要一个正则表达式的八个位置,因此总共至少需要8个正则表达式.

所以我写了我自己的代码,下面是:

A5

正如您可以看到的,第一个函数验证输入并将输入分割成块,在同一个循环中,我可以使用regex来拆分输入,但这比手动方法要快得多,我想如果我使用regex,我至少需要另一个for循环来验证结果,在这种方法中,我可以节省一个循环的成本,如果输入无效,可以尽早返回。

关于另外两个函数,在我写完第一个函数之后,我就忍不住了。

所有函数都正常工作,我引入错误的可能性正好为0,一切都会返回我假设正确的内容,也就是说,像D6这样的地址被函数视为有效,我不知道这种语法是否存在,但我认为它确实存在。

如何改进我的代码?

K17更新K28

我编写了一个新函数,它只进行验证,因此速度要快得多。

为什么我一开始就写这些函数?为什么我不只是使用D9库?

嗯,我正在重新发明轮子,我有充分的理由去做它。

库代码并不完美,首先,我需要验证IPv6地址,是的,这意味着字符串可能无效,如果字符串不是有效的IP地址,D10会引发D11,所以我必须使用thing子句.

A12

它同时验证IPv4和IPv6地址,所以我必须使用D13检查.

我的自定义函数只验证IPv6地址,不引发异常,因此不需要任何额外的步骤。

其次,它比我的功能慢得多:

A14

如您所见,库代码远不及我的函数的速度,特别是我的自定义函数指出无效输入比库代码快得多.

我说清楚了吗?

同样,D15不够快:

A16A17) MIDDLE = re.compile('(:0)+') def is_ipv6(ip: str) -> bool: if not isinstance(ip, str): raise TypeError('Argument must be an instance of str') if not ip or len(ip) > 39: return False first = True digits = 0 colons = 0 fields = 0 compressed = False for i in ip: if i == ':': digits = 0 first = False colons += 1 if colons == 2: if not compressed: compressed = True else: return False elif colons > 2: return False else: if i not in DIGITS: return False digits += 1 if digits > 4: return False if colons or first: first = False colons = 0 fields += 1 if fields > 8 - compressed: return False if (fields == 8 and colons != 1) or compressed: return True return False def split_ipv6(ip: str) -> Iterable[str]: if not isinstance(ip, str): raise TypeError('Argument must be an instance of str') buffer = '' chunks = [] digits = 0 colons = 0 fields = 0 compressed = False for i in ip: tail = True if i == ':': if digits: chunks.append(buffer) digits = 0 colons += 1 if colons == 2: if not compressed: compressed = True tail = False else: return False if colons > 2: return False else: if i not in DIGITS: return False digits += 1 if digits > 4: return False if colons or not buffer: if colons: chunks.append(':' * colons) colons = 0 fields += 1 buffer = i if fields > 8 - compressed: return False else: buffer += i if tail: chunks.append(buffer) else: chunks.append('::') if (fields == 8 and colons != 1) or compressed: return chunks return False def parse_ipv6(ip: str) -> dict: segments = split_ipv6(ip) if not segments: raise ValueError('Argument is not a valid IPv6 address') compressed = False empty_fields = None if '::' in segments: fields = ['0'] * 8 compressed = True cut = segments.index('::') left = [i for i in segments[:cut] if i not in COLONS] right = [i for i in segments[cut+1:] if i not in COLONS] fields[8-len(right):] = right fields[:len(left)] = left empty_fields = [i for i, f in enumerate(fields) if f == '0'] else: fields = [c for c in segments if c not in COLONS] digits = [int(f, 16) for f in fields] # decimal = sum(d * 65536 ** i for i, d in enumerate(digits[::-1])) hexadecimal = '0x' + ''.join(i.zfill(4) for i in fields) decimal = int(hexadecimal, 16) parsed = { 'segments': segments, 'fields': fields, 'digits': digits, 'hexadecimal': hexadecimal, 'decimal': decimal, 'compressed': compressed, 'empty fields': empty_fields } return parsed def trim_left(s: str) -> str: return s[:-1].lstrip('0') + s[-1] def to_ipv6(n: int, compress=True) -> str: if not isinstance(n, int): raise TypeError('Argument should be an instance of int') if not 0 <= n <= MAX_IPV6: raise ValueError('Argument is not in the valid range that IPv6 can represent') hexa = hex(n).removeprefix('0x').zfill(32) ipv6 = ':'.join(hexa[i:i+4] for i in range(0, 32, 4)) if compress: ipv6 = ':'.join(trim_left(i) for i in ipv6.split(':')) if ipv6 == ALL_ZEROS: return '::' elif LEADING.match(ipv6): return LEADING.sub('::', ipv6) elif TRAILING.search(ipv6): return TRAILING.sub('::', ipv6) ipv6 = MIDDLE.sub(':', ipv6, 1) return ipv6 if __name__ == '__main__': test_cases = [ (42540766411282592856904265327123268393, '2001:db8::ff00:42:8329'), (42540766411282592875278671431329809193, '2001:db8::ff00:0:42:8329'), (0, '::'), (1, '::1'), (5192296858534827628530496329220096, '1::'), (338288524927261089654018896841347694593, 'fe80::1'), (160289081533862935099527363545323831451, '7896:8ddf:4b26:f07f:a4cd:65de:ee90:809b'), (264029623924138153874706093713361856950, 'c6a2:4182:24b2:20f3:2d00:d2bb:3619:e9b6'), (155302777326544552126794348175886719955, '74d6:3a18:151d:948f:d13e:4d87:4fed:1bd3'), (152846031713612901234538066636429037612, '72fd:132e:fe1d:d05c:27d0:6001:a05f:902c'), (21824427460045008308753734783456952407, '106b:3b59:a20b:25dc:61b9:698e:d1e:c057'), (267115622348742355941753354636068900005, 'c8f4:98fa:50b3:e935:2bc9:25b0:593b:cca5'), (16777215, '::ff:ffff'), (3232235777, '::c0a8:101'), (4294967295, '::ffff:ffff'), (2155905152, '::8080:8080'), (18446744073709551615, '::ffff:ffff:ffff:ffff'), (18446744073709551616, '::1:0:0:0:0') ] for number, ip in test_cases: assert to_ipv6(number) == ip assert parse_ipv6(ip)['decimal'] == number

正如您可以看到的,第一个函数验证输入并将输入分割成块,在同一个循环中,我可以使用regex来拆分输入,但这比手动方法要快得多,我想如果我使用regex,我至少需要另一个for循环来验证结果,在这种方法中,我可以节省一个循环的成本,如果输入无效,可以尽早返回。

关于另外两个函数,在我写完第一个函数之后,我就忍不住了。

所有函数都正常工作,我引入错误的可能性正好为0,一切都会返回我假设正确的内容,也就是说,像D6这样的地址被函数视为有效,我不知道这种语法是否存在,但我认为它确实存在。

如何改进我的代码?

K17更新K28

我编写了一个新函数,它只进行验证,因此速度要快得多。

为什么我一开始就写这些函数?为什么我不只是使用D9库?

嗯,我正在重新发明轮子,我有充分的理由去做它。

库代码并不完美,首先,我需要验证IPv6地址,是的,这意味着字符串可能无效,如果字符串不是有效的IP地址,D10会引发D11,所以我必须使用thing子句.

A12

它同时验证IPv4和IPv6地址,所以我必须使用D13检查.

我的自定义函数只验证IPv6地址,不引发异常,因此不需要任何额外的步骤。

其次,它比我的功能慢得多:

A14

如您所见,库代码远不及我的函数的速度,特别是我的自定义函数指出无效输入比库代码快得多.

我说清楚了吗?

同样,D15不够快:

A16A17

它在一个步骤中完成了验证,但是IPv6是另一回事,在多次尝试使用regex解决这个问题之后,我放弃了,我搜索了一个regex来验证IPv6,在我意识到完美工作的regexes是我改变方法的很长时间之后。

IPv6地址是介于0到2^ 128之间的整数(340282366920938463463374607431768211456),表示为32位十六进制数字,格式为由7个冒号分隔的4个十六进制数字的8个字段。

如果没有缩短规则,那么IPv6地址可以很容易地通过regexes验证。

有两条缩短规则一起使用,第一条规则对每个字段中的前导零进行修剪。

现在,仅使用应用的第一条规则,仍然可以使用此正则表达式验证IPv6:

A2

但是还有第二条规则,一次忽略连续的零字段,然后使用D3代替它,例如,

A4

由于第二条规则,省略字段可能至少需要一个正则表达式的八个位置,因此总共至少需要8个正则表达式.

所以我写了我自己的代码,下面是:

A5

正如您可以看到的,第一个函数验证输入并将输入分割成块,在同一个循环中,我可以使用regex来拆分输入,但这比手动方法要快得多,我想如果我使用regex,我至少需要另一个for循环来验证结果,在这种方法中,我可以节省一个循环的成本,如果输入无效,可以尽早返回。

关于另外两个函数,在我写完第一个函数之后,我就忍不住了。

所有函数都正常工作,我引入错误的可能性正好为0,一切都会返回我假设正确的内容,也就是说,像D6这样的地址被函数视为有效,我不知道这种语法是否存在,但我认为它确实存在。

如何改进我的代码?

K17更新K28

我编写了一个新函数,它只进行验证,因此速度要快得多。

为什么我一开始就写这些函数?为什么我不只是使用D9库?

嗯,我正在重新发明轮子,我有充分的理由去做它。

库代码并不完美,首先,我需要验证IPv6地址,是的,这意味着字符串可能无效,如果字符串不是有效的IP地址,D10会引发D11,所以我必须使用thing子句.

A12

它同时验证IPv4和IPv6地址,所以我必须使用D13检查.

我的自定义函数只验证IPv6地址,不引发异常,因此不需要任何额外的步骤。

其次,它比我的功能慢得多:

A14

如您所见,库代码远不及我的函数的速度,特别是我的自定义函数指出无效输入比库代码快得多.

我说清楚了吗?

同样,D15不够快:

A16A17

但是还有第二条规则,一次忽略连续的零字段,然后使用D3代替它,例如,

A4

由于第二条规则,省略字段可能至少需要一个正则表达式的八个位置,因此总共至少需要8个正则表达式.

所以我写了我自己的代码,下面是:

A5

正如您可以看到的,第一个函数验证输入并将输入分割成块,在同一个循环中,我可以使用regex来拆分输入,但这比手动方法要快得多,我想如果我使用regex,我至少需要另一个for循环来验证结果,在这种方法中,我可以节省一个循环的成本,如果输入无效,可以尽早返回。

关于另外两个函数,在我写完第一个函数之后,我就忍不住了。

所有函数都正常工作,我引入错误的可能性正好为0,一切都会返回我假设正确的内容,也就是说,像D6这样的地址被函数视为有效,我不知道这种语法是否存在,但我认为它确实存在。

如何改进我的代码?

K17更新K28

我编写了一个新函数,它只进行验证,因此速度要快得多。

为什么我一开始就写这些函数?为什么我不只是使用D9库?

嗯,我正在重新发明轮子,我有充分的理由去做它。

库代码并不完美,首先,我需要验证IPv6地址,是的,这意味着字符串可能无效,如果字符串不是有效的IP地址,D10会引发D11,所以我必须使用thing子句.

A12

它同时验证IPv4和IPv6地址,所以我必须使用D13检查.

我的自定义函数只验证IPv6地址,不引发异常,因此不需要任何额外的步骤。

其次,它比我的功能慢得多:

A14

如您所见,库代码远不及我的函数的速度,特别是我的自定义函数指出无效输入比库代码快得多.

我说清楚了吗?

同样,D15不够快:

A16A17

它在一个步骤中完成了验证,但是IPv6是另一回事,在多次尝试使用regex解决这个问题之后,我放弃了,我搜索了一个regex来验证IPv6,在我意识到完美工作的regexes是我改变方法的很长时间之后。

IPv6地址是介于0到2^ 128之间的整数(340282366920938463463374607431768211456),表示为32位十六进制数字,格式为由7个冒号分隔的4个十六进制数字的8个字段。

如果没有缩短规则,那么IPv6地址可以很容易地通过regexes验证。

有两条缩短规则一起使用,第一条规则对每个字段中的前导零进行修剪。

现在,仅使用应用的第一条规则,仍然可以使用此正则表达式验证IPv6:

A2

但是还有第二条规则,一次忽略连续的零字段,然后使用D3代替它,例如,

A4

由于第二条规则,省略字段可能至少需要一个正则表达式的八个位置,因此总共至少需要8个正则表达式.

所以我写了我自己的代码,下面是:

A5

正如您可以看到的,第一个函数验证输入并将输入分割成块,在同一个循环中,我可以使用regex来拆分输入,但这比手动方法要快得多,我想如果我使用regex,我至少需要另一个for循环来验证结果,在这种方法中,我可以节省一个循环的成本,如果输入无效,可以尽早返回。

关于另外两个函数,在我写完第一个函数之后,我就忍不住了。

所有函数都正常工作,我引入错误的可能性正好为0,一切都会返回我假设正确的内容,也就是说,像D6这样的地址被函数视为有效,我不知道这种语法是否存在,但我认为它确实存在。

如何改进我的代码?

K17更新K28

我编写了一个新函数,它只进行验证,因此速度要快得多。

为什么我一开始就写这些函数?为什么我不只是使用D9库?

嗯,我正在重新发明轮子,我有充分的理由去做它。

库代码并不完美,首先,我需要验证IPv6地址,是的,这意味着字符串可能无效,如果字符串不是有效的IP地址,D10会引发D11,所以我必须使用thing子句.

A12

它同时验证IPv4和IPv6地址,所以我必须使用D13检查.

我的自定义函数只验证IPv6地址,不引发异常,因此不需要任何额外的步骤。

其次,它比我的功能慢得多:

A14

如您所见,库代码远不及我的函数的速度,特别是我的自定义函数指出无效输入比库代码快得多.

我说清楚了吗?

同样,D15不够快:

A16A17

EN

回答 1

Code Review用户

回答已采纳

发布于 2021-11-24 02:00:04

我写了这个简单的正则表达式

差不多吧。你没有用一种非常简单的方式写它。将它写在多行上,添加注释。

65536 ** i似乎是实现16*i左移1位的一种不太明显的方法。

这是:

代码语言:javascript
复制
    hexadecimal = '0x' + ''.join(i.zfill(4) for i in fields)
    decimal = int(hexadecimal, 16)

加入字符串域中的字段,但我认为在整数域中加入它们更有意义--即使用位移位操作。即使您想在字符串域中加入它们,也不需要添加0x

parse_ipv6目前有一个弱类型--一个dict --并且应该更喜欢类似于命名元组的东西。

让您接受的测试用例值以十进制表示是一种奇怪的选择。像0xFFFF_FFFF这样的十六进制文字将比4294967295更明显地正确。

我正在重新发明轮子,我有充分的理由去做它。

我不敢苟同,但让我们深入研究一下:

如果字符串不是有效的IP地址,ipaddress.ip_address将引发ValueError,因此我必须使用try/catch子句。

这是一个特征,不是一个bug。考虑到IP解析例程的典型使用者,编写良好的代码将比布尔值更好地利用异常,如果需要的话,执行except也是非常简单的。

它同时验证IPv4和IPv6地址,所以我必须使用isinstance检查.

那是因为你用错了。您不应该调用ip_address,而应该直接构造IPv6Address

它比我的功能慢得多。

首先,公平的比较只能使用IPv6Address(),而不是强迫ip_address尝试解析一个有保证失败的IPv4地址。

除此之外:无论是否修复上面的例程,性能都是相当的,相对来说,一个应用程序需要验证数千个地址是比较少见的,而且几乎总是,正确性和可维护性比性能更重要。您的代码是非琐碎的,与使用内置代码相比,维护起来将是一个真正的痛苦。你有多自信你的代码是正确的? 80%?90%?您认为您可以超越Python社区的稳定性和测试覆盖率吗?有时需要重新发明车轮,但这不是其中之一。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/270290

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档