Tuple Comprehension ???

avi.e.gross at gmail.com avi.e.gross at gmail.com
Tue Feb 21 23:08:53 EST 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 at python.org> On
Behalf Of Hen Hanna
Sent: Tuesday, February 21, 2023 8:52 PM
To: python-list at 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



More information about the Python-list mailing list