Packing and Unpacking¶
Indices and tables¶
Definition¶
The use of the *
and **
operators can be used to pack values into, and unpack values from iterables.
The ways it can be used differ depending on whether you are dealing with assignments, or functions.
When used as part of assignment, they are called a starred expression.
When used as part of a function, they are called starred arguments.
Starred Expressions¶
Starred expressions come about because of the (simplified) definition of assignment shown below:
target ("," target)* = expression ("," expression)*
expression
is something that yields a single value or an iterable. Multiple expressions can be given that are separated by a “,” (and due to the “,” it yields a tuple regardless of the presence of the()
brackets).One or more
target
variables to receive values generated by (unpacked from) their corresponding expression. Atarget
can be a name, attribute or index/slice of a mutable data type. Atarget
with a*
in front, as in*target
, makes it a starred expression- The
*target
gets (is packed with) the leftover values (if any) not assigned to the other targets.
- The
Generally speaking, multiple groups of targets on the left, can receive values from unpacking multiple iterables (generated by their corresponding expression) in the expression list on the right.
That’s a mouthful … and certainly more powerful functionality then you need on a daily basis.
Some examples will make this clearer!
- From a literal tuple (the
,
on the right makes it a tuple):
- From a literal tuple into a starred expression (leftover items present go in
c
):
- From a literal tuple into a starred expression (leftover items present go in
c
):
- From a literal tuple into a starred expression (no leftover items present,
c
is empty):
- From a named tuple into a starred expression (leftover items present go in
c
):
- From a named tuple into a starred expression (no leftover items present,
c
is empty):
Target-list of target-lists to assign to from a tuple of tuples.
- Combines multiple assignment operations into one statement.
- Crazy … probably too crazy to maintain. Perhaps better not to do this.
In addition to what is shown above:
Anywhere a tuple was used on the right hand side, any other iterable, like a string or list, could also be used.
Anywhere a tuple was used on the left, you can also use:
- An item of a mutable iterable
- A slice of a mutable iterable
- An attribute reference
For example:
>>> a = ['a', 'b', 'c', 'd'] >>> a[3] = 'x' >>> a ['a', 'b', 'c', 'x'] >>> a[1:3] = 'gh' >>> a ['a', 'g', 'h', 'x']
Tip
You can get pretty complicated with assignments in Python if you want to terrorize those that read your code. Don’t do it. Don’t be that guy. Not only will you make your code difficult to maintain by others, it will also be difficult for you to figure out what you were doing in 6 months when you come back to it.
Starred Arguments¶
A starred argument refers to the use of the *
and **
operators to unpack a sequence or mapping in order to supply values to formal positional or keyword arguments in a function call.
Note
There is a related concept, which is Python’s support for a Variable Parameter List using the *args
and **kwargs
as the catch-all for extra positional arguments and extra keyword arguments, respectively. To be clear, that concept refers to how the supplied parameter values are collected and presented to the code inside the function when writing the function definition.
The *
operator will unpack a sequence as positional arguments. For example:
>>> my_args = [1, 2, 3]
>>> def my_fn(a, b, c):
... print("a: {} b: {} c: {}".format(a, b, c))
>>> my_fn(*my_args)
a: 1 b: 2 c: 3
If there are more positional parameters supplied than required, you will get a TypeError
. For example:
>>> my_args = [1, 2, 3, 4]
>>> def my_fn(a, b, c):
... print("a: {} b: {} c: {}".format(a, b, c))
>>> my_fn(*my_args)
TypeError: my_fn() takes 3 positional arguments but 4 were given
However, if the parameter *args
is defined, it will pick up any extra positional parameters supplied. For example:
>>> my_args = [1, 2, 3, 4, 5, 6]
>>> def my_fn(a, b, c, *args):
... print("a: {} b: {} c: {} args: {}".format(a, b, c, args))
>>> my_fn(*my_args)
a: 1 b: 2 c: 3 args: (4, 5, 6)
The **
operator will unpack a mapping as keyword arguments. For example:
>>> my_kwargs = {'t': 10, 's': 9, 'r': 8}
>>> def my_fn(r, s, t):
... print("r: {} s: {} t: {}".format(r, s, t))
>>> my_fn(**my_kwargs)
r: 8 s: 9 t: 10
If there are more keyword parameters supplied than required, you will get a TypeError
. For example:
>>> my_kwargs = {'t': 10, 's': 9, 'r': 8, 'u': 99}
>>> def my_fn(r, s, t):
... print("r: {} s: {} t: {}".format(r, s, t))
>>> my_fn(**my_kwargs)
TypeError: my_fn() got an unexpected keyword argument 'u'
However, if the parameter **kwargs
is defined, it will pick up any extra keyword parameters supplied. For example:
>>> my_kwargs = {'t': 10, 's': 9, 'r': 8, 'u': 99}
>>> def my_fn(r, s, t, **kwargs):
... print("r: {} s: {} t: {}, kwargs: {}".format(r, s, t, kwargs))
>>> my_fn(**my_kwargs)
r: 8 s: 9 t: 10, kwargs: {'u': 99}
Python allows you to unpack both sequences and mappings in the same function call as long as the functions formal arguments support it.
Try it!
Try the following:
Write a function definition that takes in the following:
- 2 positional arguments called, “pos0” and “pos1”.
- 2 keyword arguments called,”kw0” and “kw1”.
- 0 or more additional positional arguments.
- 0 or more additional keyword arguments.
Use
pass
for the body of the function.Call the function you defined above with the following values:
- pos0 = 5
- pos1 = 2
- pos2 = (‘x’, ‘y’)
- pos3 = ‘abc’
- kw0 = ‘yes’
- kw1 = ‘no’
- kw2 = ‘up’
- kw3 = ‘down’
If the function call works, you won’t get any output (due to the
pass
statement), but you also won’t get any errors.