Different "look and feel" of some built-in functions

Oscar Benjamin oscar.j.benjamin at gmail.com
Sat Oct 2 10:45:11 EDT 2021

```On Sat, 25 Sept 2021 at 02:11, Oscar Benjamin <oscar.j.benjamin at gmail.com>
wrote:

> On Sat, 25 Sept 2021 at 02:01, Chris Angelico <rosuav at gmail.com> wrote:
>
>> On Sat, Sep 25, 2021 at 10:56 AM Oscar Benjamin
>> <oscar.j.benjamin at gmail.com> wrote:
>> >
>> > On Sat, 25 Sept 2021 at 00:37, Greg Ewing <greg.ewing at canterbury.ac.nz>
>> > wrote:
>> > > I suppose they could be fiddled somehow to make it possible, but
>> > > that would be turning them into special cases that break the rules.
>> > > It would be better to provide separate functions, as was done with
>> > > sum().
>> > >
>> >
>> > A separate union function would be good. Even in a situation where all
>> > inputs are assured to be sets the set.union method fails the base case:
>> >
>> > >>> sets = []
>> > >>> set.union(*sets)
>> > Traceback (most recent call last):
>> >   File "<stdin>", line 1, in <module>
>> > TypeError: descriptor 'union' of 'set' object needs an argument
>> >
>> > In the case of intersection perhaps the base case should be undefined.
>> >
>>
>> Rather than calling the unbound method, why not just call it on an
>> empty set? That defines your base case as well.
>>
>> set().union(*sets)
>>
>
> That is indeed what I do but it seems unnatural compared to simply
> union(*sets). It shouldn't be necessary to create a redundant empty set
> just to compute the union of some other sets. If there was a union function
> then I don't think I would ever use the union method.
>

I just hit up against a problem using this with mypy:

\$ cat y.py
from typing import Set, Tuple

class Tree:
_children: 'Tuple[Tree]'

@property
def children(self) -> 'Tuple[Tree]':
return self._children

@property
def leaves(self) -> 'Set[Tree]':
return set().union(*(child.leaves for child in self.children))

\$ mypy y.py
y.py:12: error: Incompatible return value type (got "Set[<nothing>]",
expected "Set[Tree]")
y.py:12: error: Argument 1 to "union" of "set" has incompatible type
"*Generator[Set[Tree], None, None]"; expected "Iterable[<nothing>]"
Found 2 errors in 1 file (checked 1 source file)

It seems that mypy is okay with set.union(*stuff) but not
set().union(*stuff).

I can fix it with:

@property
def leaves(self) -> 'Set[Tree]':
empty: 'Set[Tree]' = set()
return empty.union(*(child.leaves for child in self.children))

That's just horrible though. Why should I give a name and type hint for the
completely redundant empty set object?

Does anyone know of a better way to hint this?

--
Oscar
```