Skip to content

Fix selector hash function and the remainder of the types#159

Open
ocefpaf wants to merge 2 commits into
ioos:mainfrom
ocefpaf:fix_selector_hash
Open

Fix selector hash function and the remainder of the types#159
ocefpaf wants to merge 2 commits into
ioos:mainfrom
ocefpaf:fix_selector_hash

Conversation

@ocefpaf

@ocefpaf ocefpaf commented Jul 7, 2026

Copy link
Copy Markdown
Member

Adding proper types to ugrid.py revealed some inconsistencies that may require more testing in the future. Some variables are unused.

Comment thread xarray_subset_grid/selector.py Outdated
Comment on lines +29 to +33
__hash__ = None
def __hash__(self):
"""Polygon seems to be the unique property that we should use for hash calculation
and comparisons.
"""
return hash(self.polygon)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ChrisBarker-NOAA I'm not sure we do need a hash property/function here. Still, I "fixed" what seemed to be a placeholder by using the most unique property for hashing I could imagine for this class.

@ChrisBarker-NOAA ChrisBarker-NOAA Jul 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, setting __hash__ is not a placeholder, but rather makes the type non-hashable -- I'm not sure why that was done, but it was presumably deliberate, and I think it's better for it to be non-hashable than have a hash that may not work correctly.

I think we should put the None back until / if we figure out why you might want a hash for this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we can do:

    def __hash__(self):
        return hash(tuple(self))

to make it unhasable.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the advantage of that over hash = None?

I think __hash__ = None is the conical way to do it.

"""
When the __hash__() method of a class is None, instances of the class will raise an appropriate TypeError when a program attempts to retrieve their hash value, and will also be correctly identified as unhashable
"""

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, if I understood it correctly, to make something unhashble all we need to do is to not define __hash__. This happens in the latest commit, is that what you expect here?

from xarray_subset_grid.selector import Selector ; a = Selector() ; hash(a)

TypeError: unhashable type: 'Selector'

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know the pattern __hash__ = None, and mypy treats that like an error b/c we cannot define a hash that is unhashable.

After some research I think that not defining is the way to make it unhashable.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If MyPy is confused, it's MyPy's problem -- I don't think we should ever change correct code to satisfy typing.

(but I'm a typing skeptic)

If __hash__ is not defined, then it uses the id as the default hash.

Comment thread xarray_subset_grid/grids/ugrid.py
@ocefpaf ocefpaf requested a review from ChrisBarker-NOAA July 7, 2026 12:59
@ocefpaf

ocefpaf commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

@ChrisBarker-NOAA this one is ready for review.

This was referenced Jul 7, 2026
@ChrisBarker-NOAA

Copy link
Copy Markdown
Contributor

other than the hash question this LGTM.

thanks!

@ocefpaf

ocefpaf commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

other than the hash question this LGTM.

Are you OK with just removing the __hash__ to make it unhashable? It would also satisfy mypy that way.

@ChrisBarker-NOAA

Copy link
Copy Markdown
Contributor

Looking at this a bit more:

"If your class overrides the __eq__ method, Python automatically sets __hash__ = None under the hood at runtime."

In this case, __eq__ is set, so that should be fine.

But I still prefer the explicit -- "this class is not hashable" approach, and we can satisfy MyPy with:

    __hash__ = None  # type: ignore[assignment]

or maybe:

_hash__: Callable | None

But how to specify Callable[self] ?

And this why I'm a typing skeptic ...

And I really don't know MyPy, so your choice.

@ocefpaf

ocefpaf commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

And this why I'm a typing skeptic ...

I agree. It does help, but sometimes we are just trying to satisfy a "linter".

It does shine for large codebases where these the value/idiosyncrasies ratio is higher.

I'll make a explicit None and add the mypy skip tomorrow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants