commit 4ef5ee1ee8fbecabeaee872f2681cdda1fb86584 Author: Damian Johnson atagar@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
tor-commits@lists.torproject.org