Discrepancy between set.__or__ and set.union

From an “high” point of view | operator and .union method are very similar in the set context.

{1,2,3} | {4,5,6} == ({1,2,3}).union({4,5,6})

The .union method, however is “more lenient”, as it accept any iterable.
The | operator instead trows a TypeError if I try to join a set with any “non set” iterable.
Is there a reason why | instead is strict?

I discovered this behavior because I wanted to create a set of integers from a list of ranges, and hoped to write something like

idxs = set()
for r in ranges:
    idxs |= r

From an “high” point of view | operator and .union method are very
similar in the set context.

{1,2,3} | {4,5,6} == ({1,2,3}).union({4,5,6})

The .union method, however is “more lenient”, as it accept any iterable.
The | operator instead trows a TypeError if I try to join a set with any “non set” iterable.
Is there a reason why | instead is strict?

Probably a type safety intent. The .union etc methods are for adding (or
removing, whatever) various elements from the set - those elements might
be in various forms so accepting any iterable is both feasible and
convenient to the user.

However, an expression with sets:

set1 | set2

has more predictable behaviour if you’re sure all the operands are sets.
For one thing, if they’re both sets then the above is cummutable:

set1 | set2 == set2 | set1

If set.__or__ accepted nonsets then that wouldn’t hold, as you’d be
using set2.__or__ in the second expression. If that’s a different type
then you’ll get a different operation entirely. With a pure iterable
(maybe a range()) you’ll get a loud TypeError showing the issue,
but supposing it were a list (well, some collection accepting |)? It
might quietly produce another list, not the outcome you might hope.

Better to enforce tighter constraints, and leave operations which do
“conversions” (such as this iterable->set of elements situation) to
named methods whose behaviour is more overt.

I discovered this behavior because I wanted to create a set of integers from a list of ranges, and hoped to write something like

idxs = set()
for r in ranges:
   idxs |= r

This is better written as:

idxs.update(r)

Note here that .update modifies idx itself. Using |= would make a
new set like .union() does. Slower! More memory!

>>> s=set()
>>> s0 = s
>>> s.update((1,2,3))
>>> s
{1, 2, 3}
>>> s is s0
True
>>> s2=s.union((4,5,6))
>>> s2
{1, 2, 3, 4, 5, 6}
>>> s2 is s0
False

Cheers,
Cameron Simpson cs@cskk.id.au