为了账号安全,请及时绑定邮箱和手机立即绑定

如何强制某些函数参数仅是位置参数

如何强制某些函数参数仅是位置参数

莫回无 2022-09-06 18:09:40
我想在Python3.7中模仿Python3.8的这种行为仅位置参数是引入的语法,用于指示某些函数参数必须按位置指定,并且不能用作关键字参数。/#Python3.8def 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.7def f(a,b,**kwargs):    print(a,b,kwargs)>>> f(1,2,**{'a':1,'b':2})# TypeError: f() got multiple values for argument 'a'如何使 , 仅作为位置参数。 在 Python3.8 的下面不起作用ab/是否可以在 Python3.7 中模仿语法?/
查看完整描述

1 回答

?
慕的地6264312

TA贡献1817条经验 获得超6个赞

您可以创建一个自定义修饰器来声明仅位置参数,并返回一个包装器,该包装器解析自己的包装器,以便它们适合修饰函数的签名。由于仅位置参数和关键字参数之间可能存在名称冲突,因此无法对此方法使用关键字参数打包 ()(这是唯一的限制)。打包的关键字参数需要声明为最后一个位置或关键字参数,或者声明为第一个仅关键字参数。下面是两个示例:*args, **kwargs**


def foo(a, b, kwargs):  # last positional-or-keyword parameter

    pass


def foo(a, *args, kwargs):  # first keyword-only parameter

    pass

该变量将从包装器函数接收剩余的,即它可以类似地使用,就像直接在修饰函数中使用一样(如在Python 3.8 +中)。kwargs**kwargs**kwargs


以下装饰器的实现很大程度上是基于检查的实现。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


查看完整回答
反对 回复 2022-09-06
  • 1 回答
  • 0 关注
  • 174 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号