我们知道,可选参数必须位于参数列表的末尾,如下所示:
def func(arg1, arg2, ..., argN=default)
我在PyTorch
包中看到了一些例外。例如,我们可以在torch.randint
中找到此问题。如图所示,它的位置参数中有一个前导的可选参数!怎么可能?
Docstring:
randint(low=0, high, size, \*, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) -> Tensor
我们如何以与上述类似的方式定义函数?
答案 0 :(得分:2)
您的发现使我着迷,因为在Python(以及我所知道的所有其他语言)中,确实有一些引人注目的可选参数是非法的,这肯定会在我们的案例中引起关注
SyntaxError: non-default argument follows default argument
我很可疑,但是我已经搜索了源代码:
我在TensorFactories.cpp
的第566-596行发现,randint
实际上有几种(!)实现:
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ randint ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tensor randint(int64_t high, IntArrayRef size, const TensorOptions& options) {
return native::randint(high, size, c10::nullopt, options);
}
Tensor randint(
int64_t high,
IntArrayRef size,
c10::optional<Generator> generator,
const TensorOptions& options) {
return native::randint(0, high, size, generator, options);
}
Tensor randint(
int64_t low,
int64_t high,
IntArrayRef size,
const TensorOptions& options) {
return native::randint(low, high, size, c10::nullopt, options);
}
Tensor randint(
int64_t low,
int64_t high,
IntArrayRef size,
c10::optional<Generator> generator,
const TensorOptions& options) {
auto result = at::empty(size, options);
return result.random_(low, high, generator);
}
此模式重新出现在gen_pyi.py
的第466-471行,在此它为顶级函数生成类型签名:
'randint': ['def randint(low: _int, high: _int, size: _size, *,'
' generator: Optional[Generator]=None, {}) -> Tensor: ...'
.format(FACTORY_PARAMS),
'def randint(high: _int, size: _size, *,'
' generator: Optional[Generator]=None, {}) -> Tensor: ...'
.format(FACTORY_PARAMS)],
因此,基本上发生的是,没有“真正的”可选参数,而不是几个函数,其中一个存在,而另一个不存在。
这意味着,在不使用randint
参数的情况下调用low
时,其设置为0
:
Tensor randint(
int64_t high,
IntArrayRef size,
c10::optional<Generator> generator,
const TensorOptions& options) {
return native::randint(0, high, size, generator, options);
}
关于OP请求的进一步研究,关于如何可能存在多个具有相同名称和不同参数的函数:
再次回到gen_pyi.py
,我们看到这些函数被收集到第436行定义的unsorted_function_hints
,然后用于在509-513行创建function_hints
,最后是{{ 1}}在第670行设置为function_hints
。
env
字典用于写入pyi
存根文件。
这些存根文件利用PEP-484中所述的函数/方法重载。
函数/方法重载,请使用env
装饰器:
@overload装饰器允许描述支持参数类型的多种不同组合的函数和方法。内置模块和类型中经常使用此模式。
这里是一个例子:
@overload
因此,我们基本上对具有不同参数的同一函数from typing import overload
class bytes:
...
@overload
def __getitem__(self, i: int) -> int: ...
@overload
def __getitem__(self, s: slice) -> bytes: ...
进行了定义。
另一个例子:
__getitem__
在这里,我们定义了具有不同数量参数的相同函数from typing import Callable, Iterable, Iterator, Tuple, TypeVar, overload
T1 = TypeVar('T1')
T2 = TypeVar('T2')
S = TypeVar('S')
@overload
def map(func: Callable[[T1], S], iter1: Iterable[T1]) -> Iterator[S]: ...
@overload
def map(func: Callable[[T1, T2], S],
iter1: Iterable[T1], iter2: Iterable[T2]) -> Iterator[S]: ...
# ... and we could add more items to support more than two iterables
。
答案 1 :(得分:2)
单个函数不允许仅具有前导的可选参数:
8.6. Function definitions
[...] 如果参数具有默认值,则直到“ *”之前的所有后续参数也必须具有默认值 –这是一种语法限制,不能用语法。
请注意,这不包括仅关键字参数,这些参数永远不会按位置接收参数。
如果需要,可以通过手动实现参数匹配的参数来模仿这种行为。例如,您可以根据友善意愿进行分派,也可以明确匹配可变参数。
- name: Setup Node 14.x
uses: actions/setup-node@v1
with:
node-version: '14.x'
一种简单的分派形式通过迭代签名并调用第一个匹配的签名来实现函数重载。
def leading_default(*args):
# match arguments to "parameters"
*_, low, high, size = 0, *args
print(low, high, size)
leading_default(1, 2) # 0, 1, 2
leading_default(1, 2, 3) # 1, 2, 3
答案 2 :(得分:2)
我的另一个答案是对火炬库进行逆向工程,但是我想将这个答案专门用于如何以一种非骇客的,直接的方式实现类似的机制。
我们有multipledispatch库:
在Python中进行多种分派的相对理智的方法。 多重分派的这种实现是有效的,大多数情况下是完整的,执行静态分析以避免冲突,并提供可选的名称空间支持。看起来也不错。
所以我们来利用它:
from multipledispatch import dispatch
@dispatch(int, int)
def randint(low, high):
my_randint(low, high)
@dispatch(int)
def randint(high):
my_randint(0, high)
def my_randint(low, high):
print(low, high)
# 0 5
randint(5)
# 2 3
randint(2, 3)