[tor-commits] [stem/master] Simplify hash caching

atagar at torproject.org atagar at torproject.org
Wed Aug 8 23:10:32 UTC 2018


commit fb98a2c044aea25d776002300f72871e408b4394
Author: Damian Johnson <atagar at torproject.org>
Date:   Wed Aug 8 15:21:34 2018 -0700

    Simplify hash caching
    
    Ahh. On reflection there's no need for each class to handle its own hash
    caching. There's a couple spots where they should due to being special
    snowflakes, but generally our hash_attr() can do this.
---
 stem/__init__.py          | 12 ++----------
 stem/client/cell.py       | 44 ++++++++++++++++++++++++++++++--------------
 stem/client/datatype.py   | 18 +++---------------
 stem/directory.py         | 12 ++----------
 stem/exit_policy.py       |  6 +-----
 stem/manual.py            | 12 ++----------
 stem/response/__init__.py |  3 ++-
 stem/response/events.py   |  6 +-----
 stem/util/__init__.py     | 11 ++++++++++-
 stem/version.py           |  6 +-----
 10 files changed, 54 insertions(+), 76 deletions(-)

diff --git a/stem/__init__.py b/stem/__init__.py
index 1679b0a0..f95d1b5e 100644
--- a/stem/__init__.py
+++ b/stem/__init__.py
@@ -572,13 +572,9 @@ class Endpoint(object):
 
     self.address = address
     self.port = int(port)
-    self._hash = None
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'address', 'port')
-
-    return self._hash
+    return stem.util._hash_attr(self, 'address', 'port', cache = True)
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, Endpoint) else False
@@ -597,13 +593,9 @@ class ORPort(Endpoint):
   def __init__(self, address, port, link_protocols = None):
     super(ORPort, self).__init__(address, port)
     self.link_protocols = link_protocols
-    self._hash = None
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'link_protocols', parent = Endpoint)
-
-    return self._hash
+    return stem.util._hash_attr(self, 'link_protocols', parent = Endpoint, cache = True)
 
 
 class DirPort(Endpoint):
diff --git a/stem/client/cell.py b/stem/client/cell.py
index ec214018..12fa994c 100644
--- a/stem/client/cell.py
+++ b/stem/client/cell.py
@@ -95,7 +95,6 @@ class Cell(object):
   def __init__(self, unused = b''):
     super(Cell, self).__init__()
     self.unused = unused
-    self._hash = None
 
   @staticmethod
   def by_name(name):
@@ -253,9 +252,6 @@ class Cell(object):
 
     raise NotImplementedError('Unpacking not yet implemented for %s cells' % cls.NAME)
 
-  def __hash__(self):
-    return self._hash if self._hash else super(type(self), self).__hash__()
-
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, Cell) else False
 
@@ -294,7 +290,6 @@ class PaddingCell(Cell):
 
     super(PaddingCell, self).__init__()
     self.payload = payload
-    self._hash = stem.util._hash_attr(self, 'payload')
 
   def pack(self, link_protocol):
     return PaddingCell._pack(link_protocol, self.payload)
@@ -303,6 +298,9 @@ class PaddingCell(Cell):
   def _unpack(cls, content, circ_id, link_protocol):
     return PaddingCell(content)
 
+  def __hash__(self):
+    return stem.util._hash_attr(self, 'payload', cache = True)
+
 
 class CreateCell(CircuitCell):
   NAME = 'CREATE'
@@ -360,7 +358,6 @@ class RelayCell(CircuitCell):
     self.stream_id = stream_id
     self.digest = digest
     self.data = str_tools._to_bytes(data)
-    self._hash = stem.util._hash_attr(self, 'command_int', 'stream_id', 'digest', 'data')
 
     if digest == 0:
       if not stream_id and self.command in STREAM_ID_REQUIRED:
@@ -393,6 +390,9 @@ class RelayCell(CircuitCell):
 
     return RelayCell(circ_id, command, data, digest, stream_id, recognized, unused)
 
+  def __hash__(self):
+    return stem.util._hash_attr(self, 'command_int', 'stream_id', 'digest', 'data', cache = True)
+
 
 class DestroyCell(CircuitCell):
   """
@@ -409,7 +409,6 @@ class DestroyCell(CircuitCell):
   def __init__(self, circ_id, reason = CloseReason.NONE, unused = b''):
     super(DestroyCell, self).__init__(circ_id, unused)
     self.reason, self.reason_int = CloseReason.get(reason)
-    self._hash = stem.util._hash_attr(self, 'circ_id', 'reason_int')
 
   def pack(self, link_protocol):
     return DestroyCell._pack(link_protocol, Size.CHAR.pack(self.reason_int), self.unused, self.circ_id)
@@ -419,6 +418,9 @@ class DestroyCell(CircuitCell):
     reason, unused = Size.CHAR.pop(content)
     return DestroyCell(circ_id, reason, unused)
 
+  def __hash__(self):
+    return stem.util._hash_attr(self, 'circ_id', 'reason_int', cache = True)
+
 
 class CreateFastCell(CircuitCell):
   """
@@ -440,7 +442,6 @@ class CreateFastCell(CircuitCell):
 
     super(CreateFastCell, self).__init__(circ_id, unused)
     self.key_material = key_material
-    self._hash = stem.util._hash_attr(self, 'circ_id', 'key_material')
 
   def pack(self, link_protocol):
     return CreateFastCell._pack(link_protocol, self.key_material, self.unused, self.circ_id)
@@ -454,6 +455,9 @@ class CreateFastCell(CircuitCell):
 
     return CreateFastCell(circ_id, key_material, unused)
 
+  def __hash__(self):
+    return stem.util._hash_attr(self, 'circ_id', 'key_material', cache = True)
+
 
 class CreatedFastCell(CircuitCell):
   """
@@ -479,7 +483,6 @@ class CreatedFastCell(CircuitCell):
     super(CreatedFastCell, self).__init__(circ_id, unused)
     self.key_material = key_material
     self.derivative_key = derivative_key
-    self._hash = stem.util._hash_attr(self, 'circ_id', 'derivative_key', 'key_material')
 
   def pack(self, link_protocol):
     return CreatedFastCell._pack(link_protocol, self.key_material + self.derivative_key, self.unused, self.circ_id)
@@ -494,6 +497,9 @@ class CreatedFastCell(CircuitCell):
 
     return CreatedFastCell(circ_id, derivative_key, key_material, content)
 
+  def __hash__(self):
+    return stem.util._hash_attr(self, 'circ_id', 'derivative_key', 'key_material', cache = True)
+
 
 class VersionsCell(Cell):
   """
@@ -509,7 +515,6 @@ class VersionsCell(Cell):
   def __init__(self, versions):
     super(VersionsCell, self).__init__()
     self.versions = versions
-    self._hash = stem.util._hash_attr(self, 'versions')
 
   def pack(self, link_protocol):
     payload = b''.join([Size.SHORT.pack(v) for v in self.versions])
@@ -525,6 +530,9 @@ class VersionsCell(Cell):
 
     return VersionsCell(link_protocols)
 
+  def __hash__(self):
+    return stem.util._hash_attr(self, 'versions', cache = True)
+
 
 class NetinfoCell(Cell):
   """
@@ -544,7 +552,6 @@ class NetinfoCell(Cell):
     self.timestamp = timestamp if timestamp else datetime.datetime.now()
     self.receiver_address = receiver_address
     self.sender_addresses = sender_addresses
-    self._hash = stem.util._hash_attr(self, 'timestamp', 'receiver_address', 'sender_addresses')
 
   def pack(self, link_protocol):
     payload = bytearray()
@@ -571,6 +578,9 @@ class NetinfoCell(Cell):
 
     return NetinfoCell(receiver_address, sender_addresses, datetime.datetime.utcfromtimestamp(timestamp), unused = content)
 
+  def __hash__(self):
+    return stem.util._hash_attr(self, 'timestamp', 'receiver_address', 'sender_addresses', cache = True)
+
 
 class RelayEarlyCell(CircuitCell):
   NAME = 'RELAY_EARLY'
@@ -629,7 +639,6 @@ class VPaddingCell(Cell):
 
     super(VPaddingCell, self).__init__()
     self.payload = payload if payload is not None else os.urandom(size)
-    self._hash = stem.util._hash_attr(self, 'payload')
 
   def pack(self, link_protocol):
     return VPaddingCell._pack(link_protocol, self.payload)
@@ -638,6 +647,9 @@ class VPaddingCell(Cell):
   def _unpack(cls, content, circ_id, link_protocol):
     return VPaddingCell(payload = content)
 
+  def __hash__(self):
+    return stem.util._hash_attr(self, 'payload', cache = True)
+
 
 class CertsCell(Cell):
   """
@@ -653,7 +665,6 @@ class CertsCell(Cell):
   def __init__(self, certs, unused = b''):
     super(CertsCell, self).__init__(unused)
     self.certificates = certs
-    self._hash = stem.util._hash_attr(self, 'certificates')
 
   def pack(self, link_protocol):
     return CertsCell._pack(link_protocol, Size.CHAR.pack(len(self.certificates)) + b''.join([cert.pack() for cert in self.certificates]), self.unused)
@@ -672,6 +683,9 @@ class CertsCell(Cell):
 
     return CertsCell(certs, unused = content)
 
+  def __hash__(self):
+    return stem.util._hash_attr(self, 'certificates', cache = True)
+
 
 class AuthChallengeCell(Cell):
   """
@@ -695,7 +709,6 @@ class AuthChallengeCell(Cell):
     super(AuthChallengeCell, self).__init__(unused)
     self.challenge = challenge
     self.methods = methods
-    self._hash = stem.util._hash_attr(self, 'challenge', 'methods')
 
   def pack(self, link_protocol):
     payload = bytearray()
@@ -727,6 +740,9 @@ class AuthChallengeCell(Cell):
 
     return AuthChallengeCell(methods, challenge, unused = content)
 
+  def __hash__(self):
+    return stem.util._hash_attr(self, 'challenge', 'methods', cache = True)
+
 
 class AuthenticateCell(Cell):
   NAME = 'AUTHENTICATE'
diff --git a/stem/client/datatype.py b/stem/client/datatype.py
index 32295317..5ca4e820 100644
--- a/stem/client/datatype.py
+++ b/stem/client/datatype.py
@@ -349,7 +349,6 @@ class Size(Field):
     self.name = name
     self.size = size
     self.format = pack_format
-    self._hash = None
 
   @staticmethod
   def pop(packed):
@@ -378,10 +377,7 @@ class Size(Field):
     return self.unpack(to_unpack), remainder
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'name', 'size', 'format')
-
-    return self._hash
+    return stem.util._hash_attr(self, 'name', 'size', 'format', cache = True)
 
 
 class Address(Field):
@@ -404,7 +400,6 @@ class Address(Field):
         raise ValueError("'%s' isn't an IPv4 or IPv6 address" % value)
 
     self.type, self.type_int = AddrType.get(addr_type)
-    self._hash = None
 
     if self.type == AddrType.IPv4:
       if stem.util.connection.is_valid_ipv4_address(value):
@@ -454,10 +449,7 @@ class Address(Field):
     return Address(addr_value, addr_type), content
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'type_int', 'value_bin')
-
-    return self._hash
+    return stem.util._hash_attr(self, 'type_int', 'value_bin', cache = True)
 
 
 class Certificate(Field):
@@ -472,7 +464,6 @@ class Certificate(Field):
   def __init__(self, cert_type, value):
     self.type, self.type_int = CertType.get(cert_type)
     self.value = value
-    self._hash = None
 
   def pack(self):
     cell = bytearray()
@@ -493,10 +484,7 @@ class Certificate(Field):
     return Certificate(cert_type, cert_bytes), content
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'type_int', 'value')
-
-    return self._hash
+    return stem.util._hash_attr(self, 'type_int', 'value')
 
 
 class KDF(collections.namedtuple('KDF', ['key_hash', 'forward_digest', 'backward_digest', 'forward_key', 'backward_key'])):
diff --git a/stem/directory.py b/stem/directory.py
index 5d243707..1782fbf1 100644
--- a/stem/directory.py
+++ b/stem/directory.py
@@ -255,7 +255,6 @@ class Authority(Directory):
 
     self.v3ident = v3ident
     self.is_bandwidth_authority = is_bandwidth_authority
-    self._hash = None
 
   @staticmethod
   def from_cache():
@@ -315,10 +314,7 @@ class Authority(Directory):
     return section_lines
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'v3ident', 'is_bandwidth_authority', parent = Directory)
-
-    return self._hash
+    return stem.util._hash_attr(self, 'v3ident', 'is_bandwidth_authority', parent = Directory, cache = True)
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, Authority) else False
@@ -370,7 +366,6 @@ class Fallback(Directory):
     super(Fallback, self).__init__(address, or_port, dir_port, fingerprint, nickname, orport_v6)
     self.has_extrainfo = has_extrainfo
     self.header = OrderedDict(header) if header else OrderedDict()
-    self._hash = None
 
   @staticmethod
   def from_cache(path = FALLBACK_CACHE_PATH):
@@ -520,10 +515,7 @@ class Fallback(Directory):
     conf.save(path)
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'has_extrainfo', 'header', parent = Directory)
-
-    return self._hash
+    return stem.util._hash_attr(self, 'has_extrainfo', 'header', parent = Directory, cache = True)
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, Fallback) else False
diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 0912546b..0ed5e581 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -1057,7 +1057,6 @@ class MicroExitPolicyRule(ExitPolicyRule):
     self.address = None  # wildcard address
     self.min_port = min_port
     self.max_port = max_port
-    self._hash = None
     self._skip_rule = False
 
   def is_address_wildcard(self):
@@ -1073,10 +1072,7 @@ class MicroExitPolicyRule(ExitPolicyRule):
     return None
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'is_accept', 'min_port', 'max_port')
-
-    return self._hash
+    return stem.util._hash_attr(self, 'is_accept', 'min_port', 'max_port', cache = True)
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, MicroExitPolicyRule) else False
diff --git a/stem/manual.py b/stem/manual.py
index 3e3d317f..7b956aa5 100644
--- a/stem/manual.py
+++ b/stem/manual.py
@@ -187,13 +187,9 @@ class ConfigOption(object):
     self.usage = usage
     self.summary = summary
     self.description = description
-    self._hash = None
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'name', 'category', 'usage', 'summary', 'description')
-
-    return self._hash
+    return stem.util._hash_attr(self, 'name', 'category', 'usage', 'summary', 'description', cache = True)
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, ConfigOption) else False
@@ -383,7 +379,6 @@ class Manual(object):
     self.man_commit = None
     self.stem_commit = None
     self.schema = None
-    self._hash = None
 
   @staticmethod
   def from_cache(path = None):
@@ -657,10 +652,7 @@ class Manual(object):
     conf.save(path)
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'name', 'synopsis', 'description', 'commandline_options', 'signals', 'files', 'config_options')
-
-    return self._hash
+    return stem.util._hash_attr(self, 'name', 'synopsis', 'description', 'commandline_options', 'signals', 'files', 'config_options', cache = True)
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, Manual) else False
diff --git a/stem/response/__init__.py b/stem/response/__init__.py
index 54eb9c44..96436b4d 100644
--- a/stem/response/__init__.py
+++ b/stem/response/__init__.py
@@ -172,6 +172,7 @@ class ControlMessage(object):
     self._parsed_content = parsed_content
     self._raw_content = raw_content
     self._str = None
+    self._hash = stem.util._hash_attr(self, '_raw_content')
 
   def is_ok(self):
     """
@@ -302,7 +303,7 @@ class ControlMessage(object):
     return ControlLine(content)
 
   def __hash__(self):
-    return stem.util._hash_attr(self, '_raw_content')
+    return self._hash
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, ControlMessage) else False
diff --git a/stem/response/events.py b/stem/response/events.py
index 37b60b38..64ed250d 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -57,7 +57,6 @@ class Event(stem.response.ControlMessage):
 
     self.type = str(self).split()[0]
     self.arrived_at = arrived_at
-    self._hash = None
 
     # if we're a recognized event type then translate ourselves into that subclass
 
@@ -73,10 +72,7 @@ class Event(stem.response.ControlMessage):
     self._parse()
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'arrived_at', parent = stem.response.ControlMessage)
-
-    return self._hash
+    return stem.util._hash_attr(self, 'arrived_at', parent = stem.response.ControlMessage, cache = True)
 
   def _parse_standard_attr(self):
     """
diff --git a/stem/util/__init__.py b/stem/util/__init__.py
index f0c2397b..91d96bcf 100644
--- a/stem/util/__init__.py
+++ b/stem/util/__init__.py
@@ -130,10 +130,16 @@ def _hash_attr(obj, *attributes, **kwargs):
 
   :param Object obj: object to be hashed
   :param list attributes: attribute names to take into account
+  :param bool cache: persists hash in a '_cached_hash' object attribute
   :param class parent: include parent's hash value
   """
 
-  parent_class = kwargs.get('parent')
+  is_cached = kwargs.get('cache', False)
+  parent_class = kwargs.get('parent', None)
+  cached_hash = getattr(obj, '_cached_hash', None)
+
+  if is_cached and cached_hash is not None:
+    return cached_hash
 
   my_hash = parent_class.__hash__(obj) if parent_class else 0
   my_hash = my_hash * 1024 + hash(str(type(obj)))
@@ -142,4 +148,7 @@ def _hash_attr(obj, *attributes, **kwargs):
     val = getattr(obj, attr)
     my_hash = my_hash * 1024 + _hash_value(val)
 
+  if is_cached:
+    setattr(obj, '_cached_hash', my_hash)
+
   return my_hash
diff --git a/stem/version.py b/stem/version.py
index b6a866f0..979bcf95 100644
--- a/stem/version.py
+++ b/stem/version.py
@@ -182,7 +182,6 @@ class Version(object):
   def __init__(self, version_str):
     self.version_str = version_str
     version_parts = VERSION_PATTERN.match(version_str)
-    self._hash = None
 
     if version_parts:
       major, minor, micro, patch, status, extra_str, _ = version_parts.groups()
@@ -251,10 +250,7 @@ class Version(object):
     return method(my_status, other_status)
 
   def __hash__(self):
-    if self._hash is None:
-      self._hash = stem.util._hash_attr(self, 'major', 'minor', 'micro', 'patch', 'status')
-
-    return self._hash
+    return stem.util._hash_attr(self, 'major', 'minor', 'micro', 'patch', 'status', cache = True)
 
   def __eq__(self, other):
     return self._compare(other, lambda s, o: s == o)





More information about the tor-commits mailing list