Mailing List Archive

Re: Tuple Comprehension ???
On 2/20/23 20:36, Hen Hanna wrote:
> For a while, i've been curious about a [Tuple Comprehension]

I've never heard of a "Tuple comprehension." No such thing exists as
far as I know.

> So finally i tried it, and the result was a bit surprising...
>
>
> X= [ x for x in range(10) ]
> X= ( x for x in range(10) )
> print(X)
> a= list(X)
> print(a)

What was surprising? Don't keep us in suspense!

Using square brackets is a list comprehension. Using parenthesis creates
a generator expression. It is not a tuple. A generator expression can be
perhaps thought of as a lazy list. Instead of computing each member
ahead of time, it returns a generator object which, when iterated over,
produces the members one at a time. This can be a tremendous
optimization in terms of resource usage. See
https://docs.python.org/3/reference/expressions.html#generator-expressions.
Also you can search google for "generator expression" for other examples.

--
https://mail.python.org/mailman/listinfo/python-list
Re: Tuple Comprehension ??? [ In reply to ]
On Monday, February 20, 2023 at 7:57:14 PM UTC-8, Michael Torrie wrote:
> On 2/20/23 20:36, Hen Hanna wrote:
> > For a while, i've been curious about a [Tuple Comprehension]
> I've never heard of a "Tuple comprehension." No such thing exists as
> far as I know.
> > So finally i tried it, and the result was a bit surprising...
> >
> >
> > X= [ x for x in range(10) ]
> > X= ( x for x in range(10) )
> > print(X)
> > a= list(X)
> > print(a)


> What was surprising? Don't keep us in suspense!
>
> Using square brackets is a list comprehension. Using parenthesis creates
> a generator expression. It is not a tuple.

ok!



LisX= [x for x in range(10) ]

print( sum( LisX ))
print( max( LisX ))

print( sum( x for x in range(10) ) )
print( max( x for x in range(10) ) )

print( * LisX )

print( max( * LisX ))
print( sum( LisX )) # same as before
# print( sum( * LisX )) <------- Bad syntax !!!

TypeError: sum() takes at most 2 arguments (10 given)


_____________________

(A) print( max( * LisX ))
(B) print( sum( * LisX )) <------- Bad syntax !!!

What's most surprising is.... (A) is ok, and (B) is not.

even tho' max() and sum() have (basically) the same syntax... ( takes one arg , whch is a list )



i've been programming for many years... ( just knew to Python )
--
https://mail.python.org/mailman/listinfo/python-list
RE: Tuple Comprehension ??? [ In reply to ]
Tuples are immutable and sort of have to be created all at once. This does
not jive well wth being made incrementally in a comprehension. And, as
noted, the use of parentheses I too many contexts means that what looks like
a comprehension in parentheses is used instead as a generator.

If you really want a tuple made using a comprehension, you have some options
that are indirect.

One is to create a list using the comprehension and copy/convert that into a
tuple as in:

mytuple = tuple( [x for x in range(10) ] )

I think an alternative is to use a generator in a similar way that keeps
being iterated till done.

mytuple = tuple( (x for x in range(10) ) )

And similarly, you can use a set comprehension and convert that to a tuple
but only if nothing is repeated and perhaps order does not matter, albeit in
recent python versions, I think it remains ordered by insertion order!

mytuple = tuple( {x for x in range(10) } )

There are other more obscure and weird ways, of course but generally no
need.

Realistically, in many contexts, you do not have to store or use things in
tuples, albeit some sticklers think it is a good idea to use a tuple when
you want to make clear the data is to be immutable. There can be other
benefits such as storage space used. And in many ways, tuples are supposed
to be faster than lists.

-----Original Message-----
From: Python-list <python-list-bounces+avi.e.gross=gmail.com@python.org> On
Behalf Of Michael Torrie
Sent: Monday, February 20, 2023 10:57 PM
To: python-list@python.org
Subject: Re: Tuple Comprehension ???

On 2/20/23 20:36, Hen Hanna wrote:
> For a while, i've been curious about a [Tuple Comprehension]

I've never heard of a "Tuple comprehension." No such thing exists as far as
I know.

> So finally i tried it, and the result was a bit surprising...
>
>
> X= [ x for x in range(10) ]
> X= ( x for x in range(10) )
> print(X)
> a= list(X)
> print(a)

What was surprising? Don't keep us in suspense!

Using square brackets is a list comprehension. Using parenthesis creates a
generator expression. It is not a tuple. A generator expression can be
perhaps thought of as a lazy list. Instead of computing each member ahead
of time, it returns a generator object which, when iterated over, produces
the members one at a time. This can be a tremendous optimization in terms
of resource usage. See
https://docs.python.org/3/reference/expressions.html#generator-expressions.
Also you can search google for "generator expression" for other examples.

--
https://mail.python.org/mailman/listinfo/python-list

--
https://mail.python.org/mailman/listinfo/python-list
Re: Tuple Comprehension ??? [ In reply to ]
On 21/02/2023 04:13, Hen Hanna wrote:
>
> (A) print( max( * LisX ))
> (B) print( sum( * LisX )) <------- Bad syntax !!!
>
> What's most surprising is.... (A) is ok, and (B) is not.
>
> even tho' max() and sum() have (basically) the same syntax... ( takes one arg , whch is a list )
>
>
>
> i've been programming for many years... ( just knew to Python )

LOL, python is full of surprises. I'd definitely step into the same
piece of... Someday.

Of course 'Builtin functions' section explains that, but the
inconsistency is weird.

My response is absolutely useless, just two cents on the issue. Maybe
someone will fix that.

Axy.
--
https://mail.python.org/mailman/listinfo/python-list
RE: Tuple Comprehension ??? [ In reply to ]
There is a very common misunderstanding by people learning python that a
tuple has something to do with parentheses. It confused me too at first.

A tuple is made by the use of one or more commas and no parentheses are
needed except when, like everything else, they are used for grouping as in
the arithmetic for

(5 + 4) * 3

So (6) is not a tuple while a trailing comma makes (6,) to be a tuple with
one entry.

A tad confusingly is that () by itself is a tuple, containing nothing. While
(,) is a syntax error!

A serious design issue in most computer languages is that there are too few
unique symbols to go around and some get re-used in multiple ways that
usually are not ambiguous when viewed in context. As an example, sets and
dictionaries both use curly braces but {} by itself is considered ambiguous
and they chose to make it be an empty dictionary. To get an empty set, use
set() instead. Parentheses are way overused and thus it gets murky at times
as when they are used to sort of make it clear you are using a generator.

Consider how this fails without parentheses:

result = x*2 for x in [1,2,3]
SyntaxError: invalid syntax

But with parentheses works fine:

result = (x*2 for x in [1,2,3])
result
<generator object <genexpr> at 0x0000029A3CFCF030>

However if you want a generator that is expanded into a list, you do not
need the parentheses duplicated like this:

result = list( (x*2 for x in [1,2,3]) )

and can just use this without nested parentheses:

result = list( x*2 for x in [1,2,3] )

For completeness, you arguably should have a concept of a comprehension for
every possible case but the people at python chose not to for reasons like
the above and especially as it is fairly simple to use this version:

result = tuple( x*2 for x in [1,2,3] )

Yes, it is a tad indirect and requires making a generator first.






-----Original Message-----
From: Python-list <python-list-bounces+avi.e.gross=gmail.com@python.org> On
Behalf Of Hen Hanna
Sent: Monday, February 20, 2023 11:14 PM
To: python-list@python.org
Subject: Re: Tuple Comprehension ???

On Monday, February 20, 2023 at 7:57:14 PM UTC-8, Michael Torrie wrote:
> On 2/20/23 20:36, Hen Hanna wrote:
> > For a while, i've been curious about a [Tuple Comprehension]
> I've never heard of a "Tuple comprehension." No such thing exists as
> far as I know.
> > So finally i tried it, and the result was a bit surprising...
> >
> >
> > X= [ x for x in range(10) ]
> > X= ( x for x in range(10) )
> > print(X)
> > a= list(X)
> > print(a)


> What was surprising? Don't keep us in suspense!
>
> Using square brackets is a list comprehension. Using parenthesis
> creates a generator expression. It is not a tuple.

ok!



LisX= [x for x in range(10) ]

print( sum( LisX ))
print( max( LisX ))

print( sum( x for x in range(10) ) )
print( max( x for x in range(10) ) )

print( * LisX )

print( max( * LisX ))
print( sum( LisX )) # same as before
# print( sum( * LisX )) <------- Bad syntax !!!

TypeError: sum() takes at most 2 arguments (10 given)


_____________________

(A) print( max( * LisX ))
(B) print( sum( * LisX )) <------- Bad syntax !!!

What's most surprising is.... (A) is ok, and (B) is not.

even tho' max() and sum() have (basically) the same
syntax... ( takes one arg , whch is a list )



i've been programming for many years... ( just knew to Python )
--
https://mail.python.org/mailman/listinfo/python-list

--
https://mail.python.org/mailman/listinfo/python-list
Re: Tuple Comprehension ??? [ In reply to ]
Hen Hanna schreef op 21/02/2023 om 5:13:
> (A) print( max( * LisX ))
> (B) print( sum( * LisX )) <------- Bad syntax !!!
>
> What's most surprising is.... (A) is ok, and (B) is not.
>
> even tho' max() and sum() have (basically) the same syntax... ( takes one arg , whch is a list )
>
There's an important difference in syntax.

sum() takes an iterable:

sum(iterable, /, start=0)
    Return the sum of a 'start' value (default: 0) plus an iterable of
numbers

    When the iterable is empty, return the start value.
    This function is intended specifically for use with numeric values
and may
    reject non-numeric types.

max() on the other hand takes either an iterable or a number of
individual elements:

max(...)
    max(iterable, *[, default=obj, key=func]) -> value
    max(arg1, arg2, *args, *[, key=func]) -> value

    With a single iterable argument, return its biggest item. The
    default keyword-only argument specifies an object to return if
    the provided iterable is empty.
    With two or more arguments, return the largest argument.

That second form of max is why max(*some_list) works while
sum(*some_list) doesn't.

--
"You can fool some of the people all the time, and all of the people some
of the time, but you cannot fool all of the people all of the time."
-- Abraham Lincoln
"You can fool too many of the people too much of the time."
-- James Thurber

--
https://mail.python.org/mailman/listinfo/python-list
Re: Tuple Comprehension ??? [ In reply to ]
On Tuesday, February 21, 2023 at 9:33:29 AM UTC-8, Axy wrote:
> On 21/02/2023 04:13, Hen Hanna wrote:
> >
> > (A) print( max( * LisX ))
> > (B) print( sum( * LisX )) <------- Bad syntax !!!
> >
> > What's most surprising is.... (A) is ok, and (B) is not.
> >
> > even tho' max() and sum() have (basically) the same syntax... ( takes one arg , whch is a list )
> >
> >
> >
> > i've been programming for many years... ( just new to Python )



> LOL, python is full of surprises. I'd definitely step into the same
> piece of... Someday.
>
> Of course 'Builtin functions' section explains that, but the
> inconsistency is weird.
>
> My response is absolutely useless, just two cents on the issue. Maybe
> someone will fix that.
>
> Axy.


i'm glad you get it ( that the inconsistency is weird. )

(1) print(1, sum( [1,2,3,4] ))
(2) print(2, max( [1,2,3,4] ))

(3) print(3, sum( * [1,2,3,4] ))
(4) print(4, max( * [1,2,3,4] ))

both 3,4 should be good OR
both 3,4 should be bad. ------------ that's what i think!

ok.... i thnk i finally got it... (just before seeing Roel Schroeven's msg)

--
https://mail.python.org/mailman/listinfo/python-list
Re: Tuple Comprehension ??? [ In reply to ]
On 2/21/2023 12:32 PM, Axy via Python-list wrote:
> On 21/02/2023 04:13, Hen Hanna wrote:
>>
>>                  (A)   print( max( * LisX ))
>>                  (B)   print( sum( * LisX ))        <------- Bad
>> syntax !!!
>>
>> What's most surprising is....     (A)  is ok, and  (B) is not.
>>
>>             even tho'   max() and sum()  have   (basically)  the same
>> syntax...  ( takes one arg ,  whch is a list )

They **don't** have basically the same signature, though. max() takes
either an iterable or two or more numbers. Using max(*list_) presents
it with a series of numbers, so that's OK.

sum() takes just one iterable (plus an optional start index). Using
sum(*list_) presents it with a series of numbers, and that does not
match its signature.

Check what I said:

>>> help(sum)
Help on built-in function sum in module builtins:

sum(iterable, /, start=0)
Return the sum of a 'start' value (default: 0) plus an iterable of
numbers

>>> help(max)
Help on built-in function max in module builtins:

max(...)
max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value

Why they have different signatures may be lost to the whims of history
and backwards compatibility...

>>
>>
>>
>> i've been programming for many years...        ( just knew to Python )
>
> LOL, python is full of surprises. I'd definitely step into the same
> piece of... Someday.
>
> Of course 'Builtin functions' section explains that, but the
> inconsistency is weird.
>
> My response is absolutely useless, just two cents on the issue. Maybe
> someone will fix that.
>
> Axy.

--
https://mail.python.org/mailman/listinfo/python-list
RE: Tuple Comprehension ??? [ In reply to ]
There are limits to anyone arguing for designs to be the way they want or expect and Roel has explained this one below.

When it comes to designing a function, lots of rules people expect are beyond irrelevant. Many functions can be implemented truly hundreds of ways with varying numbers of arguments and defaults and other effects. I can make a function that raises the first argument to a power specified in the second argument with no defaults and you get a syntax error for calling it with one argument or more than two arguments. Or I can make the second argument use a keyword with a default of 1, or 2 or whatever I wish and it can now be called with one argument to get the default or two but not more. Or, I can have the function absorb all additional arguments and ignore them or even use them as additional powers to be raised to so pow(2, 3, 4, 5) returns a tuple or list of 8, 16, 32. Or maybe not and it would return ((2^3)^4)^5 or any other nonsense you design.

There IS NO CONSISTENCY possible in many cases unless you make a family of similarly named functions and add some thing to each name to make it clear.

Python arguably is harder than some languages in this regard as it allows way more flexibility. If a function accepts an iterator, and another does not, the call may superficially looks the same but is not.

So, yes, max() could have been designed differently and you can even design your own mymax() and mysum() to check the arguments they receive and re-arrange them in a way that lets you call the original max/sum functions potentially in the same ways.

But as a general rule, when using a function, don't GUESS what it does or infer what it does and then complain when someone says you should have read the manual. There are too many design choices, often done by different programmers and often motivated by ideas like efficiency. You likely can not guess many of them.

And lots of python functions you write can make use of all kinds of features such as caching results of previous computations or holding on to variables such as what you asked for last time so it can be used as a default. If I write a function like go(direction=something, distance=something) then perhaps my design will remember the last time it was invoked and if you call it again with no arguments, it may repeat the same action, or if only one is given, the other is repeated. But on a first call, it may fail as it has no memory yet of what you did. That may be intuitive to some and not others, but would it make as much sense for another function to be designed the same way so it tolerates being called with no arguments when this makes less sense? Do I often want to call for sin(x) and later for just sin() and expect it to mean that it be repeated?

But back to the original question about max/sum it gets weirder. Although max() takes any number of arguments, it really doesn't. There is no way to get the maximum of a single argument as in max(5) because it is designed to EITHER take one iterable OR more than one regular argument.

So one case that normally fails is max([]) or any empty iterable and you can keep it from failing with something like max([], default=0) .

In your own code, you may want to either design your own functions, or use them as documented or perhaps create your own wrapper functions that carefully examine what you ask them to do and re-arrange as needed to call the function(s) you want as needed or return their own values or better error messages. As a silly example, this fails:

max(1, "hello")

Max expects all arguments to be of compatible types. You could write your own function called charMax() that converts all arguments to be of type str before calling max() or maybe call max(... , key=mycompare) where compare as a function handles this case well.

The key point is that you need to adapt yourself to what some function you want to use offers, not expect the language to flip around at this point and start doing it your way and probably breaking many existing programs.

Yes, consistency is a good goal. Reality is a better goal.




-----Original Message-----
From: Python-list <python-list-bounces+avi.e.gross=gmail.com@python.org> On Behalf Of Roel Schroeven
Sent: Tuesday, February 21, 2023 1:11 PM
To: python-list@python.org
Subject: Re: Tuple Comprehension ???

Hen Hanna schreef op 21/02/2023 om 5:13:
> (A) print( max( * LisX ))
> (B) print( sum( * LisX )) <------- Bad syntax !!!
>
> What's most surprising is.... (A) is ok, and (B) is not.
>
> even tho' max() and sum() have (basically) the same syntax... ( takes one arg , whch is a list )
>
There's an important difference in syntax.

sum() takes an iterable:

sum(iterable, /, start=0)
Return the sum of a 'start' value (default: 0) plus an iterable of numbers

When the iterable is empty, return the start value.
This function is intended specifically for use with numeric values and may
reject non-numeric types.

max() on the other hand takes either an iterable or a number of individual elements:

max(...)
max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value

With a single iterable argument, return its biggest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the largest argument.

That second form of max is why max(*some_list) works while
sum(*some_list) doesn't.

--
"You can fool some of the people all the time, and all of the people some of the time, but you cannot fool all of the people all of the time."
-- Abraham Lincoln
"You can fool too many of the people too much of the time."
-- James Thurber

--
https://mail.python.org/mailman/listinfo/python-list

--
https://mail.python.org/mailman/listinfo/python-list
Re: Tuple Comprehension ??? [ In reply to ]
On 21/02/2023 19:11, avi.e.gross@gmail.com wrote:
> In your own code, you may want to either design your own functions, or use them as documented or perhaps create your own wrapper functions that carefully examine what you ask them to do and re-arrange as needed to call the function(s) you want as needed or return their own values or better error messages. As a silly example, this fails:
>
> max(1, "hello")
>
> Max expects all arguments to be of compatible types. You could write your own function called charMax() that converts all arguments to be of type str before calling max() or maybe call max(... , key=mycompare) where compare as a function handles this case well.
>
> The key point is that you need to adapt yourself to what some function you want to use offers, not expect the language to flip around at this point and start doing it your way and probably breaking many existing programs.
>
> Yes, consistency is a good goal. Reality is a better goal.

I don't think overengineering is a good thing. Good design utilizes
associativity so a person don't get amazed by inconsistency in things
that expected to be similar.

Axy.

--
https://mail.python.org/mailman/listinfo/python-list
RE: Tuple Comprehension ??? [ In reply to ]
Axy,

Nobody denies some of the many ways you can make a good design. But people
have different priorities that include not just conflicts between elements
of a design but also equally important factors like efficiency and deadlines
and not breaking too badly with the past.

You can easily enough design your own sub-language of sorts within the
python universe. Simply write your own module(s) and import them. Choose
brand new names for many of your functions or replace existing functions
carefully by figuring out which namespace a function is defined in, then
creating a new function with the same name that may call the old function
within it by explicitly referring to it.

SO if you want a new max() then either create an axy_max() or perhaps link
the original max to original_max and make your own max() that after playing
around internally, might call original_max.

Here is an example. Suppose you want the maximum value in a nested structure
like:

nested = [ 1, [2, 3, [4, 5], 6], 7]

This contains parts at several levels including an inner list containing yet
another inner list. using max(nested) will not work even if some intuition
wants it to work.

If you want your own version of max to be flexible enough to deal with this
case too, then you might find a flattener function or a function that
checks the depth of a structure such as a list or tuple, and apply it as
needed until you have arguments suitable to hand to original_max. Your max()
may end up being recursive as it keep peeling back one level and calling
itself on the results which may then peel back another level.

But the people who built aspects of python chose not to solve every single
case, however elegant that might be, and chose to solve several common cases
fairly efficiently.

What we often end up doing here is pseudo-religious discussions that rarely
get anywhere. It really is of no importance to discuss what SHOULD HAVE BEEN
for many scenarios albeit with some exceptions. Some errors must be slated
to be fixed or at least have more warnings in the documentation. And,
proposals for future changes to Python can be submitted and mostly will be
ignored.

What people often do not do is to ask a question that is more easy to deal
with. Asking WHY a feature is like it is can be a decent question. Asking
how to get around a feature such as whether there is some module out there
that implements it another way using some other function call, is another
good question. COMPLAINING about what has been done and used for a long time
is sometimes viewed differently and especially if it suggests people doing
this were stupid or even inconsistent.

Appealing to make-believe rules you choose to live your life by also tends
not to work. As you note, overengineering can cause even more problems than
a simple consistent design, albeit it can also create a rather boring and
useless product.

Too much of what happens under the table is hidden in python and if you
really study those details, you might see how a seemingly trivial task like
asking to create a new object of some class, can result in a cascade of code
being run that does things that themselves result in cascades of more code
as the object is assembled and modified using a weird number of searches and
executions for dunder methods in classes and metaclasses it is based on as
well as dealing with other objects/functions like descriptors and
decorators. Since our processors are faster, we might be able to afford a
design that does so much for you and we have garbage collection to deal with
the many bits and pieces created and abandoned in many processes. So our
higher level designs can often look fairly simple and even elegant but the
complexity now must be there somewhere.

I hate to bring up an analogy as my experience suggests people will take it
as meaning way more (or less) than I intend. Many languages, especially
early on, hated to fail. Heck, machines crashed. So an elegant design was
required to be overlaid with endless testing to avoid the darn errors.
Compilers had to try to catch them even earlier so you did not provide any
argument to a function that was not the same type. You had to explicitly
test for other things at run time to avoid dividing by zero or take the
square root of a negative number or see if a list was empty ...

Python allows or even often encourages a different paradigm where you throw
errors when needed but mainly just TRY something and be prepared to deal
with failure. It too is an elegant design but a very different one. And, you
can do BOTH. Heck, you can do many styles of programming as the language
keeps being extended. There is no one right way most of the time even if
someone once said there is.

So if the standard library provides one way to do something, it may not be
the only way and may not match what you want. Sometimes the fix for a
request is made by adding options like the default=value for max, and
sometimes by allowing the user to specify the comparison function to use
with another keyword argument. This literally lets you use max() to actually
easily calculate what min() does or do something entirely different like
find the longest word in a sentence or the shortest:

>>> words = "A sentence with sesquipedalian words like
disestablishmentarianism to measure length".split()
...
>>> words
...
['A', 'sentence', 'with', 'sesquipedalian', 'words', 'like',
'disestablishmentarianism', 'to', 'measure', 'length']

So the length can be measured by len() and this asks for the maximum:

>>> max(words, key=len)
...
'disestablishmentarianism'

The max() function here does not care and simply uses whatever key you
wanted.

Simply making a function that returns a negative of the length suffices to
make this act like min():

>>> def neglen(arg): return( - len(arg))
...
>>> neglen("hello")
-5
>>> max(words, key=neglen)
'A'

My point is that in one sense many standard library functions already have
had features added to them in ways that do not break existing programs and
they do allow fairly interesting problems to be solved. But sometimes such
features may mean either not accepting some formats as arguments that
another function supports, or perhaps needing to try to change lots of
functions at the same time to remain in sync. That is clearly often not
possible or may be highly expensive or it may simply be done more gradually
over many releases. I believe extending list comprehension or generators of
that sort to also do sets and dictionaries may have been done incrementally
even if today it seems like a unified set of things done in the same general
way.

So my PERSONAL view is that when looking at issues, we need to NOT find a
single principle and then insist it is the one and only principle to guide
us. We need to evaluate multiple ideas and not rule out any prematurely. We
then need to weigh the ideas in specific cases and see if anything dominates
or if perhaps a few in proper combination are a reasonable compromise. So I
submit as far as compilers and interpreters go, they would "love" really
short variable names and not see oodles of whitespace that just is there to
be ignored, as well as comments to ignore. But generally, we don't care
because what we focus on is the experience of human programmers and the ones
who read or write or maintain the code. For them, we want generally
variables long enough to hold some amount of meaningfulness. Similarly, many
other features are a tradeoff and although it is likely far cheaper to
write:

deeply.nested.object.fieldname = 0 if deeply.nested.object.fieldname > 9

As more like:

short = deeply.nested.object.fieldname
short = 0 if short > 9

It makes sense to allow the programmer to choose either way and either have
a compiler or interpreter optimize code with regions like this that keep
referring to nested areas, or pay the costs to keep re-traversing it even if
it takes a millionth of a second longer.

Now if I am designing something under my own control, boy do I take short
cuts when prototyping and ignore lots of "rules" as I build the skeleton. If
I am somewhat satisfied, I might start applying many of the principles I use
including adding lots of comments, naming variables in more meaningful ways,
perhaps refactoring for more efficiency, changing the code to handle special
cases, setting up to detect some errors and catch them and do something
appropriate, write a manual page and so on. Lots of things are then
important but not so much in early stages.

Python is a work in progress built by lots of people and I am regularly
amazed at how well much of it hangs together despite some areas where it is
a tad inconsistent. But I am pretty sure many of the inconsistencies have
been handled in ways that are not seen unless you search. There must be
other implementations for functions that handle edge cases or that try many
ways and return the ones that do not generate errors.

Do you prefer languages that pretty much either require you to create many
functions for each number and type of arguments like
max(int, int) -> int
max(int, int, int) -> int
max(int, int, int, int) -> int
max(int, double) -> double
...
max(int, double, uint16) -> double

And so on? I know languages that allow you to write frameworks and then
internally create dozens of such variants to use as needed at compile time.
Each function can be optimized but so many functions may have their own
expenses.

But a language like python takes a different tack and one function alone can
do so many things with ease including handling any number of arguments,
defaults for some, many kinds of arguments and especially those that are
either compatible or implement some protocol, and so on. When you view
things that way, the design of max() and sum() may well make quite a bit
more sense and also why they are not identically designed.



-----Original Message-----
From: Python-list <python-list-bounces+avi.e.gross=gmail.com@python.org> On
Behalf Of Axy via Python-list
Sent: Tuesday, February 21, 2023 2:37 PM
To: python-list@python.org
Subject: Re: Tuple Comprehension ???

On 21/02/2023 19:11, avi.e.gross@gmail.com wrote:
> In your own code, you may want to either design your own functions, or use
them as documented or perhaps create your own wrapper functions that
carefully examine what you ask them to do and re-arrange as needed to call
the function(s) you want as needed or return their own values or better
error messages. As a silly example, this fails:
>
> max(1, "hello")
>
> Max expects all arguments to be of compatible types. You could write your
own function called charMax() that converts all arguments to be of type str
before calling max() or maybe call max(... , key=mycompare) where compare as
a function handles this case well.
>
> The key point is that you need to adapt yourself to what some function you
want to use offers, not expect the language to flip around at this point and
start doing it your way and probably breaking many existing programs.
>
> Yes, consistency is a good goal. Reality is a better goal.

I don't think overengineering is a good thing. Good design utilizes
associativity so a person don't get amazed by inconsistency in things that
expected to be similar.

Axy.

--
https://mail.python.org/mailman/listinfo/python-list

--
https://mail.python.org/mailman/listinfo/python-list
Re: Tuple Comprehension ??? [ In reply to ]
On Tuesday, February 21, 2023 at 10:39:54 AM UTC-8, Thomas Passin wrote:
> On 2/21/2023 12:32 PM, Axy via Python-list wrote:
> > On 21/02/2023 04:13, Hen Hanna wrote:
> >>
> >> (A) print( max( * LisX ))
> >> (B) print( sum( * LisX )) <------- Bad
> >> syntax !!!
> >>
> >> What's most surprising is.... (A) is ok, and (B) is not.
> >>
> >> even tho' max() and sum() have (basically) the same
> >> syntax... ( takes one arg , whch is a list )
> They **don't** have basically the same signature, though. max() takes
> either an iterable or two or more numbers. Using max(*list_) presents
> it with a series of numbers, so that's OK.
>
> sum() takes just one iterable (plus an optional start index). Using
> sum(*list_) presents it with a series of numbers, and that does not
> match its signature.
>
> Check what I said:
>
> >>> help(sum)
> Help on built-in function sum in module builtins:
> sum(iterable, /, start=0)

> >>> help(max)

thakns... i like the use of the word [signature]


thanks for all the commetns... i'll try to catch up later.


i think i understand it much better now.

regular Python (func-calling) notation is like CL (Common Lisp) funcall.

and fun( * args ) notation is like a (compile-time) macro


( max( * X )) ----macroexpand---> (apply max X)

( max( * [1,2,3,4] )) ----macroexpand---> (apply max '(1 2 3 4) )

and
Max() can take many arguments, but
Sum() can basically take only 1.


--
https://mail.python.org/mailman/listinfo/python-list
Re: Tuple Comprehension ??? [ In reply to ]
On 2/21/2023 8:52 PM, Hen Hanna wrote:
> On Tuesday, February 21, 2023 at 10:39:54 AM UTC-8, Thomas Passin wrote:
>> On 2/21/2023 12:32 PM, Axy via Python-list wrote:
>>> On 21/02/2023 04:13, Hen Hanna wrote:
>>>>
>>>> (A) print( max( * LisX ))
>>>> (B) print( sum( * LisX )) <------- Bad
>>>> syntax !!!
>>>>
>>>> What's most surprising is.... (A) is ok, and (B) is not.
>>>>
>>>> even tho' max() and sum() have (basically) the same
>>>> syntax... ( takes one arg , whch is a list )
>> They **don't** have basically the same signature, though. max() takes
>> either an iterable or two or more numbers. Using max(*list_) presents
>> it with a series of numbers, so that's OK.
>>
>> sum() takes just one iterable (plus an optional start index). Using
>> sum(*list_) presents it with a series of numbers, and that does not
>> match its signature.
>>
>> Check what I said:
>>
>>>>> help(sum)
>> Help on built-in function sum in module builtins:
>> sum(iterable, /, start=0)
>
>>>>> help(max)
>
> thakns... i like the use of the word [signature]
>
>
> thanks for all the commetns... i'll try to catch up later.
>
>
> i think i understand it much better now.
>
> regular Python (func-calling) notation is like CL (Common Lisp) funcall.
>
> and fun( * args ) notation is like a (compile-time) macro
>
>
> ( max( * X )) ----macroexpand---> (apply max X)
>
> ( max( * [1,2,3,4] )) ----macroexpand---> (apply max '(1 2 3 4) )
>
> and
> Max() can take many arguments, but
> Sum() can basically take only 1.
... and that one has to be an iterable.

--
https://mail.python.org/mailman/listinfo/python-list
RE: Tuple Comprehension ??? [ In reply to ]
HH,

Just FYI, as a seeming newcomer to Python, there is a forum that may fit
some of your questions better as it is for sort of tutoring and related
purposes:

https://mail.python.org/mailman/listinfo/tutor

I am not discouraging you from posting here, just maybe not to overwhelm
this group with many questions and especially very basic ones.

Your choice.

But what I read below seems like an attempt by you to look for a cognate to
a python feature in a LISP dialect. There may be one but in many ways the
languages differ quite a bit.

As I see it, using the asterisk the way you tried is not all that common and
you are sometimes using it where it is not needed. Python is NOT a language
that is strongly typed so there is no need to take a list or other iterator
or container of numbers and fool the interpreter into making it look like
you placed them as multiple arguments. In many places, just handing over a
list is fine and it is expanded and used as needed.

When a function like sum() or max() is happy to take a single list argument,
then feed it the list, not *list.

Where it can be helpful is a function like range() where range() takes up to
three arguments as in:

>>> list(range(7))
[0, 1, 2, 3, 4, 5, 6]
>>> list(range(11, 20))
[11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> list(range(31, 45, 3))
[31, 34, 37, 40, 43]

In the above, you are really asking for stop=num, or start/stop or
start/stop/step.

But what if you have some data structure like a list that contains [31, 45,
3] or just a two number version or a single number and it is sitting in a
variable. You can ask Python to unpack all kinds of things in various ways
and the asterisk is one simple one. Unpacking is itself a huge topic I will
not discuss.

>>> mylist = [31, 45, 3]
>>> list(range(mylist))
Traceback (most recent call last):
File "<pyshell#57>", line 1, in <module>
list(range(mylist))
TypeError: 'list' object cannot be interpreted as an integer
>>> list(range(*mylist))
[31, 34, 37, 40, 43]

Range() takes only something like integers. So you use the * in this
context to give it three integers individually.

Range does not take named arguments but many other functions do. So what if
I have an arbitrary function that accepts arguments like
myfunc(alpha=default, beta=default, gamma=default) where the defaults are
not the issue and may be anything such as "" or 0 or an empty set.

I could write code like this that creates a toy version of the function and
create a dictionary that supplies any combination of the required arguments
and use not the * operator that expands something like a list, but the
doubled ** operator that expands all entries of a dictionary into individual
items:

>>> def myfunc(alpha=1, beta="", gamma=""):
... print(f"alpha={alpha}, beta={beta}, gamma={gamma}")
...
...
>>> myfunc()
alpha=1, beta=, gamma=
>>> myfunc(1, 2, 3)
alpha=1, beta=2, gamma=3

>>> mydict = { "alpha" : 101, "beta" : "hello", "gamma" : "buy bye" }
>>> mydict
{'alpha': 101, 'beta': 'hello', 'gamma': 'buy bye'}

>>> myfunc( **mydict )
alpha=101, beta=hello, gamma=buy bye

I have no idea if any of this is really like your macroexpand. It does not
need to be. It is what it is. If you went back to a language like C, their
macros might be used to make a "constant with "#define" but they would not
be a constant in the same way as a language that uses a keyword that makes a
variable name into a constant that cannot be changed without causing an
error. Similar but also not the same.

This is only the surface of some things commonly done in python when it
makes sense. But often there is no need and your examples are a good example
when the function happily take a list in the first place. So why fight it
especially when your expanded version is not happily received?

The takeaway is that you need to read a bit more of a textbook approach that
explains things and not use slightly more advanced features blindly. It is
NOT that sum() takes a single argument that matters. It fails on something
like sum(1) which is a single argument as well as sum("nonsense") and so on.
What sum takes is a wide variety of things in python which implement what it
takes to be considered an iterable. And it takes exactly one of them under
the current design.

sum((1,))
1
sum([1])
1
sum(n- 5 for n in range(10,15))
35

All kinds of things work. Tuples and lists are merely the easiest to see.
The latter exmple is a generator that returns 5 less than whatever range()
produces as another kind of iterable. The sum() function will not take two
or more things, iterable or not. So the first below fails and the second
does not:

>>> sum([1, 2], [3,4])
Traceback (most recent call last):
File "<pyshell#94>", line 1, in <module>
sum([1, 2], [3,4])
TypeError: can only concatenate list (not "int") to list

>>> sum([1, 2] + [3,4])
10

Why? Because the plus sign asked the lists to combine into one larger list.
The sum function is only called after python has combined the lists into one
with no name.

Now you can reasonably ask how to add a bunch of numbers, no lists or other
gimmicks? Here is a short but working version:

>>> def multiSum(*args): return sum(list(args))
...

>>> multiSum(1, 2, 3, 4 , 5)
15

Note I used an asterisk with a quite different meaning there in another sort
of mini language you use to explain what you want function arguments to be
seen as. *args there does not mean to do the expansion but the opposite and
collect them into a single argument I can then make into a list. But guess
what? args is already a list so this works just as well and since sum wants
something like a list, it gets it:

>>> def multiSum(*args): return sum(args)
...
>>> multiSum(1, 2, 3, 4 , 5)
15

So although you can slow yourself down by trying to see how Python is like
some other language, consider almost forgetting everything else you know and
learn what python IS just for itself. I learned python after so many other
languages that I would never have had time to master it if I kept comparing
it to all the others.

Having said that, many programming languages tend to converge in some ways
and features in one are often adapted to or from another. There can be
cognates. But my advice remains to learn what is expected from a function
before using it and then you can adjust what you give it to match allowed
configurations. Many languages have such needs and some of them do things
differently. As an example, a language like R allows you to say
do.call(func, list) which goes and calls func() with the expanded arguments
taken from the list. Different idea for a related goal.

Good luck. Learning can be mental fun when you have the fundamentals.

-----Original Message-----
From: Python-list <python-list-bounces+avi.e.gross=gmail.com@python.org> On
Behalf Of Hen Hanna
Sent: Tuesday, February 21, 2023 8:52 PM
To: python-list@python.org
Subject: Re: Tuple Comprehension ???

On Tuesday, February 21, 2023 at 10:39:54 AM UTC-8, Thomas Passin wrote:
> On 2/21/2023 12:32 PM, Axy via Python-list wrote:
> > On 21/02/2023 04:13, Hen Hanna wrote:
> >>
> >> (A) print( max( * LisX ))
> >> (B) print( sum( * LisX )) <------- Bad
> >> syntax !!!
> >>
> >> What's most surprising is.... (A) is ok, and (B) is not.
> >>
> >> even tho' max() and sum() have (basically) the same
> >> syntax... ( takes one arg , whch is a list )
> They **don't** have basically the same signature, though. max() takes
> either an iterable or two or more numbers. Using max(*list_) presents
> it with a series of numbers, so that's OK.
>
> sum() takes just one iterable (plus an optional start index). Using
> sum(*list_) presents it with a series of numbers, and that does not
> match its signature.
>
> Check what I said:
>
> >>> help(sum)
> Help on built-in function sum in module builtins:
> sum(iterable, /, start=0)

> >>> help(max)

thakns... i like the use of the word [signature]


thanks for all the commetns... i'll try to catch up later.


i think i understand it much better now.

regular Python (func-calling) notation is like CL (Common Lisp)
funcall.

and fun( * args ) notation is like a (compile-time) macro


( max( * X )) ----macroexpand---> (apply max X)

( max( * [1,2,3,4] )) ----macroexpand---> (apply max '(1
2 3 4) )

and
Max() can take many arguments, but
Sum() can basically take only 1.


--
https://mail.python.org/mailman/listinfo/python-list

--
https://mail.python.org/mailman/listinfo/python-list