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 int
s, string
s, and float
s (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:
- 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. - 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:
- 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
- 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)