慕的地6264312
您可以创建一个自定义修饰器来声明仅位置参数,并返回一个包装器,该包装器解析自己的包装器,以便它们适合修饰函数的签名。由于仅位置参数和关键字参数之间可能存在名称冲突,因此无法对此方法使用关键字参数打包 ()(这是唯一的限制)。打包的关键字参数需要声明为最后一个位置或关键字参数,或者声明为第一个仅关键字参数。下面是两个示例:*args, **kwargs**def foo(a, b, kwargs): # last positional-or-keyword parameter passdef foo(a, *args, kwargs): # first keyword-only parameter pass该变量将从包装器函数接收剩余的,即它可以类似地使用,就像直接在修饰函数中使用一样(如在Python 3.8 +中)。kwargs**kwargs**kwargs以下装饰器的实现很大程度上是基于检查的实现。Signature.bind 通过一些小的调整来处理通过装饰器声明的名称仅位置参数,并处理附加(人为)参数。kwargsimport functoolsimport inspectimport itertoolsdef positional_only(*names, kwargs_name='kwargs'): def decorator(func): signature = inspect.signature(func) @functools.wraps(func) def wrapper(*args, **kwargs): new_args = [] new_kwargs = {} parameters = iter(signature.parameters.values()) parameters_ex = () arg_vals = iter(args) while True: try: arg_val = next(arg_vals) except StopIteration: try: param = next(parameters) except StopIteration: break else: if param.name == kwargs_name or param.kind == inspect.Parameter.VAR_POSITIONAL: break elif param.name in kwargs: if param.name in names: msg = '{arg!r} parameter is positional only, but was passed as a keyword' msg = msg.format(arg=param.name) raise TypeError(msg) from None parameters_ex = (param,) break elif param.default is not inspect.Parameter.empty: parameters_ex = (param,) break else: msg = 'missing a required argument: {arg!r}' msg = msg.format(arg=param.name) raise TypeError(msg) from None else: try: param = next(parameters) except StopIteration: raise TypeError('too many positional arguments') from None else: if param.name == kwargs_name or param.kind == inspect.Parameter.KEYWORD_ONLY: raise TypeError('too many positional arguments') from None if param.kind == inspect.Parameter.VAR_POSITIONAL: new_args.append(arg_val) new_args.extend(arg_vals) break if param.name in kwargs and param.name not in names: raise TypeError( 'multiple values for argument {arg!r}'.format( arg=param.name)) from None new_args.append(arg_val) for param in itertools.chain(parameters_ex, parameters): if param.name == kwargs_name or param.kind == inspect.Parameter.VAR_POSITIONAL: continue try: arg_val = kwargs.pop(param.name) except KeyError: if (param.kind != inspect.Parameter.VAR_POSITIONAL and param.default is inspect.Parameter.empty): raise TypeError( 'missing a required argument: {arg!r}'.format( arg=param.name)) from None else: if param.name in names: raise TypeError( '{arg!r} parameter is positional only, ' 'but was passed as a keyword'.format(arg=param.name)) new_kwargs[param.name] = arg_val new_kwargs.update(kwargs=kwargs) return func(*new_args, **new_kwargs) return wrapper return decorator以下是如何使用它的示例:@positional_only('a')def foo(a, *args, kwargs, b=9, c): print(a, args, b, c, kwargs)foo(1, **dict(a=2), c=3) # okfoo(1, 2, 3, 4, 5, c=6) # okfoo(1, b=2, **dict(a=3), c=4) # okfoo(a=1, c=2) # errorfoo(c=1) # error