On Wed, Dec 23, 2020 at 8:22 PM Eric Traut <eric@traut.com> wrote:
Sebastian said:
>If you could provide an alternative to better model the example code that would be great.

Here are three techniques that would work with the current proposal.

class QueryResult(Generic[T]):
  # Technique 1: Accept an explicit `value` parameter expecting the caller
  # to pass in an instance; they can pass `self` if that's the instance they
  # want to test.
  def is_empty1(self, value: "QueryResult[S]") -> TypeGuard[MissingQueryResult]:
    return value._data is None

  # Technique 2: Use a static method, expecting the caller to pass in "self" (or
  # whatever other instance they want to test)
  def is_empty2(value: "QueryResult[S]") -> TypeGuard[MissingQueryResult]:
    return value._data is None

# Technique 3: Define it as a separate utility method, not part of the class.
def is_empty_query_result(value: "QueryResult[S]") -> TypeGuard[MissingQueryResult]:
  return value.empty()
Eric you explicitly said that "Applying a user-defined type guard to `self` strikes me as a very unusual use case — one that probably involves anti-patterns".

So I asked for an alternative way to model the problem so that no anti-patterns are present.

However, you just presented alternatives on how one could avoid the limitation of `self` being ignored by typeguards, none of which are really ergonomic as replied in previous threads.

Do you agree that we want to support type guards that are instance or class methods? If so, then these functions will need to accept one explicit `value` parameter in addition to the implicit "self" or "cls" parameter. I think there's a case to be made for disallowing type guards as instance or class methods. That would mean that all type guard functions would need to be either a static method or a (non-method) function. Alternatively, we could allow type guards as instance/class methods but assume that the first implicit parameter (`self` or `cls`) is the value being tested.
I like your last proposal, having `self` or `cls` the value being tested. 

I don't think that multiple arguments is the problem here. I've come across a couple of cases in real code where I wouldn't have been able to use the TypeGuard mechanism if it was limited to just one argument, so I'm reluctant to place that limitation on it. 
Note that it'd be just a deferral until we get more insights on how typeguards are used in Python. Then we could extend TypeGuard to accept an optional second argument specifying which argument is the one being guarded, something like TypeGuard[T, 'second']. That would accommodate both instance and class methods and multi argument functions. This would be equivalent to how type guards are expressed in TypeScript.
Could you share those cases? Do any of these cases receive a dynamic argument?

In general I think that multi argument typeguards (including instance and classmethods) may break type safety in some cases.

For example

def typeguard_using_attribute_of_second_argument(a, b) -> TypeGuard[Foo]: ...

if typeguard_using_attribute_of_second_argument(first, second):
  # Now first should be treated as a Foo
  # Maybe now the typeguard does not longer hold

Typing-sig mailing list -- typing-sig@python.org
To unsubscribe send an email to typing-sig-leave@python.org
Member address: skreft@gmail.com

Sebastian Kreft