Skip to content

collections.abc.Set does not support Iterable s in some dunder methods #14659

Open

Description

@Gatsik

While their implementation even specifically checks whether operand is an instance of an Iterable (for example: https://github.com/python/cpython/blob/c779f2324df06563b4ba3d70d0941e619ccaf5ff/Lib/_collections_abc.py#L628)

from collections.abc import Set
from typing import Iterable, Iterator


class S[T](Set[T]):
    def __init__(self, s: Iterable[T] | None = None) -> None:
        super().__init__()
        self._set: set[T] = set(s) if s else set()

    def __contains__(self, value: object) -> bool:
        return value in self._set

    def __iter__(self) -> Iterator[T]:
        return iter(self._set)

    def __len__(self) -> int:
        return len(self._set)

    def add(self, value: T) -> None:
        self._set.add(value)

    def discard(self, value: T) -> None:
        self._set.discard(value)

    def __repr__(self) -> str:
        return self._set.__repr__()

s = S[int]((1,2,3))
print(s & [4])
print(s | ("4",))
print(s - [1])
print(s ^ ("1",))
$ mypy t.py

t.py:29: error: Unsupported operand types for & ("S[int]" and "list[int]")  [operator]
t.py:30: error: Unsupported operand types for | ("S[int]" and "tuple[str]")  [operator]
t.py:31: error: Unsupported operand types for - ("S[int]" and "list[int]")  [operator]
t.py:32: error: Unsupported operand types for ^ ("S[int]" and "tuple[str]")  [operator]
Found 4 errors in 1 file (checked 1 source file)
$ python3 t.py

set()
{1, 2, 3, '4'}
{2, 3}
{1, 2, 3, '1'}

Activity

  1. srittau commented on Sep 3, 2025

    @srittau
    Collaborator

    As the linked PR shows, there's a conflict between what the abstract base class supports and what the (virtual) set protocol (which should be represented by collections.abc.Set) supports. It's unfortunate that AbstractSet accepts a broader interface than what its (virtual) sub-classes like set do, basically breaking the LSP.

    That's one of the reasons why these pseudo-protocols shouldn't be used in argument annotations (see also PyCQA/flake8-pyi#508 and PyCQA/flake8-pyi#525).

    I'm unsure what the correct solution is, but I slightly prefer exploring the approach in #14653 together with enforcing not to use those pseudo-protocols in argument positions. At least the LSP violations are made explicit.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions