Skip to content

Commit

Permalink
fix(hashing): clarify hashing usage with mutable objects
Browse files Browse the repository at this point in the history
  • Loading branch information
matfax committed Oct 20, 2019
1 parent 9f3d24c commit cacdcbb
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 4 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,27 @@ True
False
```


For more in-depth examples, check the tests folder.

## Hashing

mutapath paths are hashable by caching the generated hash the first time it is accessed.
However, it also adds a warning so that unintended hash usage is avoided.
Once mutated after that, the generated hashes don't provide collision detection in binary trees anymore.
Don't use them in sets or as keys in dicts.
Use the explicit string representation instead, to make the hashing input transparent.

```python
>>> p = Path("/home")
>>> hash(p)
1083235232
>>> hash(Path("/home")) == hash(p)
True
>>> with p.mutate() as m:
... m.name = "home4"
>>> hash(p) # same hash
1083235232
>>> hash(Path("/home")) == hash(p) # they are not equal anymore
True
```
2 changes: 1 addition & 1 deletion mutapath/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"__delattr__", "__setattr__", "__getattr__", "joinpath", "clone", "__exit__", "__fspath__",
"'_Path__wrap_attribute'", "__wrap_decorator", "_op_context", "__hash__", "__enter__", "_norm", "open", "lock",
"getcwd", "dirname", "owner", "uncshare", "posix_format", "posix_string", "__add__", "__radd__", "_set_contained",
"with_poxis_enabled"
"with_poxis_enabled", "_hash_cache"
]

__MUTABLE_FUNCTIONS = {"rename", "renames", "copy", "copy2", "copyfile", "copymode", "copystat", "copytree", "move",
Expand Down
8 changes: 8 additions & 0 deletions mutapath/immutapath.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import pathlib
import shutil
import warnings
from contextlib import contextmanager
from typing import Union, Iterable, ClassVar, Callable, Optional

Expand Down Expand Up @@ -100,6 +101,13 @@ def __eq__(self, other):
return str(self) == str(other)

def __hash__(self):
warnings.warn(f"It is not advised to hash mutable path objects or to use them in sets or dicts. "
f"Please use hash(str(path)) instead to make the actual hashing input transparent.",
category=SyntaxWarning)
return self._hash_cache

@cached_property
def _hash_cache(self) -> int:
return hash(self._contained)

def __lt__(self, other):
Expand Down
8 changes: 5 additions & 3 deletions tests/test_immutapath.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,11 @@ def test_home_root(self):
self.typed_instance_test(actual)

def test_hash(self):
expected = Path("/A") / "B"
actual = Path("/A/B/")
self.assertEqual(hash(expected), hash(actual))
with self.assertWarns(SyntaxWarning):
expected = hash(Path("/A") / "B")
with self.assertWarns(SyntaxWarning):
actual = hash(Path("/A/B/"))
self.assertEqual(expected, actual)

def test_lt_last(self):
lesser = Path("/A/B/")
Expand Down

0 comments on commit cacdcbb

Please sign in to comment.