Skip to content

Latest commit

 

History

History
143 lines (97 loc) · 4.05 KB

File metadata and controls

143 lines (97 loc) · 4.05 KB

pycobytes[25] := Snowflakes!

There are 2 types of people. Those who do backups and those who will do backups.

Hey pips!

Let’s say we’ve defined a function that takes in 3 parameters:

def create_student(name, age, house = None):
    return {
        "name": str(name).capitalize(),
        "age": round(age),
        "house": house or "unassigned",
    }

When we call this, we pass in the arguments individually:

>>> create_student("Stralian", 21, "Haven")
{"name": "Stralian", "age": 21, "house": "Haven"}

But suppose we had our items stored in a list. How could we pass them to each parameter?

>>> info = ["Stralian", 21, "Haven"]

If we just pass info to the function, it assigns the whole list to the first parameter name, leaving age and house without values:

>>> create_student(info)
TypeError: create_student() missing 1 required positional argument: 'age'

What we want is for the items of the list to ‘spread’ out onto each parameter. We can do this manually by indexing the list:

>>> create_student(info[0], info[1], info[2])
{"name": "Stralian", "age": 21, "house": "Haven"}

But sheesh, that’s not ergonomic at all. If we had 5 variables, we’d have to write out all the indices up to [4].

This is another situation where the snowflake operator * comes in handy. When used to define variables (function definitions, variable unpacking), it packs many arguments into one – but here it does the opposite, unpacking the values of the list:

>>> create_student(*info)
{"name": "Stralian", "age": 21, "house": "Haven"}

To visualise what’s going on here:

spreading the arguments

So the * operator used before an iterable unpacks it, turning it from 1 object into a “series” of individual arguments that can be mapped to the parameters of a function. You can also think of it like a magic operator that ‘slices off’ the brackets around an iterable.

This is very much syntactic sugar! You can only use * like this between the () of a function call. Trying to apply it to an object raw doesn’t work:

>>> stuff = [1, 2, 3]
>>> *stuff
SyntaxError: can't use starred expression here

Let’s look at another scenario which illustrates the difference between passing in an iterable argument vs unpacking it with *. Our good friend print() joins us again. If we print a list, it outputs the string representation of the list object:

>>> naturals = [0, 1, 2, 3, 4]
>>> print(naturals)
[0, 1, 2, 3, 4]

But if we unpack, then each item of the list is printed individually, with the default space separator:

>>> print(*naturals)
0 1 2 3 4

The difference is even clearer if we add a separator:

>>> print(*naturals, sep = " / ")
0 / 1 / 2 / 3 / 4

# no snowflake, no sep
>>> print(naturals, sep = " / ")
[0, 1, 2, 3, 4]

>>> print(naturals, "hi", sep = " / ")
[0, 1, 2, 3, 4] hi

Another place we can use this is when constructing a new iterable. If you’ve ever needed to merge lists, you can do so by unpacking them into a new one:

>>> list1 = [1, 2, 3]
>>> list2 = [4, 5, 6, 7]

>>> [*list1, *list2]
[1, 2, 3, 4, 5, 6, 7]

You could of course just concatenate them with +, but if you also have other items, this can make things a lot clearer:

>>> ["first", *list1, *list2, [0, 1]]
["first", 1, 2, 3, 4, 5, 6, 7, None]

>>> ["first"] + list1 + list2 + [[0, 1]]

["first", 1, 2, 3, 4, 5, 6, 7, None]

Like many of Python’s quirkier features, the snowflake operator gives us so much flexibility and convenience. It’s something you really come to miss in other languages!1



Footnotes

  1. Thank goodness JavaScript has the spread ... operator.