Studying "starred expressions" in Python

Author: Bruce W. Lowther

licensed under the GNU General Public License v2.0

Starting with list of strings and a list of numbers. Both equal length.


In [ ]:
exlst = ["This", "That", "these", "those", "wit"]

exlsv = [8,10,9,4,20]

Assignment operator allows each item from the list exlst to be assigned into a variable.


In [ ]:
A,B,C,D,E = exlst
A

But how often are you going to know exactly how many items are in a list when you need to assign to a variable?

For example, this produces an error because the number of variables on the left side of the assignment does not equal the number of items in the list. (Capturing exception with try block to add some clarity.)


In [ ]:
try:
    A,B,C,D = exlst
except Exception as X:
    print("whoopsie: Exception Type:[{0}] Text:[{1}]".format(type(X),X))

Starred expression

Starred expression tells python to keep all other list items together into a paramater. I think of this as "and all the remaining stuff goes here".


In [ ]:
X, *Y = exlst

In [ ]:
print("X:[ {} ]".format(X))
print("Y:[ {} ]".format(Y))

Ok, can we do this with parameters in a function? Can we pass in a list of many items into a function to perform some operation? First attempt:


In [ ]:
def foo(v1, v2):
    print("v1:[ {} ]".format(v1))
    print("v2:[ {} ]".format(v2))

In [ ]:
try:
    foo(*exlst)
except Exception as X:
    print("whoopsie: Exception Type:[{0}] Text:[{1}]".format(type(X),X))

No, the definition of the function requires that there are two and only two parameters.

Star on function definition parameter

If I add a star to the parameter, then I am specifying that there may be one or two parameters.


In [ ]:
def foo(v1, *v2):
    print("v1:[ {} ]".format(v1))
    print("v2:[ {} ]".format(v2))

In [ ]:
foo(exlst)

Well, that's not quite what I wanted. I wanted the first item from the list in v1 and the remaining items in the list to go into v2...

Star on the function parameter call

So now we use the star on the input parameter to specify that it will split the parameters into 1 and remaining.


In [ ]:
foo(*exlst)

The * on the parameter function specifies that there will be one or two parameters. The * on the function call split the list into two parameters -- first and remaining.

Stars and recursion

Ok, now so I can create recursion to iterate through all items in a list using a star on the second parameter of the function AND using a star in the input parameter of the function call.


In [ ]:
def foo(v1, *v2):
        print("v1:[ {} ]".format(v1))
        #Exit case.  Nothing left in v2 to pass into foo again.
        if(len(v2) > 0):
            foo(*v2)

In [ ]:
foo(*exlst)

zip example

Ok, now let's try what we've learned with star on the ZIP function


In [ ]:
exlst = ["This", "That", "these", "those", "wit"]

exlsv = [8,10,9,4,20]

What I have are two collections exlst and exlsv. What I want is one collection where the first element of exlst is paired with the first element of exlsv

This is where zip comes in. https://docs.python.org/3/library/functions.html#zip


In [ ]:
zip(exlst,exlsv)

Huh? Oh, it's not performing the operation immediately. zip is returning an iterator which is an object that will step through and provide the results when iterated with next.


In [ ]:
zf = zip(exlst,exlsv)

NOTE iterating over a zip consumes the zip. The first time we ask for a list from a zip. It returns zipped results, however subsequent calls will return no results.


In [ ]:
next(zf)

In [ ]:
next(zf)

In [ ]:
list(zf)

so you have to be careful with iterators that you don't use them up before you expect to. You could create a copy, but that forces all the calculation over the list to happen at once... which could be a problem if it's an infinite collection. :o


In [ ]:
zflc = list(zip(exlst,exlsv)).copy()
zflc

Now zflc is a fully realized list of tuples where each tuple is the corresponding values from exlst and exlsv.

Starred Expression in Zip function

The big reveal, how does this all relate back to star expressions?


In [ ]:
#returns an iterator zipping up two collections
zf = zip(exlst,exlsv)
#returns lists unzipping a collection into result collections
c1,c2 = zip(*zf)

In [ ]:
c1

In [ ]:
c2

Summary

zip() and zip(*) operations are complementary. One reverses the operation of the other.