Your discovery fascinated me, as it's indeed illegal in Python (and all other languages I know) to have leading optional arguments, that would surely raise in our case:
SyntaxError: non-default argument follows default argument
I got suspicious, yet I've searched on the source code:
I found, at lines 566-596 of TensorFactories.cpp
that there are actually several (!) implementations of 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);
}
This pattern reoccurred at lines 466-471 of gen_pyi.py
where it generates type signatures for top-level functions:
'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)],
So, what basically happens is that there is no "real" optional parameter rather than several functions, in which one is present and in the other, it's not.
That means, when randint
is called without the low
parameter it is set as 0
:
Tensor randint(
int64_t high,
IntArrayRef size,
c10::optional<Generator> generator,
const TensorOptions& options) {
return native::randint(0, high, size, generator, options);
}
Further research, as for OP request on how that possible that there are multiple functions with the same name and different arguments:
Returning once again to gen_pyi.py
we see that these functions are collected to unsorted_function_hints
defined at line 436, then it's used to create function_hints
at lines 509-513, and finally function_hints
is set to env
at line 670.
The env
dictionary is used to write pyi
stub files.
These stub files make use of Function/method overloading as described in PEP-484.
Function/method overloading, make use of @overload
decorator:
The @overload decorator allows describing functions and methods that support multiple different combinations of argument types. This pattern is used frequently in builtin modules and types.
Here is an example:
from typing import overload
class bytes:
...
@overload
def __getitem__(self, i: int) -> int: ...
@overload
def __getitem__(self, s: slice) -> bytes: ...
So we basically have a definition of the same function __getitem__
with different arguments.
And another example:
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
Here we have a definition of the same function map
with a different number of arguments.