我怎样才能强制某些函数参数仅是位置参数
How can I encforce some function parameters to be only positional only
我想在 Python3.7
中模仿 Python3.8 的这种行为
Positional-only parameters /
是引入的语法,用于指示某些函数参数必须按位置指定,不能用作关键字参数。
#Python3.8
def f(a,b,/,**kwargs):
print(a,b,kwargs)
>>> f(1,2,**{'a':100,'b':200,'c':300})
# 1 2 {'a': 100, 'b': 200, 'c': 300}
a
,b
仅用作位置参数。
我如何在 Python3.7
中做同样的事情
#Python3.7
def f(a,b,**kwargs):
print(a,b,kwargs)
>>> f(1,2,**{'a':1,'b':2})
# TypeError: f() got multiple values for argument 'a'
如何使 a
、b
仅作为位置参数。 /
在 Python3.8
以下不起作用
是否可以在 Python3.7 中模仿 /
语法?
您可以创建一个自定义装饰器来声明仅位置参数,返回一个包装器来解析它自己的 *args, **kwargs
以便它们符合装饰函数的签名。由于仅位置参数和关键字参数之间可能存在名称冲突,因此无法将关键字参数打包 (**
) 用于此方法(这是唯一的限制)。打包关键字参数需要声明为最后一个位置或关键字参数或第一个仅关键字参数。这里有两个例子:
def foo(a, b, kwargs): # last positional-or-keyword parameter
pass
def foo(a, *args, kwargs): # first keyword-only parameter
pass
变量 kwargs
将从包装函数接收剩余的 **kwargs
,即它可以像 **kwargs
直接在装饰函数中使用一样使用(如Python 3.8+).
装饰器的以下实现主要基于 inspect.Signature.bind
的实现,并进行了一些小调整,以通过装饰器声明的名称处理仅位置参数并处理额外的(人工)kwargs
参数.
import functools
import inspect
import itertools
def 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) # ok
foo(1, 2, 3, 4, 5, c=6) # ok
foo(1, b=2, **dict(a=3), c=4) # ok
foo(a=1, c=2) # error
foo(c=1) # error
我想在 Python3.7
中模仿 Python3.8 的这种行为Positional-only parameters /
是引入的语法,用于指示某些函数参数必须按位置指定,不能用作关键字参数。
#Python3.8
def f(a,b,/,**kwargs):
print(a,b,kwargs)
>>> f(1,2,**{'a':100,'b':200,'c':300})
# 1 2 {'a': 100, 'b': 200, 'c': 300}
a
,b
仅用作位置参数。
我如何在 Python3.7
中做同样的事情#Python3.7
def f(a,b,**kwargs):
print(a,b,kwargs)
>>> f(1,2,**{'a':1,'b':2})
# TypeError: f() got multiple values for argument 'a'
如何使 a
、b
仅作为位置参数。 /
在 Python3.8
是否可以在 Python3.7 中模仿 /
语法?
您可以创建一个自定义装饰器来声明仅位置参数,返回一个包装器来解析它自己的 *args, **kwargs
以便它们符合装饰函数的签名。由于仅位置参数和关键字参数之间可能存在名称冲突,因此无法将关键字参数打包 (**
) 用于此方法(这是唯一的限制)。打包关键字参数需要声明为最后一个位置或关键字参数或第一个仅关键字参数。这里有两个例子:
def foo(a, b, kwargs): # last positional-or-keyword parameter
pass
def foo(a, *args, kwargs): # first keyword-only parameter
pass
变量 kwargs
将从包装函数接收剩余的 **kwargs
,即它可以像 **kwargs
直接在装饰函数中使用一样使用(如Python 3.8+).
装饰器的以下实现主要基于 inspect.Signature.bind
的实现,并进行了一些小调整,以通过装饰器声明的名称处理仅位置参数并处理额外的(人工)kwargs
参数.
import functools
import inspect
import itertools
def 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) # ok
foo(1, 2, 3, 4, 5, c=6) # ok
foo(1, b=2, **dict(a=3), c=4) # ok
foo(a=1, c=2) # error
foo(c=1) # error