Repository: rasterio/rasterio

Issue:
Make Window Hashable
<!--

WELCOME ABOARD!

Hi and welcome to the Rasterio project. We appreciate bug reports, questions
about documentation, and suggestions for new features. This issue template
isn't intended to ward you off; only to intercept and redirect some particular
categories of reports, and to collect a few important facts that issue reporters
often omit.

The primary forum for questions about installation and usage of Rasterio is 
https://rasterio.groups.io/g/main. The authors and other users will answer 
questions when they have expertise to share and time to explain. Please take the
time to craft a clear question and be patient about responses.

Please do not bring these questions to Rasterio's issue tracker, which we want
to reserve for bug reports and other actionable issues.

Please note: Rasterio contains extension modules and is thus susceptible to
C library compatibility issues. If you are reporting an installation or module
import issue, please note that this project only accepts reports about problems
with packages downloaded from the Python Package Index. Conda users should take
issues to one of the following trackers:

- https://github.com/ContinuumIO/anaconda-issues/issues
- https://github.com/conda-forge/rasterio-feedstock

Also please note: we are currently working on 1.0 and pre-releases. Bugs found
in version 0.36 will not be fixed except in a 1.0 alpha or beta release. In 
some cases, 0.36 bugs have already been fixed in recent pre-releases.

You think you've found a bug? We believe you!
-->

## Expected behavior and actual behavior.

Given that windows are simple classes I expected them to be hashable when composing a set of them from block_windows(). But they are not (TypeError: unhashable type Window), so something should probably be done about that.

For my purposes I monkey patched `__hash__` and `__eq__` on Window using the tuple representations via flatten() to fix this locally, but I would bet that there are tricks in the python data model / using `__slots__` cleverly to do something more correct.

## Steps to reproduce the problem.

semi pseudo code
open an image then run:
`set(img.block_windows())`
will throw the TypeError 

## Operating system

MacOS 10.12.6

## Rasterio version and provenance
1.0.2 wheel installed from PyPI using pip 18.0



Discussion:
@AndrewAnnex the Window class uses the attr module. How to make attr classes hashable is explained at https://www.attrs.org/en/stable/hashing.html#hashing. Using `@attr.s(frozen=True)` looks like it will meet your need to generate sets of unique windows, yes?
ah I didn't look into attrs, that sounds reasonable way forward, I can make the PR for this change in an hour or so. 

Resolving patch:
diff --git a/rasterio/windows.py b/rasterio/windows.py
--- a/rasterio/windows.py
+++ b/rasterio/windows.py
@@ -475,7 +475,8 @@ def validate_length_value(instance, attribute, value):
         raise ValueError("Number of columns or rows must be non-negative")
 
 
-@attr.s(slots=True)
+@attr.s(slots=True,
+        frozen=True)
 class Window(object):
     """Windows are rectangular subsets of rasters.
 


Test patch:
diff --git a/tests/test_windows.py b/tests/test_windows.py
--- a/tests/test_windows.py
+++ b/tests/test_windows.py
@@ -571,3 +571,11 @@ def test_round_lengths_no_op_error():
 def test_round_offsets_no_op_error():
     with pytest.raises(WindowError):
         Window(0, 0, 1, 1).round_offsets(op='lolwut')
+
+
+def test_window_hashable():
+    a = Window(0, 0, 10, 10)
+    b = Window(0, 0, 10, 10)
+    c = Window(8, 8, 12, 12)
+    assert hash(a) == hash(b)
+    assert hash(a) != hash(c)