Skip to content

Commit 30246cc

Browse files
authored
TYP: Series/DataFrame/Index are not Hashable (#113)
* TYP: Series/DataFrame are not Hashable * explicitly set __hash__ also in the sub-classes (they inherit from classes that have proper __hash__) * cover a few more __hash__; and try to check types at runtime * just in case it would affect static type checking * cache only poetry.lock * remove runtime check * assert assert_type * undo change for the runtype hack * poetry.lock should not be dependent on the OS/python version * Empty commit * move ClassVar to the top * negative type tests * move to top
1 parent 9c1757b commit 30246cc

File tree

8 files changed

+37
-23
lines changed

8 files changed

+37
-23
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,12 @@ jobs:
2626
- name: Install Poetry
2727
run: pip install poetry
2828

29-
- name: Determine poetry cache-dir
30-
run: |
31-
echo "::set-output name=PATH::$(poetry config cache-dir)"
32-
id: cache_path
33-
34-
- name: Cache project dependencies
29+
- name: Cache poetry.lock
3530
id : cache
3631
uses: actions/cache@v3
37-
with:
38-
path: |
39-
${{ steps.cache_path.outputs.PATH }}
40-
poetry.lock
41-
key: ${{ matrix.os }}-${{ matrix.python-version }}-poetry-${{ hashFiles('pyproject.toml') }}
42-
restore-keys: ${{ matrix.os }}-${{ matrix.python-version }}-poetry-
43-
44-
- name: Delete poetry.lock on cache miss
45-
if: steps.cache.outputs.cache-hit != 'true'
46-
uses: JesseTG/[email protected]
4732
with:
4833
path: poetry.lock
34+
key: poetry-${{ hashFiles('pyproject.toml') }}
4935

5036
- name: Install project dependencies
5137
run: poetry install -vvv --no-root

pandas-stubs/_typing.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,5 +200,5 @@ XMLParsers = Literal["lxml", "etree"]
200200
# Any plain Python or numpy function
201201
Function = Union[np.ufunc, Callable[..., Any]]
202202
GroupByObject = Union[
203-
Label, List[Label], Function, Series, np.ndarray, Mapping[Label, Any]
203+
Label, List[Label], Function, Series, np.ndarray, Mapping[Label, Any], Index
204204
]

pandas-stubs/core/frame.pyi

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import datetime as _dt
55
from typing import (
66
Any,
77
Callable,
8+
ClassVar,
89
Dict,
910
Hashable,
1011
Iterable,
@@ -183,6 +184,8 @@ class DataFrame(NDFrame, OpsMixin):
183184
Index,
184185
Series,
185186
]
187+
__hash__: ClassVar[None] # type: ignore[assignment]
188+
186189
def __new__(
187190
cls,
188191
data: Optional[Union[_ListLike, DataFrame, Dict[Any, Any]]] = ...,
@@ -428,7 +431,7 @@ class DataFrame(NDFrame, OpsMixin):
428431
*,
429432
axis: Axis = ...,
430433
index: Hashable | Sequence[Hashable] = ...,
431-
columns: Hashable | Sequence[Hashable] = ...,
434+
columns: Hashable | Sequence[Hashable] | Index = ...,
432435
level: Optional[Level] = ...,
433436
inplace: Literal[True],
434437
errors: IgnoreRaise = ...,
@@ -440,7 +443,7 @@ class DataFrame(NDFrame, OpsMixin):
440443
*,
441444
axis: Axis = ...,
442445
index: Hashable | Sequence[Hashable] = ...,
443-
columns: Hashable | Sequence[Hashable] = ...,
446+
columns: Hashable | Sequence[Hashable] | Index = ...,
444447
level: Optional[Level] = ...,
445448
inplace: Literal[False] = ...,
446449
errors: IgnoreRaise = ...,
@@ -452,7 +455,7 @@ class DataFrame(NDFrame, OpsMixin):
452455
*,
453456
axis: Axis = ...,
454457
index: Hashable | Sequence[Hashable] = ...,
455-
columns: Hashable | Sequence[Hashable] = ...,
458+
columns: Hashable | Sequence[Hashable] | Index = ...,
456459
level: Optional[Level] = ...,
457460
inplace: bool = ...,
458461
errors: IgnoreRaise = ...,

pandas-stubs/core/generic.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import sys
22
from typing import (
33
Any,
44
Callable,
5+
ClassVar,
56
Dict,
67
Hashable,
78
Iterator,
@@ -48,6 +49,8 @@ _bool = bool
4849
_str = str
4950

5051
class NDFrame(PandasObject, indexing.IndexingMixin):
52+
__hash__: ClassVar[None] # type: ignore[assignment]
53+
5154
def __new__(
5255
cls,
5356
data: BlockManager,
@@ -89,7 +92,6 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
8992
def bool(self) -> _bool: ...
9093
def __abs__(self) -> NDFrame: ...
9194
def __round__(self, decimals: int = ...) -> NDFrame: ...
92-
def __hash__(self): ...
9395
def __iter__(self) -> Iterator: ...
9496
def keys(self): ...
9597
def iteritems(self): ...

pandas-stubs/core/indexes/base.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import (
22
Callable,
3+
ClassVar,
34
Dict,
45
Hashable,
56
Iterable,
@@ -41,6 +42,8 @@ class InvalidIndexError(Exception): ...
4142
_str = str
4243

4344
class Index(IndexOpsMixin, PandasObject):
45+
__hash__: ClassVar[None] # type: ignore[assignment]
46+
4447
def __new__(
4548
cls,
4649
data: Iterable = ...,
@@ -169,7 +172,6 @@ class Index(IndexOpsMixin, PandasObject):
169172
def where(self, cond, other=...): ...
170173
def is_type_compatible(self, kind) -> bool: ...
171174
def __contains__(self, key) -> bool: ...
172-
def __hash__(self) -> int: ...
173175
def __setitem__(self, key, value) -> None: ...
174176
@overload
175177
def __getitem__(

pandas-stubs/core/indexes/frozen.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ class FrozenList(PandasObject, list):
88
def __eq__(self, other) -> bool: ...
99
def __mul__(self, other): ...
1010
def __reduce__(self): ...
11-
def __hash__(self): ...
11+
def __hash__(self) -> int: ... # type: ignore[override]

pandas-stubs/core/series.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ from datetime import (
55
from typing import (
66
Any,
77
Callable,
8+
ClassVar,
89
Dict,
910
Generic,
1011
Hashable,
@@ -138,6 +139,8 @@ class _LocIndexerSeries(_LocIndexer, Generic[S1]):
138139
class Series(IndexOpsMixin, NDFrame, Generic[S1]):
139140

140141
_ListLike = Union[ArrayLike, Dict[_str, np.ndarray], List, Tuple, Index]
142+
__hash__: ClassVar[None]
143+
141144
@overload
142145
def __new__(
143146
cls,

tests/test_frame.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
TYPE_CHECKING,
88
Any,
99
Dict,
10+
Hashable,
1011
Iterable,
1112
List,
1213
Tuple,
@@ -1141,3 +1142,20 @@ def test_frame_ndarray_assignmment() -> None:
11411142

11421143
df_b = pd.DataFrame({"a": [0.0] * 10, "b": [1.0] * 10})
11431144
df_b.iloc[:, :] = np.array([[-1.0, np.inf]] * 10)
1145+
1146+
1147+
def test_not_hashable() -> None:
1148+
# GH 113
1149+
assert assert_type(pd.DataFrame.__hash__, None) is None
1150+
assert assert_type(pd.DataFrame().__hash__, None) is None
1151+
assert assert_type(pd.Series.__hash__, None) is None
1152+
assert assert_type(pd.Series([], dtype=object).__hash__, None) is None
1153+
assert assert_type(pd.Index.__hash__, None) is None
1154+
assert assert_type(pd.Index([]).__hash__, None) is None
1155+
1156+
def test_func(h: Hashable):
1157+
pass
1158+
1159+
test_func(pd.DataFrame()) # type: ignore[arg-type]
1160+
test_func(pd.Series([], dtype=object)) # type: ignore[arg-type]
1161+
test_func(pd.Index([])) # type: ignore[arg-type]

0 commit comments

Comments
 (0)