Playing around with the NewType construct in this Pyright playground gave very confusing results for which I could not use the documentation of the typing module to decipher what was going on.
I defined a NewType called PositiveInt which is also a subtype of int as such.
PositiveInt = NewType("PositiveInt", int)
Confusion Point 1: According to Pyright, variables that are annotated with ANY callable type can be assigned to the value PositiveInt, even when the signature of the annotated type’s dunder call method doesn’t match the actual signature of PositiveInt.
bogus: Callable[[str], str] = PositiveInt
confusion: Callable[[None, None, str], None] = PositiveInt
class What(Protocol):
def __call__(self, foo: str, bar: bytes) -> str:
...
z: What = PositiveInt
Interestingly enough, for each above alias variable, trying to call them with the call signature of their annotated type results in the same errors as trying to directly call PositiveInt with that very call signature.
Confusion Point 2: According to Pyright, a variable annotated with type[PositiveInt] cannot be assigned to the value PositiveInt itself, but a variable annotated with type[type[PositiveInt]] can be assigned to type(PositiveInt).
unconstructable: type[PositiveInt] = PositiveInt
nested: type[type[PositiveInt]] = type(PositiveInt)
The very fact one can nest type[…] is a bit boggling for my mind.
Confusion Point 3: Pyright can accept a function for which it infers to have a return type “containing“ type[PositiveInt], but it cannot accept a function whose return type is explicitly annotated to “contain“ type[PositiveInt]
def infered_ok(x: int):
return PositiveInt, PositiveInt(x)
def annotated_bad(x: int) -> tuple[type[PositiveInt], PositiveInt]:
return PositiveInt, PositiveInt(x)
If all of these behaviors are intentional somehow, is there a reason that indicates why they were implemented? All of these seem rather unintuitive and prone to be used as footguns.