collections.abc.Set operators raise TypeError without _from_iterable override (tested)

collections.abc.Set only requires __contains__, __iter__, and __len__ -- in exchange you get &, |, -, ^, and comparison operators for free. Those operators build their result by calling self._from_iterable(iterable), and the default implementation of _from_iterable is just cls(iterable) -- it calls your class's constructor with exactly one positional argument. If your subclass's __init__ needs anything more than that single iterable (a label, a name, a config object, anything), every operator that constructs a new set breaks, even though plain membership tests, iteration, and len() all work perfectly on the same object.

1. A Set subclass with an extra required __init__ argument

from collections.abc import Set

class LabeledSet(Set):
    def __init__(self, label, items):
        self.label = label
        self._items = frozenset(items)
    def __contains__(self, x):
        return x in self._items
    def __iter__(self):
        return iter(self._items)
    def __len__(self):
        return len(self._items)

a = LabeledSet("a", [1, 2, 3])
b = LabeledSet("b", [2, 3, 4])
print(len(a), list(a), 2 in a)   # the required abstract methods -- all fine
3 [1, 2, 3] True

Measured: the three required abstract methods work correctly. Nothing about basic usage suggests a problem -- this is exactly the kind of class that looks finished after a quick smoke test.

2. Every set operator raises TypeError

try:
    c = a & b
except TypeError as e:
    print("a & b ->", e)
try:
    c = a | b
except TypeError as e:
    print("a | b ->", e)
a & b -> LabeledSet.__init__() missing 1 required positional argument: 'items'
a | b -> LabeledSet.__init__() missing 1 required positional argument: 'items'

Measured: &, |, -, and ^ all fail the same way. Set's mixin implementations build their result by calling type(self)._from_iterable(result_items), and the inherited _from_iterable classmethod is just return cls(iterable) -- a single positional argument. LabeledSet.__init__ requires a label first, so the call fails every time, regardless of which operator triggered it.

3. The fix: override _from_iterable

class LabeledSet(Set):
    def __init__(self, label, items):
        self.label = label
        self._items = frozenset(items)
    def __contains__(self, x):
        return x in self._items
    def __iter__(self):
        return iter(self._items)
    def __len__(self):
        return len(self._items)
    @classmethod
    def _from_iterable(cls, it):
        return cls("derived", it)

a = LabeledSet("a", [1, 2, 3])
b = LabeledSet("b", [2, 3, 4])
print(list(a & b))
print(sorted(a | b))
print(list(a - b))
print(sorted(a ^ b))
[2, 3]
[1, 2, 3, 4]
[1]
[1, 4]

Measured: overriding the _from_iterable classmethod to supply whatever extra arguments the constructor needs fixes every operator at once, since they all route through it. This is documented behavior (collections.abc.Set specifically calls out _from_iterable as an overridable hook), but it is easy to miss because the three required abstract methods give no hint that a fourth, optional method needs attention until an operator is actually used.

Frequently asked questions

Why does collections.abc.Set's & operator raise TypeError on my subclass?

We measured that Set's &, |, -, and ^ operators build their result via self._from_iterable(iterable), whose default implementation calls cls(iterable) with a single positional argument. If your __init__ requires more than just an iterable, that call fails with a missing-argument TypeError.

How do I fix a collections.abc.Set subclass that needs extra constructor arguments?

Override the _from_iterable classmethod to call your constructor correctly, e.g. return cls(your_extra_args, it). We measured this fixing &, |, -, and ^ all at once, since every Set mixin operator routes through it.

Why didn't testing __contains__/__iter__/__len__ catch this bug?

Those are the three required abstract methods and work independently of _from_iterable -- we measured them succeeding normally. _from_iterable is only invoked by the derived set operators (&, |, -, ^, and the comparison-adjacent copy operations), so a class can look fully functional under basic testing and still break the moment an operator is used.

Does this affect frozenset or set directly?

No -- we only measured this for custom collections.abc.Set subclasses with a non-trivial __init__. Built-in set and frozenset already have constructors that accept a single iterable argument, so their inherited _from_iterable default works without any override.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5's built-in collections.abc.Set: subclassed it with an __init__ requiring a second argument beyond the iterable, implemented only the three required abstract methods (__contains__, __iter__, __len__), and called the inherited operators &, |, -, and ^. PlainHeaders is our extension; Python's collections.abc module is built in.