repeated _ parameters¶
basedpython lets _ appear more than once in a function or lambda signature, so
a signature that ignores several arguments doesn't need invented placeholders.
the _s are passed by position:
transpiles to:
python rejects two parameters of one name, so each _ after the first is given
a name of its own — _2, _3 and so on, skipping any name the module spells.
that name is the transpiler's rather than yours, so no call may spell it: a /
after the last _ makes them positional-only, and passing one by keyword is a
positional-only-parameter-as-kwarg error
python only exempts _: duplicates of any other name remain a syntax error
parameters after the last _¶
the / goes after the last _, so a parameter declared after it is still
passed by name:
a *_ or **_ is reached by no keyword, so a _ repeated only by one of them
is left as it is
parameters before the last _¶
python's / makes every parameter before it positional-only, so a parameter you
named ahead of a repeated _ would stop being reachable by its name. that is
refused rather than done quietly, as invalid-repeated-underscore:
writing the / yourself says it is what you meant:
a method's receiver is the exception: nothing passes self by keyword, so
def handle(self, _: int, _: int) is accepted, with self before the /
a repeated _ after * would be keyword-only, which only a keyword reaches, and
it has no name of its own to be reached by. that is refused as well
python before 3.8 has no /, so a project that targets it cannot make the _s
positional-only, and a signature that needs the / is refused there
callable types and protocol methods¶
a callable type that names its parameters, and a method of an inline protocol, follow the same rules, so a callback that ignores several arguments is called by position:
transpiles to:
from typing import Protocol
class _Callable_f9c80db4(Protocol):
def __call__(self, _: "int", _2: "str", /) -> "None": ...
type Handler = _Callable_f9c80db4
overrides¶
a method that overrides another takes the names the overridden method gives those positions, so a call written against the base keeps working on the override:
class Handler:
def handle(self, event: int, source: str): ...
class Quiet(Handler):
override def handle(self, _: int, _: str): ...
Quiet().handle(event=1, source="a")
transpiles to:
from typing_extensions import override
class Handler:
def handle(self, event: int, source: str): ...
class Quiet(Handler):
@override
def handle(self, event: int, source: str): ...
Quiet().handle(event=1, source="a")
a base parameter that is positional-only stays so, and a default the base gives a parameter comes with it, as it does for any override (see inherited default values)
a base that repeats _ itself gives the override its first _. the names it
numbers the others with are positional-only, so no call passes one, and the
override numbers its own _s there, clear of every name its module spells —
the names its body reads among them. a base that is itself such an override
gives the names it took
where the base has no parameter at some _'s position, none of the _s take a
name from it and they are numbered as above. that override is reported as an
invalid-method-override anyway
a name the base's author wrote would shadow the same name read from an enclosing
scope, so an override whose body reads one is refused as
invalid-repeated-underscore
reading _¶
references to _ in the body resolve to the first _ parameter. in an override
that took its names from the base, the first of them is bound to _ at the start
of the body, so a read of _ still finds it