Stars
Star Expression in Assignment
What will this do?
>>> numbers = [2, 1, 3, 4]
>>> first, second, rest = numbers
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
How could we get all the remaining values after the first one into a new list?
Here’s one way to do this:
>>> numbers = [2, 1, 3, 4]
>>> first, second, rest = numbers[0], numbers[1], numbers[1:]
>>> first
2
>>> second
1
>>> rest
[1, 3, 4]
Another way:
>>> numbers = [2, 1, 3, 4]
>>> (first, second), rest = numbers[:2], numbers[2:]
Or we can use a * expression during tuple unpacking to capture remaining values into a list:
>>> numbers = [2, 1, 3, 4]
>>> first, second, *rest = numbers
>>> first
2
>>> second
1
>>> rest
[3, 4]
We can use this anywhere in an unpacking assignment:
>>> first, *middle, last = numbers
>>> first
2
>>> middle
[1, 3]
>>> last
4
>>> *rest, last = numbers
>>> rest
[2, 1, 3]
>>> last
4
We cannot use two * operators in the same assignment though because that would be ambiguous:
>>> a, *b, c, *d = numbers
Traceback (most recent call last):
File "<stdin>", line 1
SyntaxError: two starred expressions in assignment
Stars in List Literals
The other side of the * operator is for unpacking iterables.
The * operator can also be used in list, set, and tuple literals:
>>> numbers = [2, 1, 3, 4]
>>> first, second, *rest = numbers
>>> (0, first, *rest, 9)
(0, 2, 3, 4, 9)
>>> rearranged = [*rest, second, first]
>>> rearranged
[3, 4, 1, 2]
>>> set(numbers) == {*numbers}
True
Argument Unpacking
This * unpacking also works for unpacking iterables into positional arguments when calling functions too:
>>> strings = ["hello", "world"]
>>> print(*strings)
hello world
>>> strings = ["hello", "world"]
>>> print(*strings, sep='\n')
hello
world
>>> print("hi", "there", *strings)
hi there hello world
>>> print(*strings, "hi")
hello world hi
The print function accepts any number of arguments.
The zip function also accepts any number of arguments:
>>> matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> list(zip(*matrix))
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
Star in Function Arguments
The * unpacking into function arguments has a flip side as well: the * operator can be used for capturing all positional arguments specified to a function.
This allows us to define functions which accept any number of positional arguments:
>>> def print_all_args(*args):
... for arg in args:
... print(arg)
...
>>> print_all_args("hello", "world")
hello
world
>>> print_all_args()
>>> print_all_args("hello", "there", "world")
hello
there
world
This can be used with any number of positional arguments:
>>> def greet_all(greeting, *names):
... for name in names:
... print(f"{greeting} {name}")
...
>>> greet_all("Hello", "Trey", "Diane")
Hello Trey
Hello Diane
>>> greet_all("Hiya", "Peter", "Gerry", "Trey")
Hiya Peter
Hiya Gerry
Hiya Trey
Keyword-Only Arguments
Any arguments after the * must be given as keyword arguments:
>>> def greet_all(*names, greeting):
... for name in names:
... print(f"{greeting} {name}!")
...
>>> greet_all("Hello", "Trey")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: greet_all() missing 1 required keyword-only argument: 'greeting'
>>> greet_all("Trey", "Gerry", greeting="Hello")
Hello Trey!
Hello Gerry!
Arguments after any * expression in a function definition can be thought of as keyword-only arguments.
A lone * can be used to require specific arguments to be keyword-only arguments (without also capturing any remaining positional arguments):
def with_previous(iterable, *, fillvalue=None):
items = []
previous = fillvalue
for item in iterable:
items.append((item, previous))
previous = item
return items
Keyword Arguments
The * operator works for packing and unpacking positional arguments. The ** operator works the same way for keyword arguments.
We can use this operator to capture all keyword arguments passed to a function:
>>> def print_words(**kwargs):
... for word, count in kwargs.items():
... print(" ".join([word] * count))
...
>>> print_words(hello=5, world=3)
hello hello hello hello hello
world world world
We can use positional arguments at the same time, but they can only be used before the keyword arguments:
>>> def example(a, **kw):
... print(a)
... print(kw)
...
>>> example(4, hello="world")
4
{'hello': 'world'}
>>> def example(*args, **kwargs):
... print(args)
... print(kwargs)
...
>>> example(4, 5, multiple="arguments", hello="world")
(4, 5)
{'multiple': 'arguments', 'hello': 'world'}
The ** operator can be used for taking a dictionary and unpacking it for use as keyword arguments:
>>> words = {'hello': 3, 'world': 5}
>>> words
{'hello': 3, 'world': 5}
>>> print_words(**words)
hello hello hello
world world world world world
Just as * operator works for unpacking iterables into list/set/tuple literals, the ** operator works for unpacking dictionaries (and other “mappings”) into dictionary literals:
>>> user = {'name': "Trey", 'website': "https://treyhunner.com"}
>>> defaults = {'name': "Anonymous User", 'page_name': "Profile Page"}
>>> merged = {**user, **defaults}
>>> merged
{'name': 'Anonymous User', 'website': 'https://treyhunner.com', 'page_name': 'Profile Page'}
Argument Unpacking Exercises
Multi-valued Dictionary
This is the dict_from_tuple exercise in dictionaries.py. Edit the dictionaries.py file in the exercises directory to implement this exercise. To test it, run python test.py dict_from_tuple in your exercises directory.
Edit the function dict_from_tuple and modify it to accept a list of tuples of any length and return a dictionary which uses the first item of each tuple as keys and all subsequent items as values.
Note
This exercise is different from (but similar to) dict_from_truple, which you may have seen earlier.
Example usage:
>>> from dictionaries import dict_from_tuple
>>> dict_from_tuple([(1, 2, 3, 4), (5, 6, 7, 8)])
{1: (2, 3, 4), 5: (6, 7, 8)}
>>> dict_from_tuple([(1, 2, 3), (4, 5, 6), (7, 8, 9)])
{1: (2, 3), 4: (5, 6), 7: (8, 9)}
Transpose
This is the transpose exercise in stars.py. Edit the stars.py file in the exercises directory to implement this exercise. To test it, run python test.py transpose in your exercises directory.
Make a function transpose that accepts a list of lists and returns the transpose of the list of lists.
Example usage:
>>> from stars import transpose
>>> transpose([[1, 2], [3, 4]])
[[1, 3], [2, 4]]
>>> matrix = [['a','b','c'],['d','e','f'],['g','h','i']]
>>> transpose(matrix)
[['a', 'd', 'g'], ['b', 'e', 'h'], ['c', 'f', 'i']]
Sum Each
Edit the sum_each function in the stars.py file so that it accepts any number of tuples of numbers and returns tuple of the sums of numbers in corresponding positions (sum of all first items, sum of all second items, etc.)
Example usage:
>>> from stars import sum_each
>>> sum_each((1, 2), (4, 5))
(5, 7)
>>> sum_each((1, 2), (4, 5), (7, 8), (1, 1))
(13, 16)
>>> sum_each((1, 2, 3), (4, 5, 6), (7, 8, 9), (1, 1, 0))
(13, 16, 18)
HTML Tag
This is the html_tag exercise in stars.py. Edit the stars.py file in the exercises directory to implement this exercise. To test it, run python test.py html_tag in your exercises directory.
Make a function that accepts a positional argument and keyword arguments and generates an HTML tag using them.
Example:
>>> from stars import html_tag
>>> html_tag("input", type="email", name="email", placeholder="E-mail address")
'<input type="email" name="email" placeholder="E-mail address">'
>>> img_tag = html_tag("img", src="https://placehold.it/10x10", alt="Placeholder")
>>> img_tag
'<img src="https://placehold.it/10x10" alt="Placeholder">'
Total Length
This is the total_length exercise in iteration.py.
Make a function total length that should calculate the total length of all given iterables.
This function should even work for iterables that don’t work with the built-in len (like zip, enumerate, etc.).
Example:
>>> from iteration import total_length
>>> total_length([1, 2, 3])
3
>>> total_length()
0
>>> total_length([1, 2, 3], [4, 5], iter([6, 7]))
7