Packing and Unpacking ===================== .. toctree:: :maxdepth: 1 Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` 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. .. _section_heading-Starred_Expressions: Starred Expressions ------------------- Starred expressions come about because of the (simplified) definition of assignment shown below: .. code-block:: text 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. A ``target`` can be a name, attribute or index/slice of a mutable data type. A ``target`` 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. 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): .. _figure-starred_expression_example0: .. figure:: starred_expressions0.png :scale: 100 % :align: center Starred Expressions Example 0 * From a literal tuple into a starred expression (leftover items present go in ``c``): .. _figure-starred_expression_example1: .. figure:: starred_expressions1.png :scale: 100 % :align: center Starred Expressions Example 1 * From a literal tuple into a starred expression (leftover items present go in ``c``): .. _figure-starred_expression_example2: .. figure:: starred_expressions2.png :scale: 100 % :align: center Starred Expressions Example 2 * From a literal tuple into a starred expression (no leftover items present, ``c`` is empty): .. _figure-starred_expression_example3: .. figure:: starred_expressions3.png :scale: 100 % :align: center Starred Expressions Example 3 * From a named tuple into a starred expression (leftover items present go in ``c``): .. _figure-starred_expression_example4: .. figure:: starred_expressions4.png :scale: 100 % :align: center Starred Expressions Example 4 * From a named tuple into a starred expression (no leftover items present, ``c`` is empty): .. _figure-starred_expression_example5: .. figure:: starred_expressions5.png :scale: 100 % :align: center Starred Expressions Example 5 * 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. .. _figure-starred_expression_example6: .. figure:: starred_expressions6.png :scale: 100 % :align: center Starred Expressions Example 6 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. .. _section_heading-Starred_Arguments: 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 :ref:`section_heading-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 .. _figure-starred_arguments_example0: .. figure:: starred_arguments0.png :scale: 100 % :align: center Starred Arguments Example 0 If there are more positional parameters supplied than required, you will get a :py:exc:`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 .. _figure-starred_arguments_example1: .. figure:: starred_arguments1.png :scale: 100 % :align: center Starred Arguments Example 1 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) .. _figure-starred_arguments_example2: .. figure:: starred_arguments2.png :scale: 100 % :align: center Starred Arguments Example 2 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 .. _figure-starred_arguments_example3: .. figure:: starred_arguments3.png :scale: 100 % :align: center Starred Arguments Example 3 If there are more keyword parameters supplied than required, you will get a :py:exc:`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' .. _figure-starred_arguments_example4: .. figure:: starred_arguments4.png :scale: 100 % :align: center Starred Arguments Example 4 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} .. _figure-starred_arguments_example5: .. figure:: starred_arguments5.png :scale: 100 % :align: center Starred Arguments Example 5 Python allows you to unpack both sequences and mappings in the same function call as long as the functions formal arguments support it. .. admonition:: Try it! :class: TryIt 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 :py:keyword:`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 :py:keyword:`pass` statement), but you also won't get any errors.