3.2. Star Signature
Define API for you functions
Require particular way of passing positional and optional parameters
All parameters after
*
must be keyword-onlyAll parameters before
/
must be positional-only*
could be anywhere, not only at the beginning/
could be anywhere, not only at the endSince Python 3.8: PEP 570 -- Python Positional-Only Parameters
3.2.1. Recap Parameters
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
Parameters (variable in function signature):
>>> def echo(a, b, c=0):
... pass
a
- required parameterb
- required parameterc
- optional parameter (with default value)
3.2.2. Recap Arguments
Argument - value passed to the function
Argument can be: positional or keyword
Positional arguments - resolved by position, order is important, must be at the left side
Keyword arguments - resolved by name, order is not important, must be on the right side
After first keyword argument, all following arguments must also be keyword
Arguments (value passed when calling a function):
>>> echo(1, 2, c=3)
1
- positional argument2
- positional argumentc=3
- keyword argument
3.2.3. Problem
>>> def echo(a, b, c):
... print(f'{a=}, {b=}, {c=}')
>>>
>>>
>>> echo(1, 2, 3)
a=1, b=2, c=3
>>>
>>> echo(1, 2, c=3)
a=1, b=2, c=3
>>>
>>> echo(1, b=2, c=3)
a=1, b=2, c=3
>>>
>>> echo(a=1, b=2, c=3)
a=1, b=2, c=3
3.2.4. Keyword-only
All parameters after
*
must be keyword-only*
could be anywhere, not only at the beginning
>>> def echo(a, b, *, c):
... print(f'{a=}, {b=}, {c=}')
>>>
>>>
>>> echo(1, 2, 3)
Traceback (most recent call last):
TypeError: echo() takes 2 positional arguments but 3 were given
>>>
>>> echo(1, 2, c=3)
a=1, b=2, c=3
>>>
>>> echo(1, b=2, c=3)
a=1, b=2, c=3
>>>
>>> echo(a=1, b=2, c=3)
a=1, b=2, c=3
3.2.5. Positional-only
Since Python 3.8: PEP 570 -- Python Positional-Only Parameters
All parameters before
/
must be positional-only/
could be anywhere, not only at the end
>>> def echo(a, /, b, c):
... print(f'{a=}, {b=}, {c=}')
>>>
>>>
>>> echo(1, 2, 3)
a=1, b=2, c=3
>>>
>>> echo(1, 2, c=3)
a=1, b=2, c=3
>>>
>>> echo(1, b=2, c=3)
a=1, b=2, c=3
>>>
>>> echo(a=1, b=2, c=3)
Traceback (most recent call last):
TypeError: echo() got some positional-only arguments passed as keyword arguments: 'a'
3.2.6. Mixed
>>> def echo(a, /, b, *, c):
... print(f'{a=}, {b=}, {c=}')
>>>
>>>
>>> echo(1, 2, 3)
Traceback (most recent call last):
TypeError: echo() takes 2 positional arguments but 3 were given
>>>
>>> echo(1, 2, c=3)
a=1, b=2, c=3
>>>
>>> echo(1, b=2, c=3)
a=1, b=2, c=3
>>>
>>> echo(a=1, b=2, c=3)
Traceback (most recent call last):
TypeError: echo() got some positional-only arguments passed as keyword arguments: 'a'
3.2.7. Case Study - 1
>>> help(len)
Help on built-in function len in module builtins:
len(obj, /)
Return the number of items in a container.
>>> data = [1,2,3]
>>> len(data)
3
>>> len(obj=data)
Traceback (most recent call last):
TypeError: len() takes no keyword arguments
3.2.8. Case Study - 2
>>> help(sorted)
Help on built-in function sorted in module builtins:
sorted(iterable, /, *, key=None, reverse=False)
Return a new list containing all items from the iterable in ascending order.
A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.
>>> sorted([1,2,3], reverse=True)
[3, 2, 1]
>>> sorted([1,2,3], True)
Traceback (most recent call last):
TypeError: sorted expected 1 argument, got 2
>>> sorted([1,2,3], None, True)
Traceback (most recent call last):
TypeError: sorted expected 1 argument, got 3
>>> sorted(iterable=[1,2,3], reverse=True)
Traceback (most recent call last):
TypeError: sorted expected 1 argument, got 0
3.2.9. Use Case - 1
>>> def add(a, b, /):
... return a + b
3.2.10. Use Case - 2
Divmod
>>> def divmod(a, b, /):
... return a//b, a%b
3.2.11. Use Case - 3
sorted(iterable, /, *, key=None, reverse=False)
>>> from inspect import signature
>>>
>>>
>>> data = [3, 1, 2]
>>>
>>> sorted(data)
[1, 2, 3]
>>>
>>> sorted(data, reverse=True)
[3, 2, 1]
>>>
>>> signature(sorted)
<Signature (iterable, /, *, key=None, reverse=False)>
3.2.12. Use Case - 3
len(obj, /)
>>> from inspect import signature
>>>
>>>
>>> data = [3, 1, 2]
>>>
>>> len(data)
3
>>>
>>> signature(len)
<Signature (obj, /)>
3.2.13. Use Case - 4
sum(iterable, /, start=0)
>>> from inspect import signature
>>>
>>>
>>> data = [3, 1, 2]
>>>
>>> sum(data)
6
>>>
>>> sum(data, start=10)
16
>>>
>>> sum(data, 10)
16
>>>
>>> signature(sum)
<Signature (iterable, /, start=0)>
3.2.14. Use Case - 5
str.strip(self, chars=None, /)
>>> from inspect import signature
>>>
>>>
>>> signature(str.strip)
<Signature (self, chars=None, /)>
3.2.15. Use Case - 6
str.split(self, /, sep=None, maxsplit=-1)
>>> from inspect import signature
>>>
>>>
>>> signature(str.split)
<Signature (self, /, sep=None, maxsplit=-1)>
3.2.16. Use Case - 7
49 parameters
>>> def read_csv(filepath_or_buffer, /, *, sep=', ', delimiter=None,
... header='infer', names=None, index_col=None, usecols=None,
... squeeze=False, prefix=None, mangle_dupe_cols=True,
... dtype=None, engine=None, converters=None, true_values=None,
... false_values=None, skipinitialspace=False, skiprows=None,
... nrows=None, na_values=None, keep_default_na=True,
... na_filter=True, verbose=False, skip_blank_lines=True,
... parse_dates=False, infer_datetime_format=False,
... keep_date_col=False, date_parser=None, dayfirst=False,
... iterator=False, chunksize=None, compression='infer',
... thousands=None, decimal=b'.', lineterminator=None,
... quotechar='"', quoting=0, escapechar=None, comment=None,
... encoding=None, dialect=None, tupleize_cols=None,
... error_bad_lines=True, warn_bad_lines=True, skipfooter=0,
... doublequote=True, delim_whitespace=False, low_memory=True,
... memory_map=False, float_precision=None): ...
>>> read_csv('iris.csv', ';', None, 'infer', None, None, None, False, None,
... True, None, None, None, None, None, False, None, None, None,
... True, True, False, True, False, False, False, None, False,
... False, None, 'infer', None, b',', None, '"', 0, None, None,
... None, None, None, True, True, 0, True, False, True, False, None)
Traceback (most recent call last):
TypeError: read_csv() takes 1 positional argument but 49 were given
>>> read_csv('mydata.csv',
... verbose=False,
... skiprows=1,
... parse_dates=['date', 'time'],
... encoding='utf-8')
3.2.17. 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 Signature Args
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2
# %% English
# 1. Refactor function `take_damage`
# 2. Function takes one argument `dmg` and always returns `None`
# 3. Argument must be passed only as positional
# 4. Run doctests - all must succeed
# %% Polish
# 1. Zrefaktoruj funkcję `take_damage`
# 2. Funkcja przyjmuje jeden argument `dmg` i zawsze zwraca `None`
# 3. Argument można podawać tylko pozycyjnie
# 4. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isfunction
>>> assert isfunction(take_damage)
>>> take_damage(1)
>>> take_damage(1, 2)
Traceback (most recent call last):
TypeError: take_damage() takes 1 positional argument but 2 were given
>>> take_damage()
Traceback (most recent call last):
TypeError: take_damage() missing 1 required positional argument: 'dmg'
>>> take_damage(dmg=1) # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
TypeError: take_damage() got some positional-only arguments passed as
keyword arguments: 'dmg'
"""
# Argument must be passed only as positional
# type: Callable[[int],None]
def take_damage(dmg):
return None
# %% 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 Signature Kwargs
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2
# %% English
# 1. Refactor function `set_position`
# 2. Function takes two arguments `x`, `y` and always returns `None`
# 3. Arguments must be passed only as keywords
# 4. Run doctests - all must succeed
# %% Polish
# 1. Zrefaktoruj funkcję `set_position`
# 2. Funkcja przyjmuje dwa argumenty `x`, `y` i zawsze zwraca `None`
# 3. Argumenty można podawać tylko nazwanie (keyword)
# 4. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isfunction
>>> assert isfunction(set_position)
>>> set_position(x=1, y=2)
>>> set_position() # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
TypeError: set_position() missing 2 required keyword-only arguments: 'x'
and 'y'
>>> set_position(1)
Traceback (most recent call last):
TypeError: set_position() takes 0 positional arguments but 1 was given
>>> set_position(1, 2)
Traceback (most recent call last):
TypeError: set_position() takes 0 positional arguments but 2 were given
>>> set_position(1, y=1) # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
TypeError: set_position() takes 0 positional arguments but 1 positional
argument (and 1 keyword-only argument) were given
>>> set_position(x=1, 2)
Traceback (most recent call last):
SyntaxError: positional argument follows keyword argument
"""
# Arguments must be passed only as keywords
# type: Callable[[int,int],None]
def set_position(x, y):
return None
# %% 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 Signature Mixed
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2
# %% English
# 1. Refactor function `compute`, which always returns `None`
# 2. Function takes arguments:
# - `a, b, c` - positional only
# - `func` - keyword only
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zrefaktoruj funkcję `compute`, która zawsze zwraca `None`
# 2. Funkcja przyjmuje argumenty:
# - `a, b, c` - tylko pozycyjne
# - `func` - tylko keyword
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isfunction
>>> assert isfunction(compute)
>>> compute(1, 2, 3)
>>> compute(1, 2, 3, func=lambda:None)
>>> compute(1, 2)
Traceback (most recent call last):
TypeError: compute() missing 1 required positional argument: 'c'
>>> compute() # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
TypeError: compute() missing 3 required positional arguments: 'a', 'b',
and 'c'
>>> compute(1, 2, 3, lambda:None)
Traceback (most recent call last):
TypeError: compute() takes 3 positional arguments but 4 were given
>>> compute(a=1, b=2, c=3) # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
TypeError: compute() got some positional-only arguments passed as
keyword arguments: 'a, b, c'
"""
# Argument a,b,c must be passed only as positional, func as keyword
# type: Callable[[int,int,int,Callable],None]
def compute(a, b, c, func=lambda: ...):
return None
# %% 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 Signature Class
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2
# %% English
# 1. Use class `User`
# 2. Refactor method `__init__()` to require passing arguments:
# - Positional-only: `firstname` and `lastname`
# - Keyword-only: `birthdate`
# 3. Run doctests - all must succeed
# %% Polish
# 1. Użyj klasę `User`
# 2. Zrefaktoruj metodę `__init__()` aby wymagała podawanie argumentów:
# - Tylko pozycyjne: `firstname` i `lastname`
# - Tylko nazwanie (keyword): `birthdate`
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isclass
>>> assert isclass(User)
>>> mark = User('Mark', 'Watney', birthdate='2000-01-02')
>>> assert isinstance(mark, User)
>>> User('Mark', 'Watney', '2000-01-02')
Traceback (most recent call last):
TypeError: User.__init__() takes 3 positional arguments but 4 were given
>>> User('Mark', lastname='Watney', birthdate='2000-01-02')
Traceback (most recent call last):
TypeError: User.__init__() got some positional-only arguments passed as keyword arguments: 'lastname'
>>> User(firstname='Mark', lastname='Watney', birthdate='2000-01-02')
Traceback (most recent call last):
TypeError: User.__init__() got some positional-only arguments passed as keyword arguments: 'firstname, lastname'
"""
# Refactor method `__init__()` to require passing arguments:
# - Positional-only: `firstname` and `lastname`
# - Keyword-only: `birthdate`
# type: type[User]
class User:
def __init__(self, firstname, lastname, birthdate):
self.firstname = firstname
self.lastname = lastname
self.birthdate = birthdate