From 9193c9f78cd7ca7ff3523cffbfbc7ab4f1f9c1fb Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Sat, 5 Sep 2026 20:22:19 +0200 Subject: [PATCH 1/2] experiment: remove __eq__ from object class and update annotations on classes instead. --- stdlib/@tests/test_cases/check_pathlib.py | 2 +- stdlib/builtins.pyi | 73 ++++++++++++++++------- stdlib/collections/__init__.pyi | 12 ++-- stdlib/datetime.pyi | 10 ++-- stdlib/pathlib/__init__.pyi | 1 + stdlib/typing.pyi | 6 +- 6 files changed, 65 insertions(+), 39 deletions(-) diff --git a/stdlib/@tests/test_cases/check_pathlib.py b/stdlib/@tests/test_cases/check_pathlib.py index d3e85188b97b..1ff0c7fed794 100644 --- a/stdlib/@tests/test_cases/check_pathlib.py +++ b/stdlib/@tests/test_cases/check_pathlib.py @@ -22,7 +22,7 @@ class MyCustomPath(Path): ... # interesting: it can never hold true on Posix, but could hold true on Windows. We should experiment # with more accurate __new__, such that we only get an error for such comparisons on platforms # where they can never hold true. -if PureWindowsPath("asdf") == Path("asdf"): # type: ignore +if PureWindowsPath("asdf") == Path("asdf"): ... diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index 903b13aa460a..3b71be2227ef 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -115,8 +115,9 @@ class object: # Overriding them in subclasses has different semantics, even if the override has an identical signature. def __setattr__(self, name: str, value: Any, /) -> None: ... def __delattr__(self, name: str, /) -> None: ... - def __eq__(self, value: object, /) -> bool: ... - def __ne__(self, value: object, /) -> bool: ... + # intentionally removed. objects should be compared via `is`. + # def __eq__(self, value: object, /) -> bool: ... + # def __ne__(self, value: object, /) -> bool: ... def __str__(self) -> str: ... # noqa: Y029 def __repr__(self) -> str: ... # noqa: Y029 def __hash__(self) -> int: ... @@ -350,8 +351,8 @@ class int: def __round__(self, ndigits: SupportsIndex = ..., /) -> int: ... def __getnewargs__(self) -> tuple[int]: ... - def __eq__(self, value: object, /) -> bool: ... - def __ne__(self, value: object, /) -> bool: ... + def __eq__(self, value: int, /) -> bool: ... + def __ne__(self, value: int, /) -> bool: ... def __lt__(self, value: int, /) -> bool: ... def __le__(self, value: int, /) -> bool: ... def __gt__(self, value: int, /) -> bool: ... @@ -418,8 +419,8 @@ class float: @overload def __round__(self, ndigits: SupportsIndex, /) -> float: ... - def __eq__(self, value: object, /) -> bool: ... - def __ne__(self, value: object, /) -> bool: ... + def __eq__(self, value: float | int, /) -> bool: ... + def __ne__(self, value: float | int, /) -> bool: ... def __lt__(self, value: float, /) -> bool: ... def __le__(self, value: float, /) -> bool: ... def __gt__(self, value: float, /) -> bool: ... @@ -463,8 +464,8 @@ class complex: def __rmul__(self, value: complex, /) -> complex: ... def __rpow__(self, value: complex, mod: None = None, /) -> complex: ... def __rtruediv__(self, value: complex, /) -> complex: ... - def __eq__(self, value: object, /) -> bool: ... - def __ne__(self, value: object, /) -> bool: ... + def __eq__(self, value: complex | float | int, /) -> bool: ... + def __ne__(self, value: complex | float | int, /) -> bool: ... def __neg__(self) -> complex: ... def __pos__(self) -> complex: ... def __abs__(self) -> float: ... @@ -687,7 +688,8 @@ class str(Sequence[str]): # Incompatible with Sequence.__contains__ def __contains__(self, key: str, /) -> bool: ... # type: ignore[override] - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: str, /) -> bool: ... + def __ne__(self, value: str, /) -> bool: ... def __ge__(self, value: str, /) -> bool: ... @overload @@ -717,8 +719,6 @@ class str(Sequence[str]): @overload def __mul__(self, value: SupportsIndex, /) -> str: ... # type: ignore[misc] - def __ne__(self, value: object, /) -> bool: ... - @overload def __rmul__(self: LiteralString, value: SupportsIndex, /) -> LiteralString: ... @overload @@ -828,8 +828,8 @@ class bytes(Sequence[int]): def __mod__(self, value: Any, /) -> bytes: ... # Incompatible with Sequence.__contains__ def __contains__(self, key: SupportsIndex | ReadableBuffer, /) -> bool: ... # type: ignore[override] - def __eq__(self, value: object, /) -> bool: ... - def __ne__(self, value: object, /) -> bool: ... + def __eq__(self, value: bytes, /) -> bool: ... + def __ne__(self, value: bytes, /) -> bool: ... def __lt__(self, value: bytes, /) -> bool: ... def __le__(self, value: bytes, /) -> bool: ... def __gt__(self, value: bytes, /) -> bool: ... @@ -959,8 +959,8 @@ class bytearray(MutableSequence[int]): def __mod__(self, value: Any, /) -> bytes: ... # Incompatible with Sequence.__contains__ def __contains__(self, key: SupportsIndex | ReadableBuffer, /) -> bool: ... # type: ignore[override] - def __eq__(self, value: object, /) -> bool: ... - def __ne__(self, value: object, /) -> bool: ... + def __eq__(self, value: ReadableBuffer, /) -> bool: ... + def __ne__(self, value: ReadableBuffer, /) -> bool: ... def __lt__(self, value: ReadableBuffer, /) -> bool: ... def __le__(self, value: ReadableBuffer, /) -> bool: ... def __gt__(self, value: ReadableBuffer, /) -> bool: ... @@ -1028,7 +1028,12 @@ class memoryview(Sequence[_I]): def __contains__(self, x: object, /) -> bool: ... def __iter__(self) -> Iterator[_I]: ... def __len__(self) -> int: ... - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: ReadableBuffer, /) -> bool: ... + def __ne__(self, value: ReadableBuffer, /) -> bool: ... + def __lt__(self, value: ReadableBuffer, /) -> bool: ... + def __le__(self, value: ReadableBuffer, /) -> bool: ... + def __gt__(self, value: ReadableBuffer, /) -> bool: ... + def __ge__(self, value: ReadableBuffer, /) -> bool: ... def __hash__(self) -> int: ... @overload @@ -1130,7 +1135,13 @@ class slice(Generic[_StartT_co, _StopT_co, _StepT_co]): @overload def __new__(cls, start: _T1, stop: _T2, step: _T3, /) -> slice[_T1, _T2, _T3]: ... - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: slice, /) -> bool: ... + def __ne__(self, value: slice, /) -> bool: ... + # surprisingly, these are implemented and delagate to tuple comparison + def __ge__(self, value: slice, /) -> bool: ... + def __gt__(self, value: slice, /) -> bool: ... + def __le__(self, value: slice, /) -> bool: ... + def __lt__(self, value: slice, /) -> bool: ... if sys.version_info >= (3, 12): def __hash__(self) -> int: ... @@ -1157,7 +1168,8 @@ class tuple(Sequence[_T_co]): def __le__(self, value: tuple[_T_co, ...], /) -> bool: ... def __gt__(self, value: tuple[_T_co, ...], /) -> bool: ... def __ge__(self, value: tuple[_T_co, ...], /) -> bool: ... - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: tuple, /) -> bool: ... + def __ne__(self, value: tuple, /) -> bool: ... def __hash__(self) -> int: ... @overload @@ -1282,7 +1294,8 @@ class list(MutableSequence[_T]): def __ge__(self, value: list[_T], /) -> bool: ... def __lt__(self, value: list[_T], /) -> bool: ... def __le__(self, value: list[_T], /) -> bool: ... - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: list, /) -> bool: ... + def __ne__(self, value: list, /) -> bool: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @disjoint_base @@ -1355,7 +1368,13 @@ class dict(MutableMapping[_KT, _VT]): def __setitem__(self, key: _KT, value: _VT, /) -> None: ... def __delitem__(self, key: _KT, /) -> None: ... def __iter__(self) -> Iterator[_KT]: ... - def __eq__(self, value: object, /) -> bool: ... + if sys.version_info >= (3, 15): + def __eq__(self, value: dict | frozendict, /) -> bool: ... # type: ignore[override] + def __ne__(self, value: dict | frozendict, /) -> bool: ... # type: ignore[override] + else: + def __eq__(self, value: dict, /) -> bool: ... # type: ignore[override] + def __ne__(self, value: dict, /) -> bool: ... # type: ignore[override] + def __reversed__(self) -> Iterator[_KT]: ... __hash__: ClassVar[None] # type: ignore[assignment] def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @@ -1397,6 +1416,9 @@ if sys.version_info >= (3, 15): cls: type[frozendict[str, _VT]], iterable: Iterable[tuple[str, _VT]], /, **kwargs: _VT ) -> frozendict[str, _VT]: ... + # overrides Mapping.__eq__, and only supports comparison with dict/frozendict + def __eq__(self, value: dict | frozendict, /) -> bool: ... # type: ignore[override] + def __ne__(self, value: dict | frozendict, /) -> bool: ... # type: ignore[override] def copy(self) -> frozendict[_KT, _VT]: ... @overload @@ -1466,7 +1488,9 @@ class set(MutableSet[_T]): def __lt__(self, value: AbstractSet[object], /) -> bool: ... def __ge__(self, value: AbstractSet[object], /) -> bool: ... def __gt__(self, value: AbstractSet[object], /) -> bool: ... - def __eq__(self, value: object, /) -> bool: ... + # overrides MutableSet.__eq__ and only supports comparison with set/frozenset + def __eq__(self, value: set | frozenset, /) -> bool: ... # type: ignore[override] + def __ne__(self, value: set | frozenset, /) -> bool: ... # type: ignore[override] __hash__: ClassVar[None] # type: ignore[assignment] def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @@ -1496,7 +1520,9 @@ class frozenset(AbstractSet[_T_co]): def __lt__(self, value: AbstractSet[object], /) -> bool: ... def __ge__(self, value: AbstractSet[object], /) -> bool: ... def __gt__(self, value: AbstractSet[object], /) -> bool: ... - def __eq__(self, value: object, /) -> bool: ... + # overrides AbstractSet.__eq__ and only supports comparison with set/frozenset + def __eq__(self, value: set | frozenset, /) -> bool: ... # type: ignore[override] + def __ne__(self, value: set | frozenset, /) -> bool: ... # type: ignore[override] def __hash__(self) -> int: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... @@ -1524,7 +1550,8 @@ class range(Sequence[int]): def count(self, value: int, /) -> int: ... def index(self, value: int, /) -> int: ... # type: ignore[override] def __len__(self) -> int: ... - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: range, /) -> bool: ... + def __ne__(self, value: range, /) -> bool: ... def __hash__(self) -> int: ... def __contains__(self, key: object, /) -> bool: ... def __iter__(self) -> Iterator[int]: ... diff --git a/stdlib/collections/__init__.pyi b/stdlib/collections/__init__.pyi index 3e2e838eaa26..d1b94b5810b9 100644 --- a/stdlib/collections/__init__.pyi +++ b/stdlib/collections/__init__.pyi @@ -130,7 +130,7 @@ class UserList(MutableSequence[_T]): def __le__(self, other: list[_T] | UserList[_T]) -> bool: ... def __gt__(self, other: list[_T] | UserList[_T]) -> bool: ... def __ge__(self, other: list[_T] | UserList[_T]) -> bool: ... - def __eq__(self, other: object) -> bool: ... + def __eq__(self, other: list | UserList) -> bool: ... def __contains__(self, item: object) -> bool: ... def __len__(self) -> int: ... @@ -182,7 +182,7 @@ class UserString(Sequence[UserString]): def __le__(self, string: str | UserString) -> bool: ... def __gt__(self, string: str | UserString) -> bool: ... def __ge__(self, string: str | UserString) -> bool: ... - def __eq__(self, string: object) -> bool: ... + def __eq__(self, string: str | UserString) -> bool: ... def __hash__(self) -> int: ... def __contains__(self, char: object) -> bool: ... def __len__(self) -> int: ... @@ -283,7 +283,7 @@ class deque(MutableSequence[_T]): def __le__(self, value: deque[_T], /) -> bool: ... def __gt__(self, value: deque[_T], /) -> bool: ... def __ge__(self, value: deque[_T], /) -> bool: ... - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: deque, /) -> bool: ... def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... class Counter(dict[_T, int], Generic[_T]): @@ -325,8 +325,8 @@ class Counter(dict[_T, int], Generic[_T]): def total(self) -> int: ... def __missing__(self, key: _T) -> int: ... def __delitem__(self, elem: object) -> None: ... - def __eq__(self, other: object) -> bool: ... - def __ne__(self, other: object) -> bool: ... + def __eq__(self, other: Counter) -> bool: ... # type: ignore[override] + def __ne__(self, other: Counter) -> bool: ... # type: ignore[override] def __le__(self, other: Counter[Any]) -> bool: ... def __lt__(self, other: Counter[Any]) -> bool: ... def __ge__(self, other: Counter[Any]) -> bool: ... @@ -412,8 +412,6 @@ class OrderedDict(dict[_KT, _VT]): @overload def pop(self, key: _KT, default: _T) -> _VT | _T: ... - def __eq__(self, value: object, /) -> bool: ... - if sys.version_info >= (3, 15): @overload def __or__(self, value: dict[_KT, _VT] | frozendict[_KT, _VT], /) -> Self: ... diff --git a/stdlib/datetime.pyi b/stdlib/datetime.pyi index 0a21f72b09de..c691975bbb19 100644 --- a/stdlib/datetime.pyi +++ b/stdlib/datetime.pyi @@ -34,7 +34,7 @@ class timezone(tzinfo): def utcoffset(self, dt: datetime | None, /) -> timedelta: ... def dst(self, dt: datetime | None, /) -> None: ... def __hash__(self) -> int: ... - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: timezone, /) -> bool: ... if sys.version_info >= (3, 11): UTC: timezone @@ -109,7 +109,7 @@ class date: def __lt__(self, value: date, /) -> bool: ... def __ge__(self, value: date, /) -> bool: ... def __gt__(self, value: date, /) -> bool: ... - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: date, /) -> bool: ... def __add__(self, value: timedelta, /) -> Self: ... def __radd__(self, value: timedelta, /) -> Self: ... @@ -156,7 +156,7 @@ class time: def __lt__(self, value: time, /) -> bool: ... def __ge__(self, value: time, /) -> bool: ... def __gt__(self, value: time, /) -> bool: ... - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: time, /) -> bool: ... def __hash__(self) -> int: ... def isoformat(self, timespec: str = "auto") -> str: ... @@ -262,7 +262,7 @@ class timedelta: def __lt__(self, value: timedelta, /) -> bool: ... def __ge__(self, value: timedelta, /) -> bool: ... def __gt__(self, value: timedelta, /) -> bool: ... - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: timedelta, /) -> bool: ... def __bool__(self) -> bool: ... def __hash__(self) -> int: ... @@ -370,7 +370,7 @@ class datetime(date): def __lt__(self, value: datetime, /) -> bool: ... # type: ignore[override] def __ge__(self, value: datetime, /) -> bool: ... # type: ignore[override] def __gt__(self, value: datetime, /) -> bool: ... # type: ignore[override] - def __eq__(self, value: object, /) -> bool: ... + def __eq__(self, value: datetime, /) -> bool: ... # type: ignore[override] def __hash__(self) -> int: ... @overload # type: ignore[override] diff --git a/stdlib/pathlib/__init__.pyi b/stdlib/pathlib/__init__.pyi index 781b6aebd8a1..d9984f99836c 100644 --- a/stdlib/pathlib/__init__.pyi +++ b/stdlib/pathlib/__init__.pyi @@ -86,6 +86,7 @@ class PurePath(PathLike[str]): if sys.version_info >= (3, 15): def __vfspath__(self) -> str: ... + def __eq__(self, other: PurePath) -> bool: ... def __lt__(self, other: PurePath) -> bool: ... def __le__(self, other: PurePath) -> bool: ... def __gt__(self, other: PurePath) -> bool: ... diff --git a/stdlib/typing.pyi b/stdlib/typing.pyi index 9f1c476eb2e7..8ff251e5ed57 100644 --- a/stdlib/typing.pyi +++ b/stdlib/typing.pyi @@ -742,7 +742,7 @@ class AbstractSet(Collection[_T_co]): def __or__(self, other: AbstractSet[_T], /) -> AbstractSet[_T_co | _T]: ... def __sub__(self, other: AbstractSet[Any], /) -> AbstractSet[_T_co]: ... def __xor__(self, other: AbstractSet[_T], /) -> AbstractSet[_T_co | _T]: ... - def __eq__(self, other: object, /) -> bool: ... + def __eq__(self, other: AbstractSet, /) -> bool: ... def isdisjoint(self, other: Iterable[Any], /) -> bool: ... class MutableSet(AbstractSet[_T]): @@ -822,7 +822,7 @@ class Mapping(Collection[_KT], Generic[_KT, _VT_co]): def keys(self) -> KeysView[_KT]: ... def values(self) -> ValuesView[_VT_co]: ... def __contains__(self, key: object, /) -> bool: ... - def __eq__(self, other: object, /) -> bool: ... + def __eq__(self, other: Mapping, /) -> bool: ... class MutableMapping(Mapping[_KT, _VT]): @abstractmethod @@ -1187,7 +1187,7 @@ else: self, globalns: dict[str, Any] | None, localns: Mapping[str, Any] | None, recursive_guard: frozenset[str] ) -> Any | None: ... # AnnotationForm - def __eq__(self, other: object) -> bool: ... + def __eq__(self, other: ForwardRef, /) -> bool: ... def __hash__(self) -> int: ... if sys.version_info >= (3, 11): def __or__(self, other: Any) -> _SpecialForm: ... From de7ee5cda6f573320c140d4942304f99b0d823fa Mon Sep 17 00:00:00 2001 From: Randolf Scholz Date: Sat, 5 Sep 2026 20:27:40 +0200 Subject: [PATCH 2/2] Add type ignore comments to NodeView and OutEdgeView classes in reportviews.pyi --- stubs/networkx/networkx/classes/reportviews.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stubs/networkx/networkx/classes/reportviews.pyi b/stubs/networkx/networkx/classes/reportviews.pyi index a133389f2420..ef8a59fe8bef 100644 --- a/stubs/networkx/networkx/classes/reportviews.pyi +++ b/stubs/networkx/networkx/classes/reportviews.pyi @@ -34,7 +34,7 @@ __all__ = [ "OutMultiDegreeView", ] -class NodeView(Mapping[_Node, _NodeData], AbstractSet[_Node], Generic[_Node, _NodeData, _EdgeData]): +class NodeView(Mapping[_Node, _NodeData], AbstractSet[_Node], Generic[_Node, _NodeData, _EdgeData]): # type: ignore[misc] # pyerfly: ignore[inconsistent-inheritance] __slots__ = ("_nodes",) def __init__(self, graph: Graph[_Node, _NodeData, _EdgeData]) -> None: ... def __len__(self) -> int: ... @@ -109,7 +109,7 @@ class MultiEdgeDataView(OutEdgeDataView[_Node, _D]): class InMultiEdgeDataView(OutEdgeDataView[_Node, _D]): __slots__ = () -class OutEdgeView(AbstractSet[Incomplete], Mapping[Incomplete, Incomplete], EdgeViewABC, Generic[_Node, _NodeData, _EdgeData]): +class OutEdgeView(AbstractSet[Incomplete], Mapping[Incomplete, Incomplete], EdgeViewABC, Generic[_Node, _NodeData, _EdgeData]): # type: ignore[misc] # pyerfly: ignore[inconsistent-inheritance] __slots__ = ("_adjdict", "_graph", "_nodes_nbrs") def __init__(self, G: Graph[_Node, _NodeData, _EdgeData]) -> None: ... def __len__(self) -> int: ...