
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