Skip to content

Commit 868f28f

Browse files
bluetoothbotbdraco
andauthored
perf: precompute address hash instead of per-instance lru_cache (#1774)
Co-authored-by: J. Nick Koston <nick@koston.org>
1 parent b80acaf commit 868f28f

2 files changed

Lines changed: 27 additions & 5 deletions

File tree

src/zeroconf/_utils/ipaddress.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
from __future__ import annotations
2424

25-
from functools import cache, lru_cache
25+
from functools import lru_cache
2626
from ipaddress import AddressValueError, IPv4Address, IPv6Address, NetmaskValueError
2727
from typing import Any
2828

@@ -34,7 +34,7 @@
3434

3535

3636
class ZeroconfIPv4Address(IPv4Address):
37-
__slots__ = ("__hash__", "_is_link_local", "_is_loopback", "_is_unspecified", "_str", "zc_integer")
37+
__slots__ = ("_hash", "_is_link_local", "_is_loopback", "_is_unspecified", "_str", "zc_integer")
3838

3939
def __init__(self, *args: Any, **kwargs: Any) -> None:
4040
"""Initialize a new IPv4 address."""
@@ -43,13 +43,17 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
4343
self._is_link_local = super().is_link_local
4444
self._is_unspecified = super().is_unspecified
4545
self._is_loopback = super().is_loopback
46-
self.__hash__ = cache(lambda: IPv4Address.__hash__(self)) # type: ignore[method-assign]
46+
self._hash = IPv4Address.__hash__(self)
4747
self.zc_integer = int(self)
4848

4949
def __str__(self) -> str:
5050
"""Return the string representation of the IPv4 address."""
5151
return self._str
5252

53+
def __hash__(self) -> int:
54+
"""Return the precomputed hash of the IPv4 address."""
55+
return self._hash
56+
5357
@property
5458
def is_link_local(self) -> bool:
5559
"""Return True if this is a link-local address."""
@@ -67,7 +71,7 @@ def is_loopback(self) -> bool:
6771

6872

6973
class ZeroconfIPv6Address(IPv6Address):
70-
__slots__ = ("__hash__", "_is_link_local", "_is_loopback", "_is_unspecified", "_str", "zc_integer")
74+
__slots__ = ("_hash", "_is_link_local", "_is_loopback", "_is_unspecified", "_str", "zc_integer")
7175

7276
def __init__(self, *args: Any, **kwargs: Any) -> None:
7377
"""Initialize a new IPv6 address."""
@@ -76,13 +80,17 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
7680
self._is_link_local = super().is_link_local
7781
self._is_unspecified = super().is_unspecified
7882
self._is_loopback = super().is_loopback
79-
self.__hash__ = cache(lambda: IPv6Address.__hash__(self)) # type: ignore[method-assign]
83+
self._hash = IPv6Address.__hash__(self)
8084
self.zc_integer = int(self)
8185

8286
def __str__(self) -> str:
8387
"""Return the string representation of the IPv6 address."""
8488
return self._str
8589

90+
def __hash__(self) -> int:
91+
"""Return the precomputed hash of the IPv6 address."""
92+
return self._hash
93+
8694
@property
8795
def is_link_local(self) -> bool:
8896
"""Return True if this is a link-local address."""

tests/utils/test_ipaddress.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ def test_cached_ip_addresses_wrapper():
4848
assert ipv6.is_unspecified is True
4949

5050

51+
def test_address_hash_matches_stdlib_and_dedups():
52+
"""Cached address objects hash like their stdlib equals and dedup in sets."""
53+
v4 = ipaddress.cached_ip_addresses("192.168.1.1")
54+
assert v4 is not None
55+
assert hash(v4) == hash(ipaddress.IPv4Address("192.168.1.1"))
56+
assert hash(v4) == hash(v4)
57+
assert len({v4, ipaddress.ZeroconfIPv4Address("192.168.1.1")}) == 1
58+
59+
v6 = ipaddress.cached_ip_addresses("fe80::1")
60+
assert v6 is not None
61+
assert hash(v6) == hash(ipaddress.IPv6Address("fe80::1"))
62+
assert len({v6, ipaddress.ZeroconfIPv6Address("fe80::1")}) == 1
63+
64+
5165
def test_get_ip_address_object_from_record():
5266
"""Test the get_ip_address_object_from_record."""
5367
# not link local

0 commit comments

Comments
 (0)