【问题标题】:function which allows only name arguments仅允许名称参数的函数
【发布时间】:2015-08-11 10:56:12
【问题描述】:

我在 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

为什么会显示这个?

【问题讨论】:

  • 很确定你不能单独拥有star,你想用它做什么?
  • 您确定您从书中复制的内容正确吗?也许这本书在逗号之前的星号之后有一个名字(变量名)?
  • [IMG]i60.tinypic.com/2dui9sx.jpg[/IMG]这是本书的代码
  • 您使用哪个 Python 版本?这本书适用于哪个 Python 版本?

标签: python function python-2.7 arguments cookbook


【解决方案1】:

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

python 3 new syntax

您也可以在参数列表中使用裸 * 表示您不接受可变长度的参数列表,但您确实有仅关键字参数。

在 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

如果您想允许一些位置参数并传递可选关键字参数但仅按名称传递,这也很有用:

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

【讨论】:

    【解决方案2】:

    我最近收到了关于此答案的评论。
    → 请注意它针对 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
    

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

    【讨论】:

    • 谢谢,但您的解决方案也会产生语法错误:文件“Reporter.py”,第 6 行 def __init__(self, *args, testline=None, sw_ver= None, directory=None): ^ SyntaxError : 无效语法
    • 我的推理只进行到一半就分心了,对此感到抱歉。现在已经修好了。
    【解决方案3】:

    星号运算符 (*) 用于unpacking。您不能将其用作参数。

    您可能想阅读Variable-Length Argument Tuples

    函数可以接受可变数量的参数。参数名称 以 * 开头的参数将参数收集到一个元组中。例如, printall 接受任意数量的参数并打印它们:

    def printall(*args):
        print args
    

    gather 参数可以有任何你喜欢的名字,但是 args 是 传统的。该函数的工作原理如下:

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

    【讨论】:

      【解决方案4】:

      这些是我为我正在从事的 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'
      >>> 
      

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

          在 python 2.* 中:

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

          但在 python 3.* 中:

          等于STAR的参数不能给this和next position参数赋值,不能按位置赋值,只能按参数名赋值。

          【讨论】:

            猜你喜欢
            • 2014-09-14
            • 2010-10-23
            • 2018-06-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-06-22
            • 2017-11-02
            • 1970-01-01
            相关资源
            最近更新 更多