classification
Title: set union/intersection/difference could accept zero arguments
Type: enhancement Stage: resolved
Components: Versions: Python 3.8
process
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: rhettinger Nosy List: SilentGhost, carandraug, josh.r, mark.dickinson, remi.lapeyre, rhettinger, xtreak
Priority: normal Keywords:

Created on 2018-11-28 14:50 by carandraug, last changed 2018-12-10 21:34 by josh.r. This issue is now closed.

Messages (10)
msg330601 - (view) Author: David Miguel Susano Pinto (carandraug) Date: 2018-11-28 14:50
set union, intersection, difference methods accept any non-zero number of sets and return a new set instance, like so:

    >>> a = set([1, 2])
    >>> b = set([1, 3])
    >>> c = set([3, 5])
    >>> set.union(a, b, c)
    {1, 2, 3, 5}

even if it's only one argument:

    >>> set.union(a)
    {1, 2}

I think it would be nice if zero arguments were not an error:

    >>> set.union()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: descriptor 'union' of 'set' object needs an argument

This would allow to handle any sequence of sets which otherwise requires this:

    if len(sequence):
        return set.union(*sequence)
    else:
        return set()
msg330603 - (view) Author: SilentGhost (SilentGhost) * (Python triager) Date: 2018-11-28 15:24
You can write your code like this:

   set().union(*sequence)

as well as: a.union(b, c) if you already have a set object available.
msg330604 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python triager) Date: 2018-11-28 15:35
I agree with @SilentGhost to use set().union(*sequence) which is compatible with Python 2 too.
msg330687 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2018-11-29 12:52
What would the intersection of zero sets be? The only reasonable mathematical answer is either "not defined", or "the universe" - i.e., the set containing everything, but what would "everything" be in a Python context?

I also don't see how a difference of zero sets makes any sense; you need a set to start subtracting items from.

So I think this feature request only really applies to union.
msg330688 - (view) Author: Rémi Lapeyre (remi.lapeyre) * Date: 2018-11-29 13:25
The intersection of an empty set of sets is either the empty set or the universe sets so if set.union() is defined to support set.union(*sequence), wouldn't it be less surprising for set.intersection() to return the empty set too?
msg330708 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2018-11-29 18:31
> The intersection of an empty set of sets is either the empty set [...]

Nope, it's never the empty set, unless you're using a *very* unusual definition.

The intersection of a collection X of sets is the set of all x in the universe such that x is in S for all S in X. If X is empty, that condition is vacuously true, and you simply get the set of all x.

There are universe problems here, but giving the empty set is definitely wrong.

OTOH, the union of an empty collection of sets _is_ unambiguously the empty set, and the request for `set.union(*args)` to be valid regardless of the length of args seems reasonable to me. Without that validity, there's a potential corner-case bug where `set.union(*args)` potentially fails when `args` is empty.
msg330709 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2018-11-29 18:32
Some reading: http://mathforum.org/library/drmath/view/62503.html
msg330792 - (view) Author: Josh Rosenberg (josh.r) * (Python triager) Date: 2018-11-30 15:26
set.union() without constructing the set you call union on only happens to work for the set.union(a) case because `a` is already a set. union takes arbitrary iterables, not just sets, and you're just cheating by explicitly passing `a` as the expected self argument. If you'd set `a = [1, 2]` (a list, not a set), set.union(a) would fail, because set.union(a) was only working by accident of a being interpreted as self; any such use is misuse.

Point is, the zero args case isn't a unique corner case;

args = ([1, 2], ANY OTHER ITERABLES HERE)
set.union(*args)

fails too, because the first argument is interpreted as self, and must be a set for this to work.

SilentGhost's solution of constructing the set before union-ing via set().union(*args) is the correct solution; it's free of corner cases, removing the specialness of the first element in args (because self is passed in correctly), and not having any troubles with empty args.

intersection is the only interesting case here, where preconstruction of the empty set doesn't work, because that would render the result the empty set unconditionally. The solution there is set(args[0]).intersection(*args) (or *args[1:]), but that's obviously uglier.

I'm -1 on making any changes to set.union to support this misuse case.
msg330802 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2018-11-30 16:20
> set.union() without constructing the set you call union on only happens to work for the set.union(a) case because `a` is already a set.

Good point. I wasn't thinking clearly about the unbound-methodness of this.

> I'm -1 on making any changes to set.union to support this misuse case.

Agreed.
msg331538 - (view) Author: Josh Rosenberg (josh.r) * (Python triager) Date: 2018-12-10 21:34
Given the "feature" in question isn't actually an intended feature (just an accident of how unbound methods work), I'm closing this. We're not going to try to make methods callable without self.
History
Date User Action Args
2018-12-10 21:34:29josh.rsetstatus: open -> closed
resolution: wont fix
messages: + msg331538

stage: resolved
2018-11-30 16:20:37mark.dickinsonsetmessages: + msg330802
2018-11-30 15:26:10josh.rsetnosy: + josh.r
messages: + msg330792
2018-11-29 18:32:30mark.dickinsonsetmessages: + msg330709
2018-11-29 18:31:25mark.dickinsonsetmessages: + msg330708
2018-11-29 13:25:24remi.lapeyresetnosy: + remi.lapeyre
messages: + msg330688
2018-11-29 12:52:22mark.dickinsonsetnosy: + mark.dickinson
messages: + msg330687
2018-11-28 16:39:35rhettingersetassignee: rhettinger
2018-11-28 15:35:24xtreaksetnosy: + rhettinger, xtreak
messages: + msg330604
2018-11-28 15:24:11SilentGhostsetnosy: + SilentGhost

messages: + msg330603
versions: + Python 3.8, - Python 3.7
2018-11-28 14:50:55carandraugcreate