2.3. Star Parameters

  • * is used for positional parameters

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

  • *args unpacks to tuple

  • ** is used for keyword parameters

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

  • **kwargs unpacks to dict

../../_images/unpack-assignment%2Cargs%2Cparams.png

2.3.1. Syntax

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

2.3.2. Recap

  • Parameter - Receiving variable used within the function

  • Required parameters - Parameter which is necessary to call function

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

  • Optional parameter - also known as: parameter with default value

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.3. 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(f'{args=}')
>>>
>>>
>>> echo()
args=()
>>>
>>> echo(1)
args=(1,)
>>>
>>> echo(2, 3)
args=(2, 3)
>>>
>>> echo(1, 2, 3, 4, 5)
args=(1, 2, 3, 4, 5)

2.3.4. 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(f'{kwargs=}')
>>>
>>>
>>> echo()
kwargs={}
>>>
>>> echo(a=1)
kwargs={'a': 1}
>>>
>>> echo(a=1, b=2)
kwargs={'a': 1, 'b': 2}
>>>
>>> echo(a=1, b=2, c=3, d=4, e=5)
kwargs={'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

2.3.5. 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.6. Parameters with Args, Kwargs

>>> def echo(a, b, c=None, *args):
...     print(f'{a=}, {b=}, {c=}, {args=}')
>>>
>>>
>>> echo(1, 2)
a=1, b=2, c=None, 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=None, **kwargs):
...     print(f'{a=}, {b=}, {c=}, {kwargs=}')
>>>
>>>
>>> echo(1, 2)
a=1, b=2, c=None, 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=None, *args, **kwargs):
...     print(f'{a=}, {b=}, {c=}, {args=}, {kwargs=}')
>>>
>>>
>>> echo(1, 2)
a=1, b=2, c=None, 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.7. Merge

>>> def echo(a,b,c,d,e):
...     return locals()
...
...
>>> data1 = {'a':1, 'b':2, 'c':3}
>>> data2 = {'d':4, 'e':5}
>>>
>>> echo(**data1, **data2)
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

Merging dicts before Python 3.9:

>>> {**data1, **data2}
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

Merging dicts since Python 3.9:

>>> data1 | data2
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

2.3.8. Use Case - 0x01

>>> 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.9. Use Case - 0x02

>>> 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.10. Use Case - 0x03

>>> 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.11. Use Case - 0x04

Intuitive definition of print function:

>>> print('hello')
hello
>>>
>>> print('hello', 'world')
hello world
>>>
>>> print('hello', 'new', 'world')
hello new world
>>> def print(*values, sep=' ', end='\n'):
...     return sep.join(values) + end

2.3.12. Assignments

Code 2.18. Solution
"""
* Assignment: Star 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

Polish:
    1. Napisz funkcję `mean()`, wyliczającą średnią arytmetyczną
    2. Funkcja przyjmuje dowolną ilość pozycyjnych argumentów

Non-functional requirements:
    * Do not import any libraries and modules
    * Use builtin functions `sum()` and `len()`
    * Run doctests - all must succeed

Hints:
    * `raise ValueError('error message')`
    * `sum(...) / len(...)`

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

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


Code 2.19. Solution
"""
* Assignment: Star Parameters Args
* Complexity: easy
* Lines of code: 6 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)`
    * `len()`

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
"""


# Return True if all arguments are int or float, otherwise False
# type: Callable[[int|float],bool]
def isnumeric(*args):
    ...


Code 2.20. Solution
"""
* Assignment: Star Parameters Kwargs
* Complexity: medium
* Lines of code: 7 lines
* Time: 8 min

English:
    1. Create function `isnumeric`
    2. Function takes 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 przyjmuje 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 `True` jako argument
    8. Uruchom funkcję bez podawania argumentów
    9. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `isinstance(obj, (type1, type2))`
    * `type(obj)`
    * `len()`
    * `dict.values()`
    * `tuple() + tuple()`

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
"""


# Return True if all arguments are int or float, otherwise False
# type: Callable[[int|float],bool]
def isnumeric(*args, **kwargs):
    ...