python 我如何强制一些函数参数仅为位置参数

gz5pxeao  于 2024-01-05  发布在  Python
关注(0)|答案(1)|浏览(131)

我想在Python 3.7中模仿Python 3.8的这种行为
仅位置参数/是引入的语法,用于指示某些函数参数必须按位置指定,并且不能用作关键字参数。

  1. #Python3.8
  2. def f(a,b,/,**kwargs):
  3. print(a,b,kwargs)
  4. >>> f(1,2,**{'a':100,'b':200,'c':300})
  5. # 1 2 {'a': 100, 'b': 200, 'c': 300}

字符串
ab仅用作位置参数。
我如何在Python 3.7中做同样的事情

  1. #Python3.7
  2. def f(a,b,**kwargs):
  3. print(a,b,kwargs)
  4. >>> f(1,2,**{'a':1,'b':2})
  5. # TypeError: f() got multiple values for argument 'a'


如何使ab仅为位置参数。/在Python 3.8中不起作用
在Python3.7中可以模仿/语法吗?

olqngx59

olqngx591#

您可以创建一个自定义的装饰器来声明仅限位置的参数,返回一个 Package 器来解析它自己的*args, **kwargs,使它们适合装饰函数的签名。不能使用关键字-参数-打包(**)(这是唯一的限制)。封装的保留字参数必须宣告为最后一个positional-or-keyword参数,或是宣告为第一个keyword-only参数。以下是两个范例:

  1. def foo(a, b, kwargs): # last positional-or-keyword parameter
  2. pass
  3. def foo(a, *args, kwargs): # first keyword-only parameter
  4. pass

字符串
变量kwargs将从 Package 函数中接收剩余的**kwargs,也就是说,它可以像**kwargs直接用在修饰函数中一样使用(就像在Python 3.8+中一样)。
以下装饰器的实现主要基于inspect.Signature.bind的实现,并进行了一些小的调整,以便通过装饰器声明的名称处理仅限位置的参数,并处理附加的(人工)kwargs参数。

  1. import functools
  2. import inspect
  3. import itertools
  4. def positional_only(*names, kwargs_name='kwargs'):
  5. def decorator(func):
  6. signature = inspect.signature(func)
  7. @functools.wraps(func)
  8. def wrapper(*args, **kwargs):
  9. new_args = []
  10. new_kwargs = {}
  11. parameters = iter(signature.parameters.values())
  12. parameters_ex = ()
  13. arg_vals = iter(args)
  14. while True:
  15. try:
  16. arg_val = next(arg_vals)
  17. except StopIteration:
  18. try:
  19. param = next(parameters)
  20. except StopIteration:
  21. break
  22. else:
  23. if param.name == kwargs_name or param.kind == inspect.Parameter.VAR_POSITIONAL:
  24. break
  25. elif param.name in kwargs:
  26. if param.name in names:
  27. msg = '{arg!r} parameter is positional only, but was passed as a keyword'
  28. msg = msg.format(arg=param.name)
  29. raise TypeError(msg) from None
  30. parameters_ex = (param,)
  31. break
  32. elif param.default is not inspect.Parameter.empty:
  33. parameters_ex = (param,)
  34. break
  35. else:
  36. msg = 'missing a required argument: {arg!r}'
  37. msg = msg.format(arg=param.name)
  38. raise TypeError(msg) from None
  39. else:
  40. try:
  41. param = next(parameters)
  42. except StopIteration:
  43. raise TypeError('too many positional arguments') from None
  44. else:
  45. if param.name == kwargs_name or param.kind == inspect.Parameter.KEYWORD_ONLY:
  46. raise TypeError('too many positional arguments') from None
  47. if param.kind == inspect.Parameter.VAR_POSITIONAL:
  48. new_args.append(arg_val)
  49. new_args.extend(arg_vals)
  50. break
  51. if param.name in kwargs and param.name not in names:
  52. raise TypeError(
  53. 'multiple values for argument {arg!r}'.format(
  54. arg=param.name)) from None
  55. new_args.append(arg_val)
  56. for param in itertools.chain(parameters_ex, parameters):
  57. if param.name == kwargs_name or param.kind == inspect.Parameter.VAR_POSITIONAL:
  58. continue
  59. try:
  60. arg_val = kwargs.pop(param.name)
  61. except KeyError:
  62. if (param.kind != inspect.Parameter.VAR_POSITIONAL
  63. and param.default is inspect.Parameter.empty):
  64. raise TypeError(
  65. 'missing a required argument: {arg!r}'.format(
  66. arg=param.name)) from None
  67. else:
  68. if param.name in names:
  69. raise TypeError(
  70. '{arg!r} parameter is positional only, '
  71. 'but was passed as a keyword'.format(arg=param.name))
  72. new_kwargs[param.name] = arg_val
  73. new_kwargs.update(kwargs=kwargs)
  74. return func(*new_args, **new_kwargs)
  75. return wrapper
  76. return decorator


下面是如何使用它的示例:

  1. @positional_only('a')
  2. def foo(a, *args, kwargs, b=9, c):
  3. print(a, args, b, c, kwargs)
  4. foo(1, **dict(a=2), c=3) # ok
  5. foo(1, 2, 3, 4, 5, c=6) # ok
  6. foo(1, b=2, **dict(a=3), c=4) # ok
  7. foo(a=1, c=2) # error
  8. foo(c=1) # error

展开查看全部

相关问题