Implied List Works But Not Explicit

Hi,

let’s say I have the following lists:

firstList = [1,2,3,4,5]
secondList = [6,7,8,9,0]

If I pass the first list into the following function as an argument, it will ‘work’; no exception is raised.

def myzip(*seqs):
    seqs = [list(S) for S in seqs]  # <---- test this outside as standalone
    res = []
    while all(seqs):
        res.append(tuple(S.pop(0) for S in seqs))
    return res

result_one = myzip(firstList)  # No exception is raised

However, if I attempt the following:

result_one =  [list(S) for S in firstList]  # Exception raised: TypeError: 'int' object is not iterable

If I try two lists as an argument, then no exception is raised.

combinedList = firstList, secondList
result_two =  [list(S) for S in combinedList]

Can someone reconcile this please. That is, the list comprehension works if implied via a function
argument but does not work when explicit when only one list is passed in.

Did you try to check the value of seqs before the rest of the function executes? If you do, it should be immediately clear why the code inside the function gives a different result in the single-input case.

(As another hint: what happens if you try combinedList = firstList, instead of combinedList = firstList, secondList - note the trailing comma?)

2 Likes

Hi,

yes, I have.

Here is the test code:

def myzip(*seqs):
    seqs = [list(S) for S in seqs]
    print('The value of seqs: ', seqs)
    res = []
    while all(seqs):
        res.append(tuple(S.pop(0) for S in seqs))
    return

g = myzip([1,2,3,4])
The value of seqs:  [[1, 2, 3, 4]]
seqs = [list(S) for S in [1,2,3,4]]
Traceback (most recent call last):
  File "<pyshell#56>", line 1, in <module>
    seqs = [list(S) for S in [1,2,3,4]]
TypeError: 'int' object is not iterable

I added the ‘trailing comma’ as you stated. Yes, by adding the trailing comma, an issue is not encountered:

oneList = [1,2,3,4],
seqs = [list(S) for S in oneList]
seqs
[[1, 2, 3, 4]]

So, is the takeaway here then that when a parameter list of a function includes an asterisk (*), a trailing comma is automatically added?

def abc(*oneParameter):
    print(oneParameter)

abc([1,2,3,4])  # Test result
([1, 2, 3, 4],)

Note the double brackets in your printed output: seqs is a list of lists. Your list comprehension is iterating over a single list of ints, so you get the TypeError.

If you print seqs before the conversion, you’ll see that it’s a tuple of lists, not a single list.

1 Like

That’s a strange way to think about it–it’s not about adding a secret comma, it’s about the type.

The trailing comma in Karl’s version changes the expression from a list to a tuple with the list as its only element. *args is always a tuple, even if you only pass one element (or zero!).

3 Likes

Yes, the ‘*’ converts an argument into a tuple.

I think I got it now. So, because the ‘*’ converts the passed in argument into a tuple, the list
itself becomes an element/value of a tuple, the list being an iterable object. Whereas if I just
pass in a list, a value of the list becomes the value of S for every iteration. These values being
integers, are not iterable objects and thus fails, raising an exception. Since the list built-in keyword
statement only accepts iterable objects as arguments.

Thank you guys (jamestwebber and kknechtel), for the help and helping me understand the issue. Much obliged.

1 Like

I would strongly recommend not thinking about it this way. This may seem like a nitpick, but getting the meaning correct now will save you time later.

The leading * means “any number of arguments may be passed, separated by commas, and they will be bundled into a tuple with the following name”.
*args means “however many arguments are given, turn them into a tuple named args”.

There’s a very dramatic difference in the signature of a function which takes a starred argument and one which does not.

3 Likes

Hi,

yes, I understand the definition of *args. I was replying to our specific conversation regarding this specific issue. The issue being the discrepancy between the list comprehension inside the function accepting a list implicitly by way of an argument of the function and passing a list explicitly to this list comprehension. As noted in a previous post, the error was in passing an integer to the list built-in statement which only accepts iterable objects as arguments. This is the reason I was getting the following error: TypeError: ‘int’ object is not iterable.

From Learning Python, 5th Edition, Chapter 18: Arguments:

 def f(*args): print(args)

“* and **, are designed to support functions that take any number of arguments., …, When this function is called, Python collects all the positional arguments into a new tuple and assigns the variable args to that tuple.”

1 Like

But this is not what happened. The list comprehension did not get a list, implicitly or explicitly. It got a tuple, because it was given the input seqs which is the tuple of arguments.

Inside the function, you have a comprehension over a tuple of lists. Outside the function, you have a comprehension over a list of integers.

This might seem like a pedantic correction but precision matters if you want to understand what the language is doing.

3 Likes

Yes, what I meant, as per my previous post is that the list passed in as an argument to the function becomes a value of the tuple as per the *args argument.

def my_handle(*args):
     return args

>>> tuple_arguments = my_handle([1,2,3,4], [5,6,7,8], [44,55,66,77])
>>> list(enumerate(tuple_arguments))  
>>> [(0, [1, 2, 3, 4]), (1, [5, 6, 7, 8]), (2, [44, 55, 66, 77])] 
>>> tuple_arguments
>>> ([1, 2, 3, 4], [5, 6, 7, 8], [44, 55, 66, 77])  # Tuple of lists

Since per theory, Python by way of the *args asterisk-based parameter:

Python collects all the positional arguments into a new tuple

From my previous post:
…, the list itself becomes an element / value of a tuple, …