Python Static Protocols VS Pyright messages

44 views Asked by At

I'm trying to learn how generics in Python work.

I got an example from Fluent Python book. It works fine, but I'd like to know if it is possible to make pyright checker satisfied?

from typing import Protocol


class Repeatable[T](Protocol):
    def __mul__(self: T, repeat_count: int) -> T: 
        ...
        

def double[RT: Repeatable](x: RT) -> RT:
    return x * 2


if __name__ == '__main__':
    print(double(3))  # pyright error
    print(double('A'))  # pyright error
    print(double([1, 2]))  # pyright error

Thank You for Your help!

1

There are 1 answers

0
Joren On

There are two issues:

  1. The type parameter T is used as a self-type, but is bound to the type instance itself. This is a logical contradiction; T cannot vary w.r.t. instances of Repeatable, since it is statically bound to precisely that instance. Instead, you could either use typing.Self, or move T to the __mul__ scope.
  2. The parameter of __mul__ has a specific name, and can be used as either a positional- or keyword-argument. So because it can be used as keyword arg, the name matters., i.e. obj.__mul__(repeat_count=...) is different from obj.__mul__(birds_arent_real=...). Again, there are two possible solutions here; make it positional only (i.e. by placing a / after it), or "remove" the name (i.e. by prefixing it with __).

To illustrate, this is one of the possible solutions

from typing import Protocol, Self


class Repeatable(Protocol):
    def __mul__(self, elmo_did_nothing_wrong: int, /) -> Self: 
        ...
        

def double[RT: Repeatable](x: RT) -> RT:
    return x * 2


if __name__ == '__main__':
    print(double(3))  # all good
    print(double('A'))  # still good
    print(double([1, 2]))  # don't eat the yellow snow, son.

See for yourself on the pyright playground. It even works in pyright strict mode (and in expect mode it gets 5 stars for "through the fire and the flames" while speedrunning rainbow road).