Skip to content

Commit f7c7708

Browse files
authored
feat: improve performance of ip address caching (#1392)
1 parent b7c45e2 commit f7c7708

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

src/zeroconf/_utils/ipaddress.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,33 @@
2828
from .._dns import DNSAddress
2929
from ..const import _TYPE_AAAA
3030

31+
if sys.version_info >= (3, 9, 0):
32+
from functools import cache
33+
else:
34+
cache = lru_cache(maxsize=None)
35+
3136
bytes_ = bytes
3237
int_ = int
3338
IPADDRESS_SUPPORTS_SCOPE_ID = sys.version_info >= (3, 9, 0)
3439

3540

3641
class ZeroconfIPv4Address(IPv4Address):
37-
__slots__ = ("_str", "_is_link_local", "_is_unspecified")
42+
__slots__ = (
43+
"_str",
44+
"_is_link_local",
45+
"_is_unspecified",
46+
"_is_loopback",
47+
"__hash__",
48+
)
3849

3950
def __init__(self, *args: Any, **kwargs: Any) -> None:
4051
"""Initialize a new IPv4 address."""
4152
super().__init__(*args, **kwargs)
4253
self._str = super().__str__()
4354
self._is_link_local = super().is_link_local
4455
self._is_unspecified = super().is_unspecified
56+
self._is_loopback = super().is_loopback
57+
self.__hash__ = cache(lambda: IPv4Address.__hash__(self)) # type: ignore[method-assign]
4558

4659
def __str__(self) -> str:
4760
"""Return the string representation of the IPv4 address."""
@@ -57,16 +70,29 @@ def is_unspecified(self) -> bool:
5770
"""Return True if this is an unspecified address."""
5871
return self._is_unspecified
5972

73+
@property
74+
def is_loopback(self) -> bool:
75+
"""Return True if this is a loop back."""
76+
return self._is_loopback
77+
6078

6179
class ZeroconfIPv6Address(IPv6Address):
62-
__slots__ = ("_str", "_is_link_local", "_is_unspecified")
80+
__slots__ = (
81+
"_str",
82+
"_is_link_local",
83+
"_is_unspecified",
84+
"_is_loopback",
85+
"__hash__",
86+
)
6387

6488
def __init__(self, *args: Any, **kwargs: Any) -> None:
6589
"""Initialize a new IPv6 address."""
6690
super().__init__(*args, **kwargs)
6791
self._str = super().__str__()
6892
self._is_link_local = super().is_link_local
6993
self._is_unspecified = super().is_unspecified
94+
self._is_loopback = super().is_loopback
95+
self.__hash__ = cache(lambda: IPv6Address.__hash__(self)) # type: ignore[method-assign]
7096

7197
def __str__(self) -> str:
7298
"""Return the string representation of the IPv6 address."""
@@ -82,6 +108,11 @@ def is_unspecified(self) -> bool:
82108
"""Return True if this is an unspecified address."""
83109
return self._is_unspecified
84110

111+
@property
112+
def is_loopback(self) -> bool:
113+
"""Return True if this is a loop back."""
114+
return self._is_loopback
115+
85116

86117
@lru_cache(maxsize=512)
87118
def _cached_ip_addresses(

tests/utils/test_ipaddress.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,17 @@ def test_cached_ip_addresses_wrapper():
1919
str(ipaddress.cached_ip_addresses(b"&\x06(\x00\x02 \x00\x01\x02H\x18\x93%\xc8\x19F"))
2020
== "2606:2800:220:1:248:1893:25c8:1946"
2121
)
22-
assert ipaddress.cached_ip_addresses("::1") == ipaddress.IPv6Address("::1")
22+
loop_back_ipv6 = ipaddress.cached_ip_addresses("::1")
23+
assert loop_back_ipv6 == ipaddress.IPv6Address("::1")
24+
assert loop_back_ipv6.is_loopback is True
25+
26+
assert hash(loop_back_ipv6) == hash(ipaddress.IPv6Address("::1"))
27+
28+
loop_back_ipv4 = ipaddress.cached_ip_addresses("127.0.0.1")
29+
assert loop_back_ipv4 == ipaddress.IPv4Address("127.0.0.1")
30+
assert loop_back_ipv4.is_loopback is True
31+
32+
assert hash(loop_back_ipv4) == hash(ipaddress.IPv4Address("127.0.0.1"))
2333

2434
ipv4 = ipaddress.cached_ip_addresses("169.254.0.0")
2535
assert ipv4 is not None

0 commit comments

Comments
 (0)