3.3. Star Parameters
*
is used for positional parametersargs
is a convention, but you can use any name*args
unpacks totuple
**
is used for keyword parameterskwargs
is a convention, but you can use any name**kwargs
unpacks todict
3.3.1. Syntax
>>> def myfunction(*args):
... pass
>>> def myfunction(**kwargs):
... pass
>>> def myfunction(*args, **kwargs):
... pass
3.3.2. Recap
Parameter - variable in function signature (function definition)
Parameter can be: required or optional
Required parameters - necessary to function call
Optional parameters - has default value
After first optional parameter, all following parameters must also be optional
Required parameters:
>>> def echo(a, b):
... ...
Optional parameters:
>>> def echo(a=1, b=2):
... ...
Required and optional parameters:
>>> def echo(a, b=2):
... ...
Required parameters must be the leftmost:
>>> def echo(a=1, b):
... ...
Traceback (most recent call last):
SyntaxError: parameter without a default follows parameter with a default
3.3.3. Positional Parameters
*
is used for positional parametersargs
is a convention, but you can use any name*args
unpacks totuple
>>> 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)
3.3.4. Keyword Parameters
**
is used for keyword parameterskwargs
is a convention, but you can use any name**kwargs
unpacks todict
>>> 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}
3.3.5. Positional and Keyword Parameters
*
is used for positional parameters**
is used for keyword parameters*args
unpacks totuple
**kwargs
unpacks todict
>>> 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}
3.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}
3.3.7. Case Study
Source: https://github.com/django/django/blob/main/django/views/generic/base.py
Out of 20 methods 17 is using either
*args
or**kwargs
(that's 85%)sometimes parameters have different names, such as:
**initkwargs
or**response_kwargs
class ContextMixin:
extra_context = None
def get_context_data(self, **kwargs): ...
class View:
http_method_names = ["get", "post", "put", "delete"]
def __init__(self, **kwargs): ...
def view_is_async(cls): ...
def as_view(cls, **initkwargs): ...
def setup(self, request, *args, **kwargs): ...
def dispatch(self, request, *args, **kwargs): ...
def http_method_not_allowed(self, request, *args, **kwargs): ...
def options(self, request, *args, **kwargs): ...
def _allowed_methods(self): ...
from django.views.generic.base import TemplateResponse
class TemplateResponseMixin:
template_name = None
template_engine = None
response_class = TemplateResponse
content_type = None
def render_to_response(self, context, **response_kwargs): ...
def get_template_names(self): ...
from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
class TemplateView(TemplateResponseMixin, ContextMixin, View):
def get(self, request, *args, **kwargs): ...
from django.views import View
class RedirectView(View):
permanent = False
url = None
pattern_name = None
query_string = False
def get_redirect_url(self, *args, **kwargs): ...
def get(self, request, *args, **kwargs): ...
def head(self, request, *args, **kwargs): ...
def post(self, request, *args, **kwargs): ...
def options(self, request, *args, **kwargs): ...
def delete(self, request, *args, **kwargs): ...
def put(self, request, *args, **kwargs): ...
def patch(self, request, *args, **kwargs): ...
3.3.8. Use Case - 1
>>> 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
3.3.9. Use Case - 2
>>> 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]
3.3.10. Use Case - 3
>>> 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>
3.3.11. Use Case - 4
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
3.3.12. Assignments
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: Star Parameters Define
# - Difficulty: easy
# - Lines: 4
# - Minutes: 5
# %% English
# 1. Create function `mean()`, which calculates arithmetic mean
# 2. If there is no arguments, raise ValueError with message:
# 'At least one argument is required'
# 3. Function can have arbitrary number of positional arguments
# 4. Non-functional requirements:
# - Do not import any libraries and modules
# - Use builtin functions `sum()` and `len()`
# 5. Run doctests - all must succeed
# %% Polish
# 1. Napisz funkcję `mean()`, wyliczającą średnią arytmetyczną
# 2. Jeżeli nie podano arumentów, podnieś wyjątek ValueError z wiadomością:
# 'At least one argument is required'
# 3. Funkcja przyjmuje dowolną ilość pozycyjnych argumentów
# 4. Wymagania niefunkcjonalne:
# - Nie importuj żadnych bibliotek i modułów
# - Użyj wbudowanych funckji `sum()` i `len()`
# 5. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `raise ValueError('error message')`
# - `sum(...) / len(...)`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> 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
"""
# Create function `mean()`, which calculates arithmetic mean
# If there is no arguments, raise ValueError with message:
# 'At least one argument is required'
def mean():
...
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: Star Parameters Args
# - Difficulty: easy
# - Lines: 6
# - Minutes: 8
# %% English
# 1. Define function `isnumeric()`
# - Function takes arbitrary number of positional arguments
# - Arguments can be of any type
# - Return `True` if all arguments are `int` or `float` only
# - Return `False` if any argument is different type
# 2. Compare using `type()` and `isinstance()`
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj funkcję `isnumeric()`
# - Funkcja przyjmuje dowolną liczbę argumentów pozycyjnych
# - Podawane argumenty mogą być dowolnego typu
# - Zwróć `True` jeżeli wszystkie argumenty są tylko typów `int` lub `float`
# - Zwróć `False` jeżeli którykolwiek jest innego typu
# 2. Porównaj użycie `type()` i `isinstance()`
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `isinstance(obj, type1|type2)`
# - `type(obj)`
# - `... in tuple()`
# - `raise TypeError('error message')`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isfunction
>>> assert isfunction(isnumeric), \
'isnumeric must be a function'
>>> isnumeric()
Traceback (most recent call last):
TypeError: At least one argument is required
>>> isnumeric(1)
True
>>> isnumeric(1.0)
True
>>> isnumeric('one')
False
>>> isnumeric(True)
False
>>> isnumeric(1, 1.0)
True
>>> isnumeric(1, 1.0, 'one')
False
"""
# Return True if all arguments are int or float, otherwise False
# type: Callable[[int|float],bool]
def isnumeric(*args):
...
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: Star Parameters Kwargs
# - Difficulty: medium
# - Lines: 7
# - Minutes: 8
# %% English
# 1. Define function `isnumeric()`
# - Function takes arbitrary number of positional and keyword arguments
# - Arguments can be of any type
# - Return `True` if all arguments are `int` or `float` only
# - Return `False` if any argument is different type
# 2. Compare using `type()` and `isinstance()`
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj funkcję `isnumeric()`
# - Funkcja przyjmuje dowolną liczbę argumentów pozycyjnych i nazwanych
# - Podawane argumenty mogą być dowolnego typu
# - Zwróć `True` jeżeli wszystkie argumenty są tylko typów `int` lub `float`
# - Zwróć `False` jeżeli którykolwiek jest innego typu
# 2. Porównaj użycie `type()` i `isinstance()`
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `isinstance(obj, type1|type2)`
# - `type(obj)`
# - `dict.values()`
# - `tuple() += tuple()`
# - `raise TypeError('error message')`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isfunction
>>> assert isfunction(isnumeric), \
'isnumeric must be a function'
>>> isnumeric()
Traceback (most recent call last):
TypeError: At least one argument is required
>>> isnumeric(1)
True
>>> isnumeric(1.0)
True
>>> isnumeric('one')
False
>>> isnumeric(True)
False
>>> isnumeric(1, 1.0)
True
>>> isnumeric(1, 1.0, 'one')
False
>>> isnumeric(a=1)
True
>>> isnumeric(a=1.0)
True
>>> isnumeric(a='one')
False
>>> isnumeric(a=True)
False
>>> isnumeric(a=1, b=1.0)
True
>>> isnumeric(a=1, b=1.0, c='one')
False
"""
# Return True if all arguments are int or float, otherwise False
# type: Callable[[int|float],bool]
def isnumeric(*args, **kwargs):
...