Skip to content

Checking Generic Type at Runtime #13053

Closed as not planned
Closed as not planned

Description

@samuelstevens

Bug Report

When checking the type of a generic, mypy reports an error when (as far as I understand), I am still following the type contract I set.

To Reproduce

from typing import TypeVar

T = TypeVar("T")

def increment(obj: T) -> T:
    if isinstance(obj, int):
        return obj + 1
    else:
        return obj
$ mypy --strict scratch.py
scratch.py:12: error: Incompatible return value type (got "int", expected "T")
Found 1 error in 1 file (checked 1 source file)

Expected Behavior

I expect there to be no error because if obj is an int, then returning an int is the correct behavior.

Actual Behavior

Mypy reports an error.

Your Environment

  • Mypy version used: 0.961
  • Mypy command-line flags: --strict
  • Mypy configuration options from mypy.ini (and other config files): no other config
  • Python version used: 3.9.7
  • Operating system and version: Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-121-generic x86_64)

Activity

  1. erictraut commented on Jul 1, 2022

    @erictraut

    This behavior is correct because isinstance(obj, int) does not guarantee that obj is an int; it simply guarantees that it's a subtype of int. However, the __add__ method for int always returns an int, not a subtype of int.

    class IntSubclass(int): ...
    
    i1 = IntSubclass(0)
    i2 = increment(i1)
    
    # At runtime, i2 is an instance of 'int', not 'IntSubclass'.
    print(type(i2)) # <class 'int'>
  2. samuelstevens commented on Jul 2, 2022

    @samuelstevens
    Author

    That makes sense. Thank you for explainging!

    My actual use case is more complicated, however. Instead of int, I have some objects of a class that call some methods. Something like:

    from typing import TypeVar
    
    T = TypeVar("T")
    
    
    class Dummy:
        def __init__(self):
            self.state: int = 0
    
        def mutate_state(self):
            self.state += 1
    
    
    def handle(obj: T) -> T:
        if isinstance(obj, Dummy):
            obj.mutate_state()
            return obj
        else:
            return obj

    I get the same error:

    $ mypy --strict scratch.py
    scratch.py:17: error: Incompatible return value type (got "Dummy", expected "T")
    Found 1 error in 1 file (checked 1 source file)
    

    Even though obj might just be a subclass of Dummy, I am still returning obj directly which is still of type T. So shouldn't this be error-free?

  3. erictraut commented on Jul 2, 2022

    @erictraut

    Yes, your revised sample is type safe. I don't think mypy should emit an error in this case. By comparison, pyright does not.

    Here's a workaround for mypy:

    @overload
    def handle(obj: Dummy) -> Dummy: ...
    @overload
    def handle(obj: T) -> T: ...
    
    def handle(obj: T | Dummy) -> T | Dummy:
        if isinstance(obj, Dummy):
            obj.mutate_state()
            return obj
        else:
            return obj
  4. samuelstevens commented on Jul 2, 2022

    @samuelstevens
    Author

    Thanks for the suggestion; I will try it in my full case but I was hoping to avoid all the extra boilerplate! (I have half a dozen classes that have special isinstance cases.)

    I'll leave this open as a bug in mypy. If I have time, maybe I will be able to look into why this happens (probably not).

  5. ilevkivskyi commented on May 31, 2025

    @ilevkivskyi
    Member

    Yeah, this is not a bug. The revised example is going to be fixed in #19183

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions