PEP 3102 -- Keyword-Only Arguments

PEP 仅限关键字参数 翻译

Posted by Rpl on May 31, 2019

本篇文章是对PEP 3102 – Keyword-Only Arguments这篇文章的翻译。PEP文章真是难看,一堆晦涩,加上能力不够,所以这篇翻译可能并不完美,需要阅读原英文文档的,可点击查看以上的链接。PDF版本可在此处下载 百度云提取码:wpnq

这篇文章只是对python3新增加的keyword-only Argument的解释,关于python 函数位置参数,默认参数, 可变参数,可变关键字参数,可移步python 位置参数,默认参数, 可变参数,仅限关键字参数,可变关键字参数的详解及区别

摘要

本篇PEP提出了一个对函数参数赋值到命名参数槽的方法的改变。特别是,支持声明仅限关键字参数:此种参数只能由关键字提供,绝对不会被位置参数自动填充。

原理阐述

当前Python的函数调用模式,允许通过位置或关键字指定参数。参数可以通过指定的名字显式填充或通过位置隐式填充。 通常情况下,一个函数会需要使用可变数量的参数,Python语言支持使用’varargs‘语法(*name),指定将任何剩余的参数都以元组的方式传入这个可变参数。前提限制是,在填充varargs参数槽之前,所有的普通参数槽需要被填满。 这并不总是可取的,我们可以很容易的预想到:一个函数可能会需要一个可变数量的参数,但同时也需要一个或多个关键字参数形式的可选项。目前,实现这一点的唯一方法是同时定义一个可变参数vargas和一个’keywords‘参数(kwargs),然后手动从字典中提取出可用的关键字。

使用说明

从语法上讲,本篇文章提议的更改相当简单,第一个改变是允许常规参数出现在可变参数之后(此时这个常规参数就是一个仅限关键字参数。强制性的,它只能通过关键字传参):

1
2
def sortwords(*wordlist,case_sensitive=False):
	...

例如上面的函数可以接受任意数量的位置参数,同时也可以接受一个关键字参数‘case_sensitive’。因为这个关键字参数选项绝对不会被位置参数填充,而是必须显式的通过名字指定填充,所以称为仅限关键字参数。 仅限关键字参数不需要有默认值, 由于Python需要将所有的参数都绑定一个值,而且将值绑定到关键字参数的唯一方法是通过这个关键字,因此这种参数是‘需要关键字的参数’。所以这些参数必须通过调用方提供,且必须通过关键字提供值。
第二点语法上的更改是允许省略可变参数的参数名。这意味着对于一个有仅限关键字参数的函数来说,它不会再接受一个可变参数。如下例:

1
2
def compare(a, b, *, key=None):
	...

此函数省略了可变参数名,它不在接受任何的可变参数,这种改变背后的原因:设想一个函数需要几个位置参数,同时这个函数也需要一个关键字参数,那么按照原来的方法我们可以这样定义函数:

1
2
def compare(a, b, key=None):
	...

现在,假设你想要这个‘key’是一个仅限关键字参数,由上面第一点的语法,你可以通过先添加一个可变参数,之后再加入一个关键字参数,这样的话这个关键字参数就会是仅限关键字参数:

1
2
def compare(a, b,*ignore, key=None):
	...

不幸的是,这个可变参数‘ignore’会吸收任意多个由调用者提供的错误位置参数,。考虑到我们希望任何不需要的参数都能引发错误,可以这样做:

1
2
3
def compare(a, b, *ignore, key=None):
	if ignore:  # 如果ignore不为空
		raise TypeError

方便点,我们可以简单的省略掉‘ignore’这个参数名, 意思是‘不允许任何超过这一点的位置参数’。因此,‘单星号语法可以表示为位置参数的结束符’。

函数的参数调用行为

当一个函数被调用时,输入的参数会以如下几种方式赋值给形参:

  • 对于每个形参,都有一个参数槽用于存放将要赋给这个参数的值。
  • 已经赋过值得参数槽被标记为‘filled’,没有赋值的参数槽仍然会被视为‘empty
  • 初始化时,所有的参数槽都被标记为空。
  • 位置参数第一个被赋值,接下来是关键字参数。
  • 对于每一个位置参数: - 先尝试将值绑定到第一个空的参数槽,如果这个参数槽不是一个可变变量的参数槽,将它标记为’filled’ - 否则,如果下一个空槽是可变参数槽,就将所有剩下的非关键字参数值放到这个可变参数槽里。
  • 对于每一个关键字参数: - 如果存在与关键字命名相同的参数,就把这个实参的值赋给这个关键字参数槽。如果所有的参数槽都已经被填满了,会引发错误异常 - 否则,如果存在一个关键字 字典的关键字参数,这个参数会被添加到一个字典:关键字为此字典的键,如果这个键已经存在,会报错。 - 否则, 如果不存在keyword dictionary, 而且没有匹配到参数名,报错。
  • 最后: - 如果可变变量的参数槽仍然没有填充,则赋一个空的元组作为它的值。 - 对每一个剩余的空参数槽:如果这个参数槽有默认值,就把这个参数槽用默认值填充。如果没有默认值,报错。

PEP 3102 – Keyword-Only Arguments

Abstract

This PEP proposes a change to the way that function arguments are assigned to named parameter slots. In particular, it enables the declaration of “keyword-only” arguments: arguments that can only be supplied by keyword and which will never be automatically filled in by a positional argument.

Rationale

The current Python function-calling paradigm allows arguments to be specified either by position or by keyword. An argument can be filled in either explicitly by name, or implicitly by position.

There are often cases where it is desirable for a function to take a variable number of arguments. The Python language supports this using the ‘varargs’ syntax (*name), which specifies that any ‘left over’ arguments be passed into the varargs parameter as a tuple.

One limitation on this is that currently, all of the regular argument slots must be filled before the vararg slot can be.

This is not always desirable. One can easily envision a function which takes a variable number of arguments, but also takes one or more ‘options’ in the form of keyword arguments. Currently, the only way to do this is to define both a varargs argument, and a ‘keywords’ argument (**kwargs), and then manually extract the desired keywords from the dictionary.

Specification

Syntactically, the proposed changes are fairly simple. The first change is to allow regular arguments to appear after a varargs argument:

1
2
def sortwords(*wordlist, case_sensitive=False):
	...

This function accepts any number of positional arguments, and it also accepts a keyword option called ‘case_sensitive’. This option will never be filled in by a positional argument, but must be explicitly specified by name.

Keyword-only arguments are not required to have a default value. Since Python requires that all arguments be bound to a value, and since the only way to bind a value to a keyword-only argument is via keyword, such arguments are therefore ‘required keyword’ arguments. Such arguments must be supplied by the caller, and they must be supplied via keyword.

The second syntactical change is to allow the argument name to be omitted for a varargs argument. The meaning of this is to allow for keyword-only arguments for functions that would not otherwise take a varargs argument:

1
2
def compare(a, b, *, key=None):
	...

The reasoning behind this change is as follows. Imagine for a moment a function which takes several positional arguments, as well as a keyword argument:

1
2
3
def compare(a, b, key=None):
	...

Now, suppose you wanted to have ‘key’ be a keyword-only argument. Under the above syntax, you could accomplish this by adding a varargs argument immediately before the keyword argument:

1
2
def compare(a, b, *ignore, key=None):
	...

Unfortunately, the ‘ignore’ argument will also suck up any erroneous positional arguments that may have been supplied by the caller. Given that we’d prefer any unwanted arguments to raise an error, we could do this:

1
2
3
4
5
6
7
def compare(a, b, *ignore, key=None):

	if ignore:

	# If ignore is not empty
	raise TypeError

As a convenient shortcut, we can simply omit the ‘ignore’ name, meaning ‘don’t allow any positional arguments

beyond this point’.

1
2
def compare(a, b, *, key=None)
	...

(Note: After much discussion of alternative syntax proposals, the BDFL has pronounced in favor of this ‘single star’ syntax for indicating the end of positional parameters.)

Function Calling Behavior

The previous section describes the difference between the old behavior and the new. However, it is also useful to have a description of the new behavior that stands by itself, without reference to the previous model. So this next section will attempt to provide such a description.

When a function is called, the input arguments are assigned to formal parameters as follows:

  • For each formal parameter, there is a slot which will be used to contain the value of the argument assigned to that parameter.

  • Slots which have had values assigned to them are marked as ‘filled’. Slots which have no value assigned to them yet are considered ‘empty’.

  • Initially, all slots are marked as empty.

  • Positional arguments are assigned first, followed by keyword arguments.

  • For each positional argument:

    • Attempt to bind the argument to the first unfilled parameter slot. If the slot is not a vararg slot, then mark the slot as ‘filled’.

    • If the next unfilled slot is a vararg slot, and it does not have a name, then it is an error.

    • Otherwise, if the next unfilled slot is a vararg slot then all remaining non-keyword arguments are placed into the vararg slot.

  • For each keyword argument:

    • If there is a parameter with the same name as the keyword, then the argument value is assigned to that parameter slot. However, if the parameter slot is already filled, then that is an error.

    • Otherwise, if there is a ‘keyword dictionary’ argument, the argument is added to the dictionary using the keyword name as the dictionary key, unless there is already an entry with that key, in which case it is an error.

    • Otherwise, if there is no keyword dictionary, and no matching named parameter, then it is an error. Finally:

  • If the vararg slot is not yet filled, assign an empty tuple as its value.

  • For each remaining empty slot: if there is a default value for that slot, then fill the slot with the default value. If there is no default value, then it is an error.

In accordance with the current Python implementation, any errors encountered will be signaled by raising TypeError. (If you want something different, that’s a subject for a different PEP.)

Backwards Compatibility

The function calling behavior specified in this PEP is a superset of the existing behavior - that is, it is expected that any existing programs will continue to work.

This document has been placed in the public domain.

Source: https://github.com/python/peps/blob/master/pep-3102.txt