[tor-commits] [stem/master] Adding hash functions to exit policy classes

atagar at torproject.org atagar at torproject.org
Mon Oct 14 00:42:16 UTC 2013


commit 4ef5ee1ee8fbecabeaee872f2681cdda1fb86584
Author: Damian Johnson <atagar at torproject.org>
Date:   Sat Oct 12 20:18:12 2013 -0700

    Adding hash functions to exit policy classes
    
    Huh, didn't expect that. To work the @lru_cache requires that all arguments
    (including the class in the ase of methods) are hashable. This makes perfectly
    sense, after all the cache is a 'argument => cached value' dictionary.
    
    Under python 2.x all seemed to be well, but under python 3.x the @lru_cache
    complained that our classes weren't hashable (which... well, they aren't). Why,
    then, did it work under python 2.x? Turns out there's a subtle difference where
    our object parent provides a __hash__ method for our id, but in python 3.x it's
    a little different (object still does, but for reasons I haven't been able to
    figure out it doesn't for our classes).
    
    The ExitPolicyRule can certainly be hashable. The ExitPolicy, however, is a lot
    trickier due to its lazy loading of rules. For now just opting for the same
    behavior as python 2.x and using the address.
---
 stem/exit_policy.py |   42 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 41 insertions(+), 1 deletion(-)

diff --git a/stem/exit_policy.py b/stem/exit_policy.py
index 9366a54..a358908 100644
--- a/stem/exit_policy.py
+++ b/stem/exit_policy.py
@@ -349,6 +349,14 @@ class ExitPolicy(object):
   def __str__(self):
     return ', '.join([str(rule) for rule in self._get_rules()])
 
+  def __hash__(self):
+    # TODO: It would be nice to provide a real hash function, but doing so is
+    # tricky due to how we lazily load the rules. Like equality checks a proper
+    # hash function would need to call _get_rules(), but that's behind
+    # @lru_cache which calls hash() forming a circular dependency.
+
+    return id(self)
+
   def __eq__(self, other):
     if isinstance(other, ExitPolicy):
       return self._get_rules() == list(other)
@@ -429,6 +437,9 @@ class MicroExitPolicy(ExitPolicy):
   def __str__(self):
     return self._policy
 
+  def __hash__(self):
+    return id(self)
+
   def __eq__(self, other):
     if isinstance(other, MicroExitPolicy):
       return str(self) == str(other)
@@ -682,6 +693,22 @@ class ExitPolicyRule(object):
 
     return label
 
+  def __hash__(self):
+    my_hash = 0
+
+    for attr in ("is_accept", "address", "min_port", "max_port"):
+      my_hash *= 1024
+
+      attr_value = getattr(self, attr)
+
+      if attr_value is not None:
+        my_hash += hash(attr_value)
+
+    my_hash *= 1024
+    my_hash += hash(self.get_mask(False))
+
+    return my_hash
+
   @lru_cache()
   def _get_mask_bin(self):
     # provides an integer representation of our mask
@@ -792,7 +819,7 @@ class ExitPolicyRule(object):
       # 0.0.0.0/0" == "accept 0.0.0.0/0.0.0.0" will be True), but these
       # policies are effectively equivalent.
 
-      return str(self) == str(other)
+      return hash(self) == hash(other)
     else:
       return False
 
@@ -827,3 +854,16 @@ class MicroExitPolicyRule(ExitPolicyRule):
 
   def get_masked_bits(self):
     return None
+
+  def __hash__(self):
+    my_hash = 0
+
+    for attr in ("is_accept", "min_port", "max_port"):
+      my_hash *= 1024
+
+      attr_value = getattr(self, attr)
+
+      if attr_value is not None:
+        my_hash += hash(attr_value)
+
+    return my_hash





More information about the tor-commits mailing list