只允许名称参数的函数

时间:2015-08-11 10:56:12

标签: python function python-2.7 arguments cookbook

我在Python Cookbooks中阅读了一个解决方案,用于创建仅允许名称参数的函数。我编写了自己的代码来试试:

class Reporter(object):
    def __init__(self, *, testline=None, sw_ver= None, directory=None):
        pass

if __name__ == "__main__"
    r = Reporter()

但解释器显示此错误:

File "Reporter.py", line 6
    def __init__(self, *, testline=None, sw_ver= None, directory=None):
                        ^
SyntaxError: invalid syntax

为什么会显示这个?

6 个答案:

答案 0 :(得分:7)

您使用的代码是有效的语法,但对于 python3 ,所以本书必须使用python3语法,它只允许关键字参数,pep-3102:< / p>

python 3 new syntax

  

您还可以在参数列表中使用bare *来表示您不接受可变长度参数列表,但是您确实有关键字参数。

使用您的代码并在python 3中传递非关键字会出错,但原因不同:

TypeError                                 Traceback (most recent call last)
<ipython-input-2-b4df44fa1e0c> in <module>()
      1 if __name__ == "__main__":
----> 2         r = Reporter(4)
      3 

TypeError: __init__() takes 1 positional argument but 2 were given

使用关键字后,工作正常:

In [4]: if __name__ == "__main__":
             r = Reporter(testline=4)
   ...:     

使用具有相同语法的函数可能会产生更明显的错误:

def f(*, foo=None, bar=None):
    return foo, bar


In [6]: f(4)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-a122d87dbe99> in <module>()
----> 1 f(4)

TypeError: f() takes 0 positional arguments but 1 was given

如果您想允许某些位置参数并且传递了可选的关键字args但仅通过名称,那么它也很有用:

def f(a,b, *, foo=None, bar=None):
    return a, b, foo, bar

然后传递3个位置参数将会出错:

In [8]: f(1,2,3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-b61741968103> in <module>()
----> 1 f(1,2,3)

TypeError: f() takes 2 positional arguments but 3 were given

答案 1 :(得分:5)

我最近收到了关于这个答案的评论 →请注意它的目标是python2,因为OP用python2.7明确地标记了他的问题。对于python3,请参阅Padraic Cunningham的回答。

[原始回答]

您不能单独使用*。在函数声明中,它表示“解压缩此变量中的任何其他未命名参数”,因此您必须为其指定变量名称。

你可以通过给它一个名字来实现你想要的东西,然后检查它是否为空,如下所示:

class Reporter(object):
    def __init__(self, *args):
        assert not args, "Reporter.__ini__ only accepts named arguments"

然后你想要添加你可以允许的参数,如下所示:

# won't work
def __init__(self, *args, testline=None, sw_ver= None, directory=None):

...除了* args需要在最后。但是如果你把它们放在最后,你会发现你仍然可以先传递未命名的其他参数。

你必须扭转逻辑,只采取kwargs。

class Reporter(object):
    def __init__(self, **kwargs):
        testline = kwargs.pop('testline', None)
        sw_ver = kwargs.pop('sw_ver', None)
        directory = kwargs.pop('directory', None)
        assert not kwargs, 'Unknown arguments: %r' % kwargs

现在,如果有人试图提供未命名的辩论,他们将被拒绝。

答案 2 :(得分:1)

星号运算符(*)用于unpacking。你不能用它作为论据。

您可能需要阅读Variable-Length Argument Tuples

  

函数可以使用可变数量的参数。参数名称   以*开始将参数收集到元组中开头。例如,   printall接受任意数量的参数并打印出来:

def printall(*args):
    print args
  

gather参数可以有你喜欢的任何名字,但是args是   常规。以下是该功能的工作原理:

>>> printall(1, 2.0, '3') (1, 2.0, '3')

答案 3 :(得分:1)

这是我在正在研究的Python 2项目的切线上写的一些实用程序装饰器。引发的异常尽可能地反映了Python 3中使用仅关键字参数语法的函数所引发的异常。

它们不会禁止位置参数,但它们可以要求/限制关键字参数。你可以创建另一个不允许位置参数的装饰器。

import functools

def original_wrapped_function(f):
    try:
        while True:
            f = f.__wrapped__
    except AttributeError:
        return f


def restrict_kwargs(*allowed_keywords):
    def restrict_kwargs_decorator(func):
        @functools.wraps(original_wrapped_function(func))
        def restrict_wrapper(*args, **kwargs):
            for keyword in kwargs:
                if keyword not in allowed_keywords:
                    msg = "%s() got an unexpected keyword argument '%s'"
                    raise TypeError(msg % (func.__name__, keyword))
            return func(*args, **kwargs)
        restrict_wrapper.__wrapped__ = func
        return restrict_wrapper
    return restrict_kwargs_decorator


def require_kwargs(*required_keywords):
    def require_kwargs_decorator(func):
        @functools.wraps(original_wrapped_function(func))
        def require_wrapper(*args, **kwargs):
            missing_keywords = []
            for keyword in required_keywords:
                if keyword not in kwargs:
                    missing_keywords.append(keyword)
            if missing_keywords:
                func_name = func.__name__
                count = len(missing_keywords)
                if count == 1:
                    arg_word = 'argument'
                    missing_keywords_str = "'%s'" % missing_keywords[0]
                else:
                    arg_word = 'arguments'
                    and_join_str = ' and ' if count == 2 else ', and '
                    missing_keywords_str = ', '.join(
                        ("'%s'" % mk) for mk in missing_keywords[:-1])
                    missing_keywords_str = and_join_str.join((
                        missing_keywords_str, ("'%s'" % missing_keywords[-1])))
                msg = "%s() missing %d required keyword-only %s: %s"
                raise TypeError(msg % (func_name, count, arg_word,
                                       missing_keywords_str))
            return func(*args, **kwargs)
        require_wrapper.__wrapped__ = func
        return require_wrapper
    return require_kwargs_decorator


def exact_kwargs(*exact_keywords):
    def exact_kwargs_decorator(func):
        @restrict_kwargs(*exact_keywords)
        @require_kwargs(*exact_keywords)
        @functools.wraps(original_wrapped_function(func))
        def exact_wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        exact_wrapper.__wrapped__ = func
        return exact_wrapper
    return exact_kwargs_decorator

一些例子:

>>> @restrict_kwargs('five', 'six')
... def test_restrict_kwargs(arg1, arg2, *moreargs, **kwargs):
...     return (arg1, arg2, moreargs, kwargs)
... 
>>> 
>>> @require_kwargs('five', 'six')
... def test_require_kwargs(arg1, arg2, *moreargs, **kwargs):
...     return (arg1, arg2, moreargs, kwargs)
... 
>>> 
>>> @exact_kwargs('five', 'six')
... def test_exact_kwargs(arg1, arg2, *moreargs, **kwargs):
...     return (arg1, arg2, moreargs, kwargs)
... 
>>> 
>>> 
>>> 
>>> test_restrict_kwargs(1, 2, 3, 4, five=5)
(1, 2, (3, 4), {'five': 5})
>>> 
>>> test_restrict_kwargs(1, 2, 3, 4, five=5, six=6)
(1, 2, (3, 4), {'six': 6, 'five': 5})
>>> 
>>> test_restrict_kwargs(1, 2, 3, 4, five=5, six=6, seven=7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "SO_31939890.py", line 19, in restrict_wrapper
    raise TypeError(msg % (func.__name__, keyword))
TypeError: test_restrict_kwargs() got an unexpected keyword argument 'seven'
>>> 
>>> 
>>> 
>>> test_require_kwargs(1, 2, 3, 4, five=5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "SO_31939890.py", line 49, in require_wrapper
    missing_keywords_str))
TypeError: test_require_kwargs() missing 1 required keyword-only argument: 'six'
>>> 
>>> test_require_kwargs(1, 2, 3, 4, five=5, six=6)
(1, 2, (3, 4), {'six': 6, 'five': 5})
>>> 
>>> test_require_kwargs(1, 2, 3, 4, five=5, six=6, seven=7)
(1, 2, (3, 4), {'seven': 7, 'six': 6, 'five': 5})
>>> 
>>> 
>>> 
>>> test_exact_kwargs(1, 2, 3, 4, five=5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "SO_31939890.py", line 20, in restrict_wrapper
    return func(*args, **kwargs)
  File "SO_31939890.py", line 49, in require_wrapper
    missing_keywords_str))
TypeError: test_exact_kwargs() missing 1 required keyword-only argument: 'six'
>>> 
>>> test_exact_kwargs(1, 2, 3, 4, five=5, six=6)
(1, 2, (3, 4), {'six': 6, 'five': 5})
>>> 
>>> test_exact_kwargs(1, 2, 3, 4, five=5, six=6, seven=7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "SO_31939890.py", line 19, in restrict_wrapper
    raise TypeError(msg % (func.__name__, keyword))
TypeError: test_exact_kwargs() got an unexpected keyword argument 'seven'
>>> 

答案 4 :(得分:0)

星号(*)不是有效的Python 2.7标识符,它是一个运算符。我认为你在复制食谱中的代码时犯了一个错误。

然而,它是Python 3中的有效代码,如Padraic Cunningham answered

答案 5 :(得分:0)

在python 2中。*:

参数名称派生为任何编程语言中的变量名称定律。但在python 2. * STAR 符号用于determining special situation的参数名称和单个 STAR 符号作为参数名称引发错误。

但是在python 3. *:

等于 STAR 的参数不能为此分配任何值,下一个位置参数不能按位置给出值,只能按参数名称值。