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.