How should you unpack a list or a tuple into the first element and then the rest? Or into the last element and everything else? Pydon't unpack with slices, prefer starred assignment instead.
(If you are new here and have no idea what a Pydon't is, you may want to read the Pydon't Manifesto.)
It is fairly common to have a list or another iterable that you want to split in the first element and then the rest. You can do this by using slicing in Python, but the most explicit way is with starred assignments.
This feature was introduced in PEP 3132 -- Extended Iterable Unpacking and allows for the following:
>>> l = [1, 2, 3, 4, 5] >>> head, *tail = l >>> head 1 >>> tail [2, 3, 4, 5]
This starred assignment is done by placing one
* to the left of a variable name
in a multiple assignment, and by having any iterable on the right of the assignment.
All variable names get a single element and the variable name with the "star"
*) gets all other elements as a list:
>>> string = "Hello!" >>> *start, last = string >>> start ['H', 'e', 'l', 'l', 'o'] >>> last '!'
You can have more than two variable names on the left, but only one asterisk:
>>> a, b, *c, d = range(5) # any iterable works >>> a 0 >>> b 1 >>> c [2, 3] >>> d 4
When you use the starred assignment, the starred name might get an empty list,
>>> a, *b =  >>> a 1 >>> b 
and an error is issued if there are not enough items to assign to the names that are not starred:
>>> a, *b =  Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: not enough values to unpack (expected at least 1, got 0)
Here is a simple example that hints at the practical use cases of this feature.
The string method
.split accepts a second argument called
maxsplit that specifies the maximum number of splits that we want:
>>> sentence = "This is a sentence with many words." >>> sentence.split(" ") ['This', 'is', 'a', 'sentence', 'with', 'many', 'words.'] >>> sentence.split(" ", maxsplit=3) ['This', 'is', 'a', 'sentence with many words.']
By making use of starred assignment, we can easily get the first three words of a sentence:
>>> *first_three, rest = sentence.split(" ", 3) >>> first_three ['This', 'is', 'a'] >>> rest 'sentence with many words.'
Here are a couple of examples in some actual code, to give more context.
Imagine you wanted to implement a function akin to the
reduce function from
functools (you can reads its documentation here).
Here is how an implementation might look like, using slices:
def reduce(function, list_): """Reduce the elements of the list by the binary function.""" if not list_: raise TypeError("Cannot reduce empty list.") value = list_ list_ = list_[1:] while list_: value = function(value, list_) list_ = list_[1:] return value
And here is an equivalent implementation using starred assignment:
def reduce(function, list_): """Reduce the elements of the list by the binary function.""" if not list_: raise TypeError("Cannot reduce empty list.") value, *list_ = list_ while list_: val, *list_ = list_ value = function(value, val) return value
The usage of the starred assignment here makes it abundantly clear that we wish to unpack the list into an item to be used now and the rest to be used later.
Another similar example, but with the starred name in the beginning, follows.
The Luhn Algorithm is used to compute a check digit for things like credit card numbers or bank accounts.
Let's implement a function that verifies if the check digit is correct, according to the Luhn Algorithm, and using starred assignment to separate the check digit from all the other digits:
def verify_check_digit(digits): """Use the Luhn algorithm to verify the check digit.""" *digits, check_digit = digits weight = 2 acc = 0 for digit in reversed(digits): value = digit * weight acc += (value // 10) + (value % 10) weight = 3 - weight # 2 -> 1 and 1 -> 2 return (9 * acc % 10) == check_digit # Example from Wikipedia. print(verify_check_digit([7, 9, 9, 2, 7, 3, 9, 8, 7, 1, 3])) # True
Maybe it is not obvious to you what the function does just by looking at it,
but it should be very clear that the line
*digits, check_digit = digits splits
digits into the items in the beginning and the final digit.
How would you implement the function above, using slices and indexing? An example could be like so:
def verify_check_digit(digits): """Use the Luhn algorithm to verify the check digit.""" weight = 2 acc = 0 for digit in reversed(digits[:-1]): value = digit * weight acc += (value // 10) + (value % 10) weight = 3 - weight # 2 -> 1 and 1 -> 2 return (9 * acc % 10) == digits[-1] # Example from Wikipedia. print(verify_check_digit([7, 9, 9, 2, 7, 3, 9, 8, 7, 1, 3])) # True
This also works, but looks a bit more confusing. Notice we have two similar indexing operations, but one is actually a slice while the other is a proper indexing.
for loop we have a
reversed(digits[:-1]) while in the
return value we have
... == digits[-1].
If I am not paying enough attention, I won't notice those are different things.
Of course it is my fault that I am not paying enough attention, but when I'm
writing code, I prefer for my code to be as clear as possible:
I don't want the reader to spend too much time reading the code, I prefer them
to spend time studying the algorithms.
If you liked this Pydon't be sure to leave a reaction below and share this with your friends and fellow Pythonistas. Comment below a nice use case you found for starred assignments!
Also, don't forget to subscribe to the newsletter so you don't miss a single Pydon't!
functools, https://docs.python.org/3/library/functools.html#functools.reduce [consulted on the 12th of January of 2021].
I hope you learned something new! If you did, consider following the footsteps of the readers who bought me a slice of pizza 🍕. Your contribution boosts my confidence and helps me produce this content for you.