Here's a proof-of-concept for the decorator. It does not address the issue of passing aliases to positional arguments to **kwargs - I guess this requires changes in the CPython's core.

(Sorry about the coloring, that's how it's pasted)

from inspect import signature, Parameter
from functools import wraps


def positional_only(n):
def wrap(f):
s = signature(f)
params = list(s.parameters.values())
for i in range(n):
if params[i].kind != Parameter.POSITIONAL_OR_KEYWORD:
raise TypeError('{} has less than {} positional arguments'.format(f.__name__, n))
params[i] = params[i].replace(kind=Parameter.POSITIONAL_ONLY)
f.__signature__ = s.replace(parameters=params)
@wraps(f)
def inner(*args, **kwargs):
if len(args) < n:
raise TypeError('{} takes at least {} positional arguments'.format(f.__name__, n))
return f(*args, **kwargs)
return inner
return wrap


@positional_only(2)
def f(a, b, c):
print(a, b, c)


help(f)
# f(a, b, /, c, **kwargs)

f(1, 2, c=2)

# f(1, b=2, c=3)
# TypeError: f takes at least 2 positional arguments


@positional_only(3)
def g(a, b, *, c):
print(a, b, c)

# TypeError: g has less than 3 positional arguments
Elazar