# 2.2. Star Signature¶

• Define API for you functions

• Require particular way of passing positional and optional parameters

• All parameters after * must be keyword-only

• All parameters before / must be positional-only

• * could be anywhere, not only at the beginning

• / could be anywhere, not only at the end

• Since Python 3.8: PEP 570 -- Python Positional-Only Parameters

## 2.2.1. Recap¶

• Positional arguments - value passed to function

• Positional arguments - order is important

• Positional arguments - must be at the left side

• Keyword arguments - value passed to function resolved by name

• Keyword arguments - order is not important

• Keyword arguments - must be on the right side

• Positional argument cannot follow keyword arguments

>>> def set_point(x, y, z=None):
...      print(f'{x=}, {y=}, {z=}')

Valid:

>>> set_point(1, 2, 3)
x=1, y=2, z=3
>>>
>>> set_point(x=1, y=2, z=3)
x=1, y=2, z=3
>>>
>>> set_point(1, 2, z=3)
x=1, y=2, z=3
>>>
>>> set_point(1, y=2, z=3)
x=1, y=2, z=3

Errors:

>>> set_point(1, 2)
x=1, y=2, z=None
>>>
>>> set_point(1, y=2)
x=1, y=2, z=None
>>>
>>> set_point(1, z=3)
Traceback (most recent call last):
TypeError: set_point() missing 1 required positional argument: 'y'

## 2.2.2. Keyword-only Parameters¶

• All parameters after * must be keyword-only

• * could be anywhere, not only at the beginning

>>> def set_point(*, x, y, z=None):
...     print(f'{x=}, {y=}, {z=}')

Valid:

>>> set_point(x=1, y=2)
x=1, y=2, z=None
>>>
>>> set_point(x=1, y=2, z=3)
x=1, y=2, z=3

Errors:

>>> set_point(1, 2, 3)
Traceback (most recent call last):
TypeError: set_point() takes 0 positional arguments but 3 were given
>>>
>>> set_point(1, 2, z=3)
Traceback (most recent call last):
TypeError: set_point() takes 0 positional arguments but 2 positional arguments (and 1 keyword-only argument) were given
>>>
>>> set_point(1, y=2, z=3)
Traceback (most recent call last):
TypeError: set_point() takes 0 positional arguments but 1 positional argument (and 2 keyword-only arguments) were given

## 2.2.3. Positional-only Parameters¶

• 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 set_point(x, y, z=None, /):
...     print(f'{x=}, {y=}, {z=}')

Valid:

>>> set_point(1, 2)
x=1, y=2, z=None
>>>
>>> set_point(1, 2, 3)
x=1, y=2, z=3

Errors:

>>> set_point(1, 2, z=3)
Traceback (most recent call last):
TypeError: set_point() got some positional-only arguments passed as keyword arguments: 'z'
>>>
>>> set_point(1, y=2, z=3)
Traceback (most recent call last):
TypeError: set_point() got some positional-only arguments passed as keyword arguments: 'y, z'

## 2.2.4. Positional and Keyword Parameters¶

>>> def set_point(x, /, y, *, z=None):
...     print(f'{x=}, {y=}, {z=}')

Valid:

>>> set_point(1, 2)
x=1, y=2, z=None
>>>
>>> set_point(1, 2, z=3)
x=1, y=2, z=3
>>>
>>> set_point(1, y=2)
x=1, y=2, z=None
>>>
>>> set_point(1, y=2, z=3)
x=1, y=2, z=3

Errors:

>>> set_point(1, 2, 3)
Traceback (most recent call last):
TypeError: set_point() takes 2 positional arguments but 3 were given
>>>
>>>
>>> set_point(x=1, y=2, z=3)
Traceback (most recent call last):
TypeError: set_point() got some positional-only arguments passed as keyword arguments: 'x'

## 2.2.5. Use Case - 0x01¶

...     return a + b

## 2.2.6. Use Case - 0x02¶

• Divmod

>>> def divmod(a, b, /):
...     return a//b, a%b

## 2.2.7. Use Case - 0x03¶

• 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)>

## 2.2.8. Use Case - 0x03¶

• len(obj, /)

>>> from inspect import signature
>>>
>>>
>>> data = [3, 1, 2]
>>>
>>> len(data)
3
>>>
>>> signature(len)
<Signature (obj, /)>

## 2.2.9. Use Case - 0x04¶

• 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)>

## 2.2.10. Use Case - 0x05¶

>>> from inspect import signature
>>>
>>>
>>> signature(str.strip)
<Signature (self, chars=None, /)>

## 2.2.11. Use Case - 0x06¶

>>> from inspect import signature
>>>
>>>
>>> signature(str.split)
<Signature (self, /, sep=None, maxsplit=-1)>

## 2.2.12. Use Case - 0x07¶

• 49 parameters

>>> def read_csv(filepath_or_buffer, /, *, sep=', ', delimiter=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,
...              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
...          verbose=False,
...          skiprows=1,
...          parse_dates=['date', 'time'],
...          encoding='utf-8')

## 2.2.13. Assignments¶

Code 2.15. Solution
"""
* Assignment: Star Signature Args
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min
* Warning: This assignment will work only in Python 3.8+

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
>>> from inspect import isfunction

>>> assert callable(take_damage)
>>> 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):
pass

Code 2.16. Solution
"""
* Assignment: Star Signature Kwargs
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

English:
1. Create 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. Stwórz 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
>>> from inspect import isfunction

>>> assert callable(set_position)
>>> 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):
pass

Code 2.17. Solution
"""
* Assignment: Star Signature Mixed
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min
* Warning: This assignment will work only in Python 3.8+

English:
1. Create function compute, which always returns None
2. Function takes arguments:
a. a, b, c - positional only
b. func - keyword only
3. Run doctests - all must succeed

Polish:
1. Stwórz funkcję compute, która zawsze zwraca None
2. Funkcja przyjmuje argumenty:
a. a, b, c - tylko pozycyjne
b. func - tylko keyword
3. Uruchom doctesty - wszystkie muszą się powieść

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

>>> assert callable(compute)
>>> 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: ...):
pass