2.3. Unpack Parameters

2.3.1. Rationale

  • More information in Unpack Assignment

  • More information in Unpack Parameters

  • More information in Unpack Arguments

../../_images/unpack-assignment,args,params1.png

2.3.2. Syntax

>>> def myfunction(*args):
...     pass
>>> def myfunction(**kwargs):
...     pass
>>> def myfunction(*args, **kwargs):
...     pass

2.3.3. Recap

  • parameter - Receiving variable used within the function/block

  • required parameters - Parameter which is necessary to call function

  • optional parameters (with default value) - Parameter which is optional and has default value (if not specified at call time)

Required parameters:

>>> def echo(a, b):
...     pass

Optional parameters:

>>> def echo(a=1, b=2):
...     pass

Required and optional parameters:

>>> def echo(a, b=2):
...     pass

Required parameters must be the leftmost:

>>> def echo(a=1, b):
...     pass
Traceback (most recent call last):
SyntaxError: non-default argument follows default argument

2.3.4. Positional Parameters

  • * is used for positional parameters

  • args is a convention, but you can use any name

  • *args unpacks to tuple

>>> def echo(*args):
...     print(args)
>>>
>>>
>>> echo()
()
>>>
>>> echo(1)
(1,)
>>>
>>> echo(2, 3)
(2, 3)
>>>
>>> echo(1, 2, 3, 4, 5)
(1, 2, 3, 4, 5)

2.3.5. Keyword Parameters

  • ** is used for keyword parameters

  • kwargs is a convention, but you can use any name

  • **kwargs unpacks to dict

>>> def echo(**kwargs):
...     print(kwargs)
>>>
>>>
>>> echo()
{}
>>>
>>> echo(a=1)
{'a': 1}
>>>
>>> echo(a=1, b=2)
{'a': 1, 'b': 2}
>>>
>>> echo(a=1, b=2, c=3, d=4, e=5)
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

2.3.6. Positional and Keyword Parameters

  • * is used for positional parameters

  • ** is used for keyword parameters

  • *args unpacks to tuple

  • **kwargs unpacks to dict

>>> def echo(*args, **kwargs):
...     print(f'{args=}, {kwargs=}')
>>>
>>>
>>> echo()
args=(), kwargs={}
>>>
>>> echo(1, 2, 3)
args=(1, 2, 3), kwargs={}
>>>
>>> echo(d=4, e=5, f=6)
args=(), kwargs={'d': 4, 'e': 5, 'f': 6}
>>>
>>> echo(1, 2, 3, d=4, e=5, f=6)
args=(1, 2, 3), kwargs={'d': 4, 'e': 5, 'f': 6}

2.3.7. Parameters with Args, Kwargs

>>> def echo(a, b, c=0, *args):
...     print(f'{a=}, {b=}, {c=}, {args=}')
>>>
>>>
>>> echo(1, 2)
a=1, b=2, c=0, args=()
>>>
>>> echo(1, 2, 3)
a=1, b=2, c=3, args=()
>>>
>>> echo(1, 2, 3, 4)
a=1, b=2, c=3, args=(4,)
>>>
>>> echo(1, 2, 3, 4, 5, 6)
a=1, b=2, c=3, args=(4, 5, 6)
>>> def echo(a, b, c=0, **kwargs):
...     print(f'{a=}, {b=}, {c=}, {kwargs=}')
>>>
>>>
>>> echo(1, 2)
a=1, b=2, c=0, kwargs={}
>>>
>>> echo(1, 2, 3)
a=1, b=2, c=3, kwargs={}
>>>
>>> echo(1, 2, 3, d=7, e=8, f=9)
a=1, b=2, c=3, kwargs={'d': 7, 'e': 8, 'f': 9}
>>>
>>> echo(1, 2, a=7)
Traceback (most recent call last):
TypeError: echo() got multiple values for argument 'a'
>>> def echo(a, b, c=0, *args, **kwargs):
...     print(f'{a=}, {b=}, {c=}, {args=}, {kwargs=}')
>>>
>>>
>>> echo(1, 2)
a=1, b=2, c=0, args=(), kwargs={}
>>>
>>> echo(1, 2, 3, 4, 5, 6)
a=1, b=2, c=3, args=(4, 5, 6), kwargs={}
>>>
>>> echo(1, 2, 3, d=7, e=8, f=9)
a=1, b=2, c=3, args=(), kwargs={'d': 7, 'e': 8, 'f': 9}
>>>
>>> echo(1, 2, 3, 4, 5, 6, d=7, e=8, f=9)
a=1, b=2, c=3, args=(4, 5, 6), kwargs={'d': 7, 'e': 8, 'f': 9}

2.3.8. Use Case - 0x01

>>> def echo(a, b, c=0, *args):
...     print(locals())
>>>
>>>
>>> echo(*range(0,10))
{'a': 0, 'b': 1, 'c': 2, 'args': (3, 4, 5, 6, 7, 8, 9)}

2.3.9. Use Case - 0x02

>>> def add(*values):
...     total = 0
...     for value in values:
...         total += value
...     return total
>>>
>>>
>>> add()
0
>>>
>>> add(1)
1
>>>
>>> add(1, 4)
5
>>>
>>> add(3, 1)
4
>>>
>>> add(1, 2, 3, 4)
10

2.3.10. Use Case - 0x03

>>> def celsius_to_kelvin(degree):
...     return degree + 273.15
>>>
>>>
>>> celsius_to_kelvin(1)
274.15
>>>
>>> celsius_to_kelvin([1,2,3])
Traceback (most recent call last):
TypeError: can only concatenate list (not "float") to list
>>> def celsius_to_kelvin(*degrees):
...     return [x+273.15 for x in degrees]
>>>
>>>
>>> celsius_to_kelvin(1)
[274.15]
>>>
>>> celsius_to_kelvin(1, 2, 3, 4, 5)
[274.15, 275.15, 276.15, 277.15, 278.15]

2.3.11. Use Case - 0x04

>>> def html_list(*fruits):
...     print('<ul>')
...     for fruit in fruits:
...         print(f'<li>{fruit}</li>')
...     print('</ul>')
>>>
>>>
>>> html_list('apple', 'banana', 'orange')
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

2.3.12. Use Case - 0x05

Intuitive definition of print function:

>>> def print(*values, sep=' ', end='\n'):
...     return sep.join(values) + end
>>>
>>>
>>> print('a')
'a\n'
>>>
>>> print('a', 'b')
'a b\n'
>>>
>>> print('a', 'b', 'c')
'a b c\n'
>>>
>>> print('a', 'b', 'c', sep=',')
'a,b,c\n'
>>>
>>> print('a', 'b', 'c', sep='|')
'a|b|c\n'

2.3.13. Assignments

Code 2.17. Solution
"""
* Assignment: Unpack Parameters Define
* Complexity: easy
* Lines of code: 4 lines
* Time: 5 min

English:
    1. Create function `mean()`, which calculates arithmetic mean
    2. Function can have arbitrary number of positional arguments
    3. Do not import any libraries and modules
    4. Use builtin functions `sum()` and `len()`
    5. Run doctests - all must succeed

Polish:
    1. Napisz funkcję `mean()`, wyliczającą średnią arytmetyczną
    2. Funkcja przyjmuje dowolną ilość pozycyjnych argumentów
    3. Nie importuj żadnych bibliotek i modułów
    4. Użyj wbudowanych funkcji `sum()` i `len()`
    5. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `sum(...) / len(...)`

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> mean(1)
    1.0
    >>> mean(1, 3)
    2.0
    >>> mean()
    Traceback (most recent call last):
    ValueError: At least one argument is required
"""


Code 2.18. Solution
"""
* Assignment: Unpack Parameters Args
* Complexity: easy
* Lines of code: 7 lines
* Time: 8 min

English:
    1. Create function `isnumeric`
    2. Function can have arbitrary number of positional arguments
    3. Arguments can be of any type
    4. Return `True` if all arguments are `int` or `float` only
    5. Return `False` if any argument is different type
    6. Do not use `all()` and `any()`
    7. Run doctests - all must succeed

Polish:
    1. Stwórz funkcję `isnumeric`
    2. Funkcja może przyjmować dowolną liczbę argumentów pozycyjnych
    3. Podawane argumenty mogą być dowolnego typu
    4. Zwróć `True` jeżeli wszystkie argumenty są tylko typów `int` lub `float`
    5. Zwróć `False` jeżeli którykolwiek jest innego typu
    6. Nie używaj `all()` oraz `any()`
    7. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `isinstance(obj, (type1, type2))`
    * `type(obj)`

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert isfunction(isnumeric), \
    'isnumeric must be a function'

    >>> isnumeric()
    False
    >>> isnumeric(0)
    True
    >>> isnumeric(1)
    True
    >>> isnumeric(-1)
    True
    >>> isnumeric(1.1)
    True
    >>> isnumeric('one')
    False
    >>> isnumeric([1, 1.1])
    False
    >>> isnumeric(1, 1.1)
    True
    >>> isnumeric(1, 'one')
    False
    >>> isnumeric(1, 'one', 'two')
    False
    >>> isnumeric(True)
    False
"""


Code 2.19. Solution
"""
* Assignment: Unpack Parameters Kwargs
* Complexity: medium
* Lines of code: 8 lines
* Time: 5 min

English:
    1. Create function `isnumeric`
    2. Function can have arbitrary number of positional **and keyword arguments**
    3. Arguments can be of any type
    4. Return `True` if all arguments are `int` or `float` only
    5. Return `False` if any argument is different type
    6. Do not use `all()` and `any()`
    7. Compare using `type()` and `isinstance()` passing `True` as an argument
    8. Run the function without any arguments
    9. Run doctests - all must succeed

Polish:
    1. Stwórz funkcję `isnumeric`
    2. Funkcja może przyjmować dowolną liczbę argumentów pozycyjnych **i nazwanych**
    3. Podawane argumenty mogą być dowolnego typu
    4. Zwróć `True` jeżeli wszystkie argumenty są tylko typów `int` lub `float`
    5. Zwróć `False` jeżeli którykolwiek jest innego typu
    6. Nie używaj `all()` oraz `any()`
    7. Porównaj użycie `type()` i `isinstance()` podając argument do funkcji `True`
    8. Uruchom funkcję bez podawania argumentów
    9. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `isinstance(obj, (type1, type2))`
    * `type(obj)`

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert isfunction(isnumeric), \
    'isnumeric must be a function'

    >>> isnumeric()
    False
    >>> isnumeric(0)
    True
    >>> isnumeric(1)
    True
    >>> isnumeric(-1)
    True
    >>> isnumeric(1.1)
    True
    >>> isnumeric('one')
    False
    >>> isnumeric([1, 1.1])
    False
    >>> isnumeric(1, 1.1)
    True
    >>> isnumeric(1, 'one')
    False
    >>> isnumeric(1, 'one', 'two')
    False
    >>> isnumeric(True)
    False
    >>> isnumeric(a=1)
    True
    >>> isnumeric(a=1.1)
    True
    >>> isnumeric(a='one')
    False
"""