Algorithms

As discussed in PEP 570, positional-only parameters syntax is a way to indicate positional-only parameters in addition to keyword-only parameters and positional-or-keyword parameters, using the notation / in the parameter list. It can be optional and not so critical to developers, thus such syntax can simply by dismissed for compatibility with older Python versions.

Basic Concepts

To convert, poseur will first extract all positional-only parameters from the function parameter list, then add a decorator to the original function definition with these parameters for runtime checks.

For example, with the samples from PEP 570:

def name(p1, p2, /, p_or_kw, *, kw): ...

it should be converted to

@decorator('p1', 'p2')
def name(p1, p2, p_or_kw, *, kw): ...

Definition of the decorator can be found as poseur.decorator(), which takes a list of strings as names of the positional-only parameters.

Runtime Decorator

The definition of the decorator can be described as below:

def decorator(*poseur):
    """Positional-only parameters runtime checker.

        Args:
            *poseur: Name list of positional-only parameters.

        Raises:
            TypeError: If any position-only parameters were passed as
                keyword parameters.

        The decorator function may decorate regular :term:`function` and/or
        :term:`lambda` function to provide runtime checks on the original
        positional-only parameters.

    """
    import functools
    def caller(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            poseur_args = set(poseur).intersection(kwargs)
            if poseur_args:
                raise TypeError('%s() got some positional-only arguments passed as keyword arguments: %r' % (func.__name__, ', '.join(poseur_args)))
            return func(*args, **kwargs)
        return wrapper
    return caller

which will mimic the actual behaviour of the Python compiler and raises TypeError if any positional-only parameters are provided as keyword parameters.

Formatted String Literals

Since Python 3.6, formatted string literals (f-string) were introduced in PEP 498. And since Python 3.8, f-string debugging syntax were added to the grammar. However, when poseur performs the conversion on positional-only parameters inside :term:`f-string`s, it may break the lexical grammar and/or the original context.

Therefore, we utilise f2format to first expand such f-string`s into :meth:`str.format calls, then rely on poseur to perform the conversion and processing. Basically, there are two cases as below:

  1. When a lambda with positional-only parameters is in a debug f-string. (To prevent the converted code from changing the original expression for self-documenting and debugging.)

  2. When positional-only parameters is in an f-string and the runtime checks decorator is to be added. (To prevent the converted code from breaking the quotes of the original string.)

Class Identifiers

In some corner cases, as all identifiers must be mangled and normalised in a class context, names of the extracted positional-only parameters will have to be processed before putting into the parameter list of the decorator.

Lambda Functions

lambda functions are alike regular function, except that we cannot add a decorator to its definition, but we add simply put the lambda definition inside a pair of parentheses () as an argument to the decorator function.

For a sample lambda function as follows:

lambda p1, p2, /, p_or_kw, *, kw: ...

poseur will convert the code as below:

decorator('p1', 'p2')(lambda p1, p2, p_or_kw, *, kw: ...)