• Tuple Comprehension ???

    From Hen Hanna@21:1/5 to All on Mon Feb 20 19:36:15 2023
    For a while, i've been curious about a [Tuple Comprehension]

    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)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Michael Torrie@21:1/5 to Hen Hanna on Mon Feb 20 20:56:46 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Hen Hanna@21:1/5 to Michael Torrie on Mon Feb 20 20:13:59 2023
    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 )

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From avi.e.gross@gmail.com@21:1/5 to Hen Hanna on Tue Feb 21 01:23:31 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Cameron Simpson@21:1/5 to Hen Hanna on Tue Feb 21 17:51:50 2023
    On 20Feb2023 19:36, Hen Hanna <henhanna@gmail.com> wrote:
    For a while, i've been curious about a [Tuple Comprehension]

    So finally i tried it, and the result was a bit surprising...

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

    This is a list comprehension, resulting in a list as its result.

    X= ( x for x in range(10) )

    This is not a tuple comprehension - Python does not have one.

    Instead, it is a generator expression:

    x for x in range(10)

    inside some brackets, which are just group as you might find in an
    expression like:

    (3 + 4) * 7

    If you want a tuple, you need to write:

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

    which makes a tuple from an iterable (such as a list, but anything
    iterable will do). Here the iterable is the generator expression:

    x for x in range(10)

    Cheers,
    Cameron Simpson <cs@cskk.id.au>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Axy@21:1/5 to Hen Hanna on Tue Feb 21 17:32:48 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Roel Schroeven@21:1/5 to Hen Hanna on Tue Feb 21 19:11:07 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From avi.e.gross@gmail.com@21:1/5 to Michael Torrie on Tue Feb 21 12:41:07 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Hen Hanna@21:1/5 to Axy on Tue Feb 21 10:18:48 2023
    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)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Thomas Passin@21:1/5 to Axy via Python-list on Tue Feb 21 13:22:37 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From avi.e.gross@gmail.com@21:1/5 to Hen Hanna on Tue Feb 21 14:11:38 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Axy@21:1/5 to avi.e.gross@gmail.com on Tue Feb 21 19:37:22 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From avi.e.gross@gmail.com@21:1/5 to avi.e.gross@gmail.com on Tue Feb 21 16:09:04 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Hen Hanna@21:1/5 to Thomas Passin on Tue Feb 21 17:52:21 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Thomas Passin@21:1/5 to Hen Hanna on Tue Feb 21 21:57:07 2023
    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.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From avi.e.gross@gmail.com@21:1/5 to Thomas Passin on Tue Feb 21 23:08:53 2023
    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

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)