Skip to content

Inheritance broken when testing isinstance? #6386

Closed

Description

@rldleblanc

I don't know if I'm running into a bug or I'm just not doing things right.

I was able to minimize my problem to this using Python 3.5 and mypy 0.660:

from abc import ABC, abstractmethod                                                
from typing import Any, Optional, Type, Union                                      
                                                                                   
class A(ABC):                                                                      
    pass                                                                           
                                                                                   
class AA(A):                                                                       
    pass                                                                           
                                                                                   
class AB(A):                                                                       
    pass                                                                           
                                                                                   
class M(object):                                                                   
    pass                                                                           
                                                                                   
class MA(M, AA):                                                                   
    pass                                                                           
                                                                                   
class MB(M, AB):                                                                   
    pass                                                                           
                                                                                   
MYTYPES = Union[MA, MB]                                                            
                                                                                   
class T(object):                                                                   
    def __init__(self, newtype:Type[MYTYPES]=MA) -> None:                          
        assert isinstance(newtype, type(M))                                        
        self._new_type = newtype    # type: Type[MYTYPES]                          
                                                                                   
    def test(self) -> A:                                                           
        ret = self._new_type()                                                     
        print(type(ret))                                                           
        return ret                                                                 
                                                                                   
x = T()                                                                            
print(isinstance(x.test(), A)) # True                                              
y = T(MB)                                                                          
print(isinstance(y.test(), A)) # True

My results are:

$ python3 -m mypy mixin_test.py
mixin_test.py:27: error: Incompatible types in assignment (expression has type "Type[M]", variable has type "Union[Type[MA], Type[MB]]")
$ python3 mixin_test.py                                                                
<class '__main__.MA'>
True
<class '__main__.MB'>
True

It seems that the assert is causing mypy to not realize that the object is still of type 'A'. I just want to make sure that the item passed in has the MixIn 'M' so that it can support the functions that I need. It is quite possible that I'm approaching this whole thing wrong.

Activity

  1. ilevkivskyi commented on Feb 13, 2019

    @ilevkivskyi
    Member

    Mypy has special treatment of assert isinstance(obj, ...), they work as runtime-checked casts, and are normally used to narrow types. In your case it actually widens the type. I would say, we should not widen type after an assert (unless I am missing some use cases).

    Anyway, IIUC you should just do this:

    from typing import TYPE_CHECKING
    
    ...
    
    if not TYPE_CHECKING:
        assert isinstance(newtype, type(M))
    
    ...

    (Another question is that we use runtime expression -- function call -- for static checks, which is probably bad.)

  2. rldleblanc commented on Feb 13, 2019

    @rldleblanc
    Author

    Mypy has special treatment of assert isinstance(obj, ...), they work as runtime-checked casts, and are normally used to narrow types. In your case it actually widens the type. I would say, we should not widen type after an assert (unless I am missing some use cases).

    I don't know that I'm widening the type. My class T (is actually an implementation of an ABC) that requires MixIn M to work properly. The ABC has base functionally and requires A at a minimum. Class MA has both M and A which satisfies the specific needs of class T and has the generic stuff to satisfy the ABC of class T. So, I just want to make sure that M is only accepted for class T which returns objects of class A which could be an MA object. What you proposed looks like an acceptable solution. Too bad types aren't enforced at run time, it would make all this simpler.

    Anyway, IIUC you should just do this:

    from typing import TYPE_CHECKING
    
    ...
    
    if not TYPE_CHECKING:
        assert isinstance(newtype, type(M))
    
    ...

    (Another question is that we use runtime expression -- function call -- for static checks, which is probably bad.)

    I'm not sure what you mean by this. Are you talking about the newtype:Type[MYTYPES]=MA in def __init__(self, newtype:Type[MYTYPES]=MA) -> None:? I'm still pretty new at all of this.

  3. ilevkivskyi commented on Feb 17, 2019

    @ilevkivskyi
    Member

    I'm not sure what you mean by this.

    Using the call type(M) as a source of static type information. Anyway, this is gray area, because type() is a "special" function.

    Btw I just noticed that logic in your code is wrong

    >>> class M: ...
    ... 
    >>> isinstance(int, type(M))
    True

    This is because type of C is just type. What you want is probably assert issubclass(newtype, M).

  4. added a commit that references this issue on Feb 23, 2019
    ed0dcad
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions