[tor-commits] [stem/master] Cache immutable class hash values

atagar at torproject.org atagar at torproject.org
Wed Jul 18 21:37:49 UTC 2018


commit 99c4ad9db437dc528f1e7134001842d30a073a8a
Author: Damian Johnson <atagar at torproject.org>
Date:   Wed Jul 18 04:00:34 2018 -0700

    Cache immutable class hash values
    
    Immutable classes have immutable cache values, and as frequntly used
    equality/dictionary values it's useful to cache these.
---
 stem/__init__.py        | 12 ++++++++++--
 stem/client/cell.py     | 46 +++++++++++++++-------------------------------
 stem/client/datatype.py | 14 ++++++++++----
 stem/directory.py       | 17 +++++++++++++----
 stem/manual.py          | 15 +++++++++++----
 stem/util/__init__.py   |  7 +++----
 stem/version.py         |  5 ++---
 7 files changed, 64 insertions(+), 52 deletions(-)

diff --git a/stem/__init__.py b/stem/__init__.py
index 7d84423a..6da73320 100644
--- a/stem/__init__.py
+++ b/stem/__init__.py
@@ -572,9 +572,13 @@ class Endpoint(object):
 
     self.address = address
     self.port = int(port)
+    self._hash = None
 
   def __hash__(self):
-    return stem.util._hash_attr(self, 'address', 'port')
+    if self._hash is None:
+      self._hash = stem.util._hash_attr(self, 'address', 'port')
+
+    return self._hash
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, Endpoint) else False
@@ -593,9 +597,13 @@ 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):
-    return stem.util._hash_attr(self, 'link_protocols', parent = Endpoint)
+    if self._hash is None:
+      self._hash = stem.util._hash_attr(self, 'link_protocols', parent = True)
+
+    return self._hash
 
 
 class DirPort(Endpoint):
diff --git a/stem/client/cell.py b/stem/client/cell.py
index 948201fd..51e2fbd3 100644
--- a/stem/client/cell.py
+++ b/stem/client/cell.py
@@ -46,7 +46,7 @@ import stem.util
 
 from stem import UNDEFINED
 from stem.client.datatype import HASH_LEN, ZERO, LinkProtocol, Address, Certificate, CloseReason, RelayCommand, Size, split
-from stem.util import _hash_attr, datetime_to_unix, str_tools
+from stem.util import datetime_to_unix, str_tools
 
 FIXED_PAYLOAD_LEN = 509  # PAYLOAD_LEN, per tor-spec section 0.2
 AUTH_CHALLENGE_SIZE = 32
@@ -95,6 +95,7 @@ class Cell(object):
   def __init__(self, unused = b''):
     super(Cell, self).__init__()
     self.unused = unused
+    self._hash = None
 
   @staticmethod
   def by_name(name):
@@ -252,6 +253,9 @@ 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
 
@@ -290,6 +294,7 @@ 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)
@@ -298,9 +303,6 @@ class PaddingCell(Cell):
   def _unpack(cls, content, circ_id, link_protocol):
     return PaddingCell(content)
 
-  def __hash__(self):
-    return _hash_attr(self, 'payload')
-
 
 class CreateCell(CircuitCell):
   NAME = 'CREATE'
@@ -358,6 +360,7 @@ 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:
@@ -390,9 +393,6 @@ class RelayCell(CircuitCell):
 
     return RelayCell(circ_id, command, data, digest, stream_id, recognized, unused)
 
-  def __hash__(self):
-    return _hash_attr(self, 'command_int', 'stream_id', 'digest', 'data')
-
 
 class DestroyCell(CircuitCell):
   """
@@ -409,6 +409,7 @@ 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)
@@ -418,9 +419,6 @@ class DestroyCell(CircuitCell):
     reason, unused = Size.CHAR.pop(content)
     return DestroyCell(circ_id, reason, unused)
 
-  def __hash__(self):
-    return _hash_attr(self, 'circ_id', 'reason_int')
-
 
 class CreateFastCell(CircuitCell):
   """
@@ -442,6 +440,7 @@ 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)
@@ -455,9 +454,6 @@ class CreateFastCell(CircuitCell):
 
     return CreateFastCell(circ_id, key_material, unused)
 
-  def __hash__(self):
-    return _hash_attr(self, 'circ_id', 'key_material')
-
 
 class CreatedFastCell(CircuitCell):
   """
@@ -483,6 +479,7 @@ 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)
@@ -497,9 +494,6 @@ class CreatedFastCell(CircuitCell):
 
     return CreatedFastCell(circ_id, derivative_key, key_material, content)
 
-  def __hash__(self):
-    return _hash_attr(self, 'circ_id', 'derivative_key', 'key_material')
-
 
 class VersionsCell(Cell):
   """
@@ -515,6 +509,7 @@ 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])
@@ -530,9 +525,6 @@ class VersionsCell(Cell):
 
     return VersionsCell(link_protocols)
 
-  def __hash__(self):
-    return _hash_attr(self, 'versions')
-
 
 class NetinfoCell(Cell):
   """
@@ -552,6 +544,7 @@ 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()
@@ -578,9 +571,6 @@ class NetinfoCell(Cell):
 
     return NetinfoCell(receiver_address, sender_addresses, datetime.datetime.utcfromtimestamp(timestamp), unused = content)
 
-  def __hash__(self):
-    return _hash_attr(self, 'timestamp', 'receiver_address', 'sender_addresses')
-
 
 class RelayEarlyCell(CircuitCell):
   NAME = 'RELAY_EARLY'
@@ -639,6 +629,7 @@ 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)
@@ -647,9 +638,6 @@ class VPaddingCell(Cell):
   def _unpack(cls, content, circ_id, link_protocol):
     return VPaddingCell(payload = content)
 
-  def __hash__(self):
-    return _hash_attr(self, 'payload')
-
 
 class CertsCell(Cell):
   """
@@ -665,6 +653,7 @@ 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)
@@ -683,9 +672,6 @@ class CertsCell(Cell):
 
     return CertsCell(certs, unused = content)
 
-  def __hash__(self):
-    return _hash_attr(self, 'certificates')
-
 
 class AuthChallengeCell(Cell):
   """
@@ -709,6 +695,7 @@ 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()
@@ -740,9 +727,6 @@ class AuthChallengeCell(Cell):
 
     return AuthChallengeCell(methods, challenge, unused = content)
 
-  def __hash__(self):
-    return _hash_attr(self, 'challenge', 'methods')
-
 
 class AuthenticateCell(Cell):
   NAME = 'AUTHENTICATE'
diff --git a/stem/client/datatype.py b/stem/client/datatype.py
index 2ee624cb..22827245 100644
--- a/stem/client/datatype.py
+++ b/stem/client/datatype.py
@@ -121,8 +121,6 @@ import stem.util
 import stem.util.connection
 import stem.util.enum
 
-from stem.util import _hash_attr
-
 ZERO = b'\x00'
 HASH_LEN = 20
 KEY_LEN = 16
@@ -382,6 +380,7 @@ 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):
@@ -431,7 +430,10 @@ class Address(Field):
     return Address(addr_value, addr_type), content
 
   def __hash__(self):
-    return _hash_attr(self, 'type_int', 'value_bin')
+    if self._hash is None:
+      self._hash = stem.util._hash_attr(self, 'type_int', 'value_bin')
+
+    return self._hash
 
 
 class Certificate(Field):
@@ -446,6 +448,7 @@ 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()
@@ -466,7 +469,10 @@ class Certificate(Field):
     return Certificate(cert_type, cert_bytes), content
 
   def __hash__(self):
-    return _hash_attr(self, 'type_int', 'value')
+    if self._hash is None:
+      self._hash = stem.util._hash_attr(self, 'type_int', 'value')
+
+    return self._hash
 
 
 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 efd37641..020ac696 100644
--- a/stem/directory.py
+++ b/stem/directory.py
@@ -41,9 +41,10 @@ as follows...
 import os
 import re
 
+import stem.util
 import stem.util.conf
 
-from stem.util import _hash_attr, connection, str_tools, tor_tools
+from stem.util import connection, str_tools, tor_tools
 
 try:
   # added in python 2.7
@@ -217,7 +218,7 @@ class Directory(object):
     raise NotImplementedError('Unsupported Operation: this should be implemented by the Directory subclass')
 
   def __hash__(self):
-    return _hash_attr(self, 'address', 'or_port', 'dir_port', 'fingerprint', 'nickname', 'orport_v6')
+    return stem.util._hash_attr(self, 'address', 'or_port', 'dir_port', 'fingerprint', 'nickname', 'orport_v6')
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, Directory) else False
@@ -254,6 +255,7 @@ class Authority(Directory):
 
     self.v3ident = v3ident
     self.is_bandwidth_authority = is_bandwidth_authority
+    self._hash = None
 
   @staticmethod
   def from_cache():
@@ -313,7 +315,10 @@ class Authority(Directory):
     return section_lines
 
   def __hash__(self):
-    return _hash_attr(self, 'v3ident', 'is_bandwidth_authority', parent = Directory)
+    if self._hash is None:
+      self._hash = stem.util._hash_attr(self, 'v3ident', 'is_bandwidth_authority', parent = True)
+
+    return self._hash
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, Authority) else False
@@ -365,6 +370,7 @@ 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):
@@ -514,7 +520,10 @@ class Fallback(Directory):
     conf.save(path)
 
   def __hash__(self):
-    return _hash_attr(self, 'has_extrainfo', 'header', parent = Directory)
+    if self._hash is None:
+      self._hash = stem.util._hash_attr(self, 'has_extrainfo', 'header', parent = True)
+
+    return self._hash
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, Fallback) else False
diff --git a/stem/manual.py b/stem/manual.py
index 573542da..458014b0 100644
--- a/stem/manual.py
+++ b/stem/manual.py
@@ -54,13 +54,12 @@ import sys
 import tempfile
 
 import stem.prereq
+import stem.util
 import stem.util.conf
 import stem.util.enum
 import stem.util.log
 import stem.util.system
 
-from stem.util import _hash_attr
-
 try:
   # added in python 2.7
   from collections import OrderedDict
@@ -189,9 +188,13 @@ class ConfigOption(object):
     self.usage = usage
     self.summary = summary
     self.description = description
+    self._hash = None
 
   def __hash__(self):
-    return _hash_attr(self, 'name', 'category', 'usage', 'summary', 'description')
+    if self._hash is None:
+      self._hash = stem.util._hash_attr(self, 'name', 'category', 'usage', 'summary', 'description')
+
+    return self._hash
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, ConfigOption) else False
@@ -381,6 +384,7 @@ class Manual(object):
     self.man_commit = None
     self.stem_commit = None
     self.schema = None
+    self._hash = None
 
   @staticmethod
   def from_cache(path = None):
@@ -654,7 +658,10 @@ class Manual(object):
     conf.save(path)
 
   def __hash__(self):
-    return _hash_attr(self, 'name', 'synopsis', 'description', 'commandline_options', 'signals', 'files', 'config_options')
+    if self._hash is None:
+      self._hash = stem.util._hash_attr(self, 'name', 'synopsis', 'description', 'commandline_options', 'signals', 'files', 'config_options')
+
+    return self._hash
 
   def __eq__(self, other):
     return hash(self) == hash(other) if isinstance(other, Manual) else False
diff --git a/stem/util/__init__.py b/stem/util/__init__.py
index b812fc13..cc40bd0a 100644
--- a/stem/util/__init__.py
+++ b/stem/util/__init__.py
@@ -130,12 +130,11 @@ def _hash_attr(obj, *attributes, **kwargs):
 
   :param Object obj: object to be hashed
   :param list attributes: attribute names to take into account
-  :param class parent: parent object to include in the hash value
+  :param bool parent: include parent's hash value
   """
 
-  # TODO: deal with this parent thing
-
-  my_hash = hash(str(type(obj))) if kwargs.get('parent') is None else kwargs.get('parent').__hash__(obj)
+  my_hash = super(type(obj), obj).__hash__() if kwargs.get('parent') else 0
+  my_hash = my_hash * 1024 + hash(str(type(obj)))
 
   for attr in attributes:
     val = getattr(obj, attr)
diff --git a/stem/version.py b/stem/version.py
index 9036effb..ed1a3a38 100644
--- a/stem/version.py
+++ b/stem/version.py
@@ -83,11 +83,10 @@ easily parsed and compared, for instance...
 import os
 import re
 
+import stem.util
 import stem.util.enum
 import stem.util.system
 
-from stem.util import _hash_attr
-
 try:
   # added in python 3.2
   from functools import lru_cache
@@ -252,7 +251,7 @@ class Version(object):
 
   def __hash__(self):
     if self._hash is None:
-      self._hash = _hash_attr(self, 'major', 'minor', 'micro', 'patch', 'status')
+      self._hash = stem.util._hash_attr(self, 'major', 'minor', 'micro', 'patch', 'status')
 
     return self._hash
 



More information about the tor-commits mailing list