Home > Software design >  Can Python type hints handle a generic class changing its own type in a method?
Can Python type hints handle a generic class changing its own type in a method?

Time:01-02

I have an existing class that changes an important type upon a certain method call. It looks something like this:

class RememberLast:
    def __init__(self, last):
        self._last = last

    def set(self, new_last):
        self._last = new_last

    def get(self):
        return self._last

remember = RememberLast(5)
type(remember.get())  # int
remember.set('wow')
type(remember.get())  # str
remember.set(4.5)
type(remember.get())  # float

Ideally the type of remember would change from RememberLast[int] to RememberLast[str] and then to RememberLast[float]. Is there a way to represent this situation with type hints?

Returning self with a different type hint in set() isn't ideal because there are existing callers. For these existing callers that don't use the return value, the type would stay as RememberLast[int] even though the type was "destroyed" and isn't correct anymore.

The existing class I'm referring to is twisted.internet.defer.Deferred, which allows chaining callbacks. The type of the last return value becomes the parameter for the next callback. So the type of a Deferred can be thought of as the type of the last callback added to it.

CodePudding user response:

I'm going to assume that RememberLast must be able to handle things besides ints, strings, and floats (or more than a finite union of types), because otherwise you could just use Union[int, str, float].

I'm unfortunately pessimistic that you can use an annotation more specific than Any for two reasons:

  1. mypy's own's documentation suggests using Any for dynamically typed code. If it was possible to use something more specific, they would have said so.
  2. This StackOverflow question asks something similar, and the accepted answer is basically "refactor so you can explicitly define the expected types, or use Any".

CodePudding user response:

Unfortunately, type hints in python doesn't prevent the type from changing for example in your snippet if you used the type hints as bellow it will give the same output:

class RememberLast:
    def __init__(self, last: int):
        self._last = last

    def set(self, new_last: int):
        self._last = new_last

    def get(self):
        return self._last

remember = RememberLast(5)
type(remember.get())  # int
remember.set('wow')
type(remember.get())  # str
remember.set(4.5)
type(remember.get())  # float

There are two ways to force the methods to take only specific type:

  1. Assert with isinstance:
class RememberLast:
    def __init__(self, last: int):
        assert isinstance(last, int)
        self._last = last

    def set(self, new_last: int):
        assert isinstance(last, int)
        self._last = new_last

    def get(self):
        assert isinstance(last, int)
        return self._last

remember = RememberLast(5)
type(remember.get())  # int
remember.set('wow')
type(remember.get())  # raises AssertionError
remember.set(4.5)
type(remember.get())  # raises AssertionError
  1. use mypy with type hinting before you deploy your code to make sure that types are matching correctly
mypy my_python_script.py

this should be the output

my_python_script.py:26: error: Argument 1 to "set" of "RememberLast" has incompatible type "str"; expected "int"
my_python_script.py:28: error: Argument 1 to "set" of "RememberLast" has incompatible type "float"; expected "int"
Found 2 errors in 1 file (checked 1 source file)
  • Related