Mailing List Archive

Different "look and feel" of some built-in functions
Why do some built-in Python functions feel so differently:

For example sum(), all(), any() expect exactly one argument which is a
sequence to operate on, i.e. a list, an iterator or a generator etc.

sum([1,2,3,4])
sum(range(1, 101))
sum(2**i for i in range(10))
all([True, False])
any(even, {1,2,3,4})

while other functions like set.union() and set.intersection() work on
a list of arguments but not on a sequence:

set.intersection({1,2,3}, {3,4,5})

but

set.union(map(...)) # does not work
set.intersection(list(...)) # does not work

and you have to use a * instead

set.union(*map(...))

etc.

Is this just for historical reason? And wouldn't it be possible and
desirable to have more consistency?

Steve
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
On Fri, Sep 24, 2021 at 11:47 PM Steve Keller <keller.steve@gmx.de> wrote:
>
> Why do some built-in Python functions feel so differently:
>
> For example sum(), all(), any() expect exactly one argument which is a
> sequence to operate on, i.e. a list, an iterator or a generator etc.
>
> sum([1,2,3,4])
> sum(range(1, 101))
> sum(2**i for i in range(10))
> all([True, False])
> any(even, {1,2,3,4})
>
> while other functions like set.union() and set.intersection() work on
> a list of arguments but not on a sequence:
>
> set.intersection({1,2,3}, {3,4,5})
>
> but
>
> set.union(map(...)) # does not work
> set.intersection(list(...)) # does not work
>
> and you have to use a * instead
>
> set.union(*map(...))
>
> etc.
>
> Is this just for historical reason? And wouldn't it be possible and
> desirable to have more consistency?
>

The ones you're calling off the set class are actually meant to be methods.

>>> s = {1,2,3}
>>> s.intersection({3,4,5})
{3}

They expect a set, specifically, as the first argument, because
normally that one goes before the dot. If you want to call the unbound
method with two arguments, that's fine, but it's not the intended use,
so you have to basically fudge it to look like a normal method call :)
That's why it doesn't take a sequence.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
Steve Keller wrote at 2021-9-24 11:48 +0200:
>Why do some built-in Python functions feel so differently:

Because the typical use cases are different

>For example sum(), all(), any() expect exactly one argument which is a
>sequence to operate on, i.e. a list, an iterator or a generator etc.
>
> sum([1,2,3,4])
> sum(range(1, 101))
> sum(2**i for i in range(10))
> all([True, False])
> any(even, {1,2,3,4})

You use those functions typically on a large number
of operands, typically already collected together via some form
of iterator.
If you want to compute the sum of a few operands, you
would usually not use `sum` but `o1 + o2 + ...`.

>while other functions like set.union() and set.intersection() work on
>a list of arguments but not on a sequence:
>
> set.intersection({1,2,3}, {3,4,5})

Those operations are typically applied to a small number
of operands. You would need to build an iterator in that
case should the functions only accept iterators.



--
Dieter
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
Stefan Ram wrote at 2021-9-24 14:53 GMT:
>Steve Keller <keller.steve@gmx.de> writes:
>>Why do some built-in Python functions feel so differently:
>
>|>>> s = set()
>|>>> s.add( 1 )
>|>>>
>
>
>|>>> l = []
>|>>> l.add( 1 )
>
>|
>|Traceback (most recent call last):
>| File "<stdin>", line 1, in <module>
>|AttributeError: 'list' object has no attribute 'add'
>|
>|>>> l.append( 1 )

A list is ordered. Therefore, it is important where
in this order an element is added. Thus, for a list,
`append` is a better name than `add` -- because it already
tells us in the name where it adds the new element.

A set is unordered. Therefore, the name `append` does not make sense
for a set.



--
Dieter
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
On Sat, Sep 25, 2021 at 3:42 AM Stefan Ram <ram@zedat.fu-berlin.de> wrote:
>
> "Dieter Maurer" <dieter@handshake.de> writes:
> >A list is ordered. Therefore, it is important where
> >in this order an element is added. Thus, for a list,
> >`append` is a better name than `add` -- because it already
> >tells us in the name where it adds the new element.
>
> In a collection of texts, which is not large but mixed from
> many different fields and genres, I find (using a Python
> script, of course) eight hits for "added to the list" :
>
> |s and rock n roll can be added to the list. As - Taylor, 2012
> | of opinion was probably added to the list tow - from a dictionary
> |gg and his wife might be added to the list of - Sir Walter Scott
> |ships when your name was added to the list. In - Percy Bysshe Shelley
> |em that wealth should be added to the list. No - Henry
> |n was discovered and was added to the list of - from a dictionary
> |nd said his name must be added to the list, or - Mark Twain
>
> . There was no hit for "appended to the list".
>
> When one says "add something to a list", it is usually understood
> that one adds it at the /end/. In the case of traditional written
> lists it is not possible in any other way.
>

When I add something to the shopping list, it is not added to the end.
It is added anywhere that there is room. If you care about the
sequence, you would say "add to the end". Or, using the more technical
and briefer word, "append". Most of the lists you're seeing there are
not being added to the end of; for instance, I would guess that quite
a few of them are inherently sorted lists, so you would be adding a
person's name in affabeck lauder, or adding something to a particular
ranked position, or whatever else. This is not the same thing.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
"Dieter Maurer" <dieter@handshake.de> writes:

> Steve Keller wrote at 2021-9-24 11:48 +0200:
> >Why do some built-in Python functions feel so differently:
>
> Because the typical use cases are different
>
> [...]
>
> >while other functions like set.union() and set.intersection() work on
> >a list of arguments but not on a sequence:
> >
> > set.intersection({1,2,3}, {3,4,5})
>
> Those operations are typically applied to a small number
> of operands. You would need to build an iterator in that
> case should the functions only accept iterators.

In my experience I need intersection and union on a list of sets, set
of sets or a map() returning sets much more often. E.g. in some
mathematical problems, and in automaton theory (IIRC, computing of LR
or LALR sets, converting NFA to DFA, minimizing DFA), many graph
algorithms traversing the nodes (shortest path, ...), and so on).

Intersection and union of two sets is actually often seen in na?ve
programming in loops like

for t in (some_sequence):
s.union(t)

where set.union(*(some_sequence)) would be much more elegant.

BTW, I like how the min() and max() functions allow both ways of being
called.

Steve
Re: Different "look and feel" of some built-in functions [ In reply to ]
On 25/09/21 10:15 am, Steve Keller wrote:
> BTW, I like how the min() and max() functions allow both ways of being
> called.

That wouldn't work for set.union and set.intersection, because as
was pointed out, they're actually methods, so set.union(some_seq)
is a type error:

>>> a = {1, 2}
>>> b = {3, 4}
>>> set.union([a, b])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor 'union' for 'set' objects doesn't apply to a
'list' object

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().

--
Greg
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
On Sat, 25 Sept 2021 at 00:37, Greg Ewing <greg.ewing@canterbury.ac.nz>
wrote:

> On 25/09/21 10:15 am, Steve Keller wrote:
> > BTW, I like how the min() and max() functions allow both ways of being
> > called.
>
> That wouldn't work for set.union and set.intersection, because as
> was pointed out, they're actually methods, so set.union(some_seq)
> is a type error:
>
> >>> a = {1, 2}
> >>> b = {3, 4}
> >>> set.union([a, b])
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> TypeError: descriptor 'union' for 'set' objects doesn't apply to a
> 'list' object
>
> 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.

--
Oscar
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
On Sat, Sep 25, 2021 at 10:56 AM Oscar Benjamin
<oscar.j.benjamin@gmail.com> wrote:
>
> On Sat, 25 Sept 2021 at 00:37, Greg Ewing <greg.ewing@canterbury.ac.nz>
> wrote:
>
> > On 25/09/21 10:15 am, Steve Keller wrote:
> > > BTW, I like how the min() and max() functions allow both ways of being
> > > called.
> >
> > That wouldn't work for set.union and set.intersection, because as
> > was pointed out, they're actually methods, so set.union(some_seq)
> > is a type error:
> >
> > >>> a = {1, 2}
> > >>> b = {3, 4}
> > >>> set.union([a, b])
> > Traceback (most recent call last):
> > File "<stdin>", line 1, in <module>
> > TypeError: descriptor 'union' for 'set' objects doesn't apply to a
> > 'list' object
> >
> > 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)

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
On Sat, 25 Sept 2021 at 02:01, Chris Angelico <rosuav@gmail.com> wrote:

> On Sat, Sep 25, 2021 at 10:56 AM Oscar Benjamin
> <oscar.j.benjamin@gmail.com> wrote:
> >
> > On Sat, 25 Sept 2021 at 00:37, Greg Ewing <greg.ewing@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.


--
Oscar
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
On Sat, Sep 25, 2021 at 11:11 AM Oscar Benjamin
<oscar.j.benjamin@gmail.com> wrote:
>
> On Sat, 25 Sept 2021 at 02:01, Chris Angelico <rosuav@gmail.com> wrote:
>>
>> On Sat, Sep 25, 2021 at 10:56 AM Oscar Benjamin
>> <oscar.j.benjamin@gmail.com> wrote:
>> >
>> > On Sat, 25 Sept 2021 at 00:37, Greg Ewing <greg.ewing@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.
>

Maybe, but if you start with a set, then you define the base case, and
it also is quite happy to take non-set arguments:

>>> set().union([1,2,3], map(int, "234"), {3:"three",4:"four",5:"five"})
{1, 2, 3, 4, 5}

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
On Sat, 25 Sept 2021 at 02:16, Chris Angelico <rosuav@gmail.com> wrote:

> On Sat, Sep 25, 2021 at 11:11 AM Oscar Benjamin
> <oscar.j.benjamin@gmail.com> wrote:
> >
> > On Sat, 25 Sept 2021 at 02:01, Chris Angelico <rosuav@gmail.com> wrote:
> >>
> >> On Sat, Sep 25, 2021 at 10:56 AM Oscar Benjamin
> >> <oscar.j.benjamin@gmail.com> wrote:
> >> >
> >> > On Sat, 25 Sept 2021 at 00:37, Greg Ewing <
> greg.ewing@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.
> >
>
> Maybe, but if you start with a set, then you define the base case, and
> it also is quite happy to take non-set arguments:
>
> >>> set().union([1,2,3], map(int, "234"), {3:"three",4:"four",5:"five"})
> {1, 2, 3, 4, 5}


Actually it should be union(sets) rather than union(*sets). A more
realistic example is when you need the union of sets given by something
like a generator expression so it looks like:

items = set().union(*(f(x) for x in stuff))

With a union function it should look like:

items = union(f(x) for x in stuff)

--
Oscar
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
Stefan Ram wrote at 2021-9-24 16:48 GMT:
>"Dieter Maurer" <dieter@handshake.de> writes:
>>A list is ordered. Therefore, it is important where
>>in this order an element is added. Thus, for a list,
>>`append` is a better name than `add` -- because it already
>>tells us in the name where it adds the new element.
>
> In a collection of texts, which is not large but mixed from
> many different fields and genres, I find (using a Python
> script, of course) eight hits for "added to the list" :
>
>|s and rock n roll can be added to the list. As - Taylor, 2012
>| of opinion was probably added to the list tow - from a dictionary
>|gg and his wife might be added to the list of - Sir Walter Scott
>|ships when your name was added to the list. In - Percy Bysshe Shelley
>|em that wealth should be added to the list. No - Henry
>|n was discovered and was added to the list of - from a dictionary
>|nd said his name must be added to the list, or - Mark Twain

While a list is ordered,
applications using a list may not be interested in the particular
order and thus just speak of "add to the list"
rather than "append to the list".

Nevertheless, I find the name `append` far better than `add` for
the list type - because it describes better what it is doing.
I am a big fan of "speaking names".

> . There was no hit for "appended to the list".
>
> When one says "add something to a list", it is usually understood
> that one adds it at the /end/. In the case of traditional written
> lists it is not possible in any other way.

Really? Prepending should be as possible as appending
(if one disregards implementation details).



--
Dieter
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
On Sun, Sep 26, 2021 at 2:27 AM Dieter Maurer <dieter@handshake.de> wrote:
>
> Stefan Ram wrote at 2021-9-24 16:48 GMT:
> >"Dieter Maurer" <dieter@handshake.de> writes:
> >>A list is ordered. Therefore, it is important where
> >>in this order an element is added. Thus, for a list,
> >>`append` is a better name than `add` -- because it already
> >>tells us in the name where it adds the new element.
> >
> > In a collection of texts, which is not large but mixed from
> > many different fields and genres, I find (using a Python
> > script, of course) eight hits for "added to the list" :
> >
> >|s and rock n roll can be added to the list. As - Taylor, 2012
> >| of opinion was probably added to the list tow - from a dictionary
> >|gg and his wife might be added to the list of - Sir Walter Scott
> >|ships when your name was added to the list. In - Percy Bysshe Shelley
> >|em that wealth should be added to the list. No - Henry
> >|n was discovered and was added to the list of - from a dictionary
> >|nd said his name must be added to the list, or - Mark Twain
>
> While a list is ordered,
> applications using a list may not be interested in the particular
> order and thus just speak of "add to the list"
> rather than "append to the list".
>
> Nevertheless, I find the name `append` far better than `add` for
> the list type - because it describes better what it is doing.
> I am a big fan of "speaking names".

Yeah, so I would say it makes perfectly good sense to do something like this:

def add_customer(...):
cust = ...
customers.append(cust)

where it's "add" in your application, but "append" in Python's list object.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
Steve Keller wrote at 2021-9-25 00:15 +0200:
>"Dieter Maurer" <dieter@handshake.de> writes:
>
>> Steve Keller wrote at 2021-9-24 11:48 +0200:
>> >Why do some built-in Python functions feel so differently:
>>
>> Because the typical use cases are different
>>
>> [...]
>>
>> >while other functions like set.union() and set.intersection() work on
>> >a list of arguments but not on a sequence:
>> >
>> > set.intersection({1,2,3}, {3,4,5})
>>
>> Those operations are typically applied to a small number
>> of operands. You would need to build an iterator in that
>> case should the functions only accept iterators.
>
>In my experience I need intersection and union on a list of sets, set
>of sets or a map() returning sets much more often. E.g. in some
>mathematical problems, and in automaton theory (IIRC, computing of LR
>or LALR sets, converting NFA to DFA, minimizing DFA), many graph
>algorithms traversing the nodes (shortest path, ...), and so on).

I, too, occasionally work with set operations on many operands
-- in the context of `Zope`, more precisely `BTrees`.
There, I have both `union(op1, op2)` and `multiunion(iterator)`
(`multiunion` is there primarily for efficiency reasons).

I you often have operations on large sets of operands,
you could define corresponding "convernience functions".



--
Dieter
--
https://mail.python.org/mailman/listinfo/python-list
Re: Different "look and feel" of some built-in functions [ In reply to ]
On Sat, 25 Sept 2021 at 02:11, Oscar Benjamin <oscar.j.benjamin@gmail.com>
wrote:

> On Sat, 25 Sept 2021 at 02:01, Chris Angelico <rosuav@gmail.com> wrote:
>
>> On Sat, Sep 25, 2021 at 10:56 AM Oscar Benjamin
>> <oscar.j.benjamin@gmail.com> wrote:
>> >
>> > On Sat, 25 Sept 2021 at 00:37, Greg Ewing <greg.ewing@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
--
https://mail.python.org/mailman/listinfo/python-list