Thanks Eric for updating the PEP. I have some questions:

If a type guard function is implemented as an instance method or class method, the first explicit argument maps to the second parameter (after "self" or "cls").

What's the rationale behind this? AFAIK in Typescript you can write user-defined typeguards asserting about the nature of the calling object. Using this is SomeInterface. This could be useful to narrow an object implementing a protocol.

What's the rationale for user-defined type guards to only apply in the positive case? In fact, of all the type guards listed in the motivation, only the truthy-typeguard applies only in the positive case, meaning user-defined typeguards are going to be less powerful than builtin ones.

Maybe we could make TypeGuard exhaustive by default (working on positive and negative cases) and making the type in the negative case to be T - guarded_type. Then we could receive an optional second argument, so that the type in the negative case is:

TypeGuard[guarded_type] -> (T - guarded_type)
TypeGuard[guarded_type, negative_extra_types] -> Union[T - guarded_type, negative_extra_types]

in that way you could write all the guards listed in the motivation as user-defined ones and even narrow the type further os some existing guards:

# As shown in the motivation the negative case still has type Optional[str]
def truthy_string(s: Optional[string]) -> Typeguard[str, str]:
  return bool(s)

# In the negative case it will have type Union[None, Literal['']]
def truthy_string(s: Optional[string]) -> TypeGuard[str, Literal['']]:
  return bool(s)

To mimic the current proposal of guards only working in the positive case one could write:

def guard(t: T) -> TypeGuard[U, T]:
  return ...

Finally, the example is_str_list should probably read:
if len(val) == 0:
  return allow_empty
Otherwise, it will always return True when the list is empty.

On Mon, Nov 9, 2020 at 12:02 PM Eric Traut <> wrote:
Thanks for the feedback everyone. I've updated the language in the PEP to clear up some points that were raised.

I also fixed the bug in the code sample that Sebastian noted. It is not be necessary to wrap the result in a TypeGuard object. Any bool return result is fine.

There were questions about what happens when a type guard function is implemented as a method (instance or class). In this case, the value being tested is still the first argument of the bound method. It maps to the second parameter of the method — the one after the "self" or "cls" parameter.

I also clarified what I mean by " User-defined type guards apply narrowing only in the positive case (the if clause)." If that's still not clear, let me know.

Sebastian, you asked about "type assertions". For that use case, I recommend a different approach. The validation routine should return the typed value that it's validating and raise an exception if it doesn't match that type.

def validate_foo_json(j: Any) -> Foo:
     if xxx:
        raise RuntimeError()
     return cast(Foo, j)

def parse_foo_json_request(j: Any) -> None:
     validated_j = validate_foo_json(j)

Guido, I'm interested in your feedback on the PEP once you have a chance to review.
Typing-sig mailing list --
To unsubscribe send an email to
Member address:

Sebastian Kreft