…gives error: TypeGuard functions must have a positional argument [valid-type].
PEP 647 says this:
A concern was raised that there may be cases where it is desired to apply the narrowing logic on self and cls. This is an unusual use case, and accommodating it would significantly complicate the implementation of user-defined type guards. It was therefore decided that no special provision would be made for it. If narrowing of self or cls is required, the value can be passed as an explicit argument to a type guard function.
It’s funny that the only times I have wanted to use TypeGuard was when I wanted to narrow self, so I don’t think it’s as unusual as the PEP makes it sound.
Yup, I keep coming back to this. I almost never use TypeGuard, and the only times I want to use it is when I add a convenient property to a class to avoid having to use isinstance (and therefore having to import an additional class at runtime)
Nothing is stopping anyone here from making a proper proposal and pushing it through.[1] It would just need a champion to argue for this. I don’t think it would need a new PEP, just an update to the spec after discussion with type-checkers-authors.
Ok, later discussion might prevent this from going through if something comes up, I can’t guarantee this will succeed ↩︎
The closest I’ve got to a solution is by using a custom property-like desciptor, with which pyright is able to correctly reveal the type of the property as TypeGuard[Module] but is still unable narrow the type of self accordingly even though self is properly passed as the first argument to the __get__ method of the descriptor:
from typing import TypeGuard, reveal_type
class type_guard[T]:
def __init__(self, func):
self.func = func
def __get__(self, obj, obj_type) -> TypeGuard[T]:
return self.func(obj)
class Object:
@type_guard['Module']
def is_module(self):
return isinstance(self, Module)
def do(self) -> None:
# pyright: Type of "self.is_module" is "TypeGuard[Module]"
reveal_type(self.is_module)
if self.is_module:
# pyright: Type of "self" is "Self@Object"
reveal_type(self)
class Module(Object): ...
mypy on the other hand reveals self.is_module to be of type bool so it needs even more work for type narrowing to be possible.
from typing import Optional, reveal_type
class Object:
def to_module(self) -> Optional['Module']:
return self if isinstance(self, Module) else None
def do(self):
reveal_type(self.to_module())
if m := self.to_module():
reveal_type(m)
class Module(Object):
...
class Class(Object):
...
if __name__ == "__main__":
mod = Module()
mod.do()
cls = Class()
cls.do()