pythonpython-3.xpycharmsubclassing

PyCharm incorrectly assumes object is instance of BaseClass after calling issubclass()


I am running this test with Python 3.10.9 and PyCharm 2022.2.1 (Community Edition). This is an issue with PyCharm, not Python itself.

In my example code, I use a generator method that takes in a class type as an argument, and constructs the class. I want to make sure the class type that is passed is a subclass of BaseClass, so I use issubclass() on the class_type argument:

class BaseClass:
    def __init__(self, foo):
        self.foo = foo


class SubClass(BaseClass):
    def __init__(self):
        super().__init__(foo='hi')
        self.bar = "hello"


def generate_sub_class(class_type):
    assert issubclass(class_type, BaseClass)
    new_obj = class_type()      # <--- PyCharm warning: "Parameter 'foo' unfilled"
    return new_obj


# Main
new_subclass = generate_sub_class(SubClass)
print(f"Type of new_subclass: {type(new_subclass)}")
print(f"new_subclass.foo: {new_subclass.foo}")
print(f"new_subclass.bar: {new_subclass.bar}")     # <--- PyCharm warning: "Unresolved attribute reference
                                                   #                        'bar' for class 'BaseClass'"

This code works as expected when running in Python, as we get the following output:

Type of new_subclass: <class '__main__.SubClass'>
new_subclass.foo: hi
new_subclass.bar: hello

However, as you can see with the code above, as soon as I call issubclass(class_type, BaseClass), PyCharm now thinks that the constructed object is a BaseClass object. The constructor call has a warning because it is looking at the wrong constructor, and the new_subclass.bar call in Main has a warning because it doesn't see bar in BaseClass, even though it is a SubClass object as verified by the Python output.

Does anybody know why this would be happening? It seems like a PyCharm error to me, I have no idea why simply calling issubclass would make PyCharm assume the object is the BaseClass. Without the call to issubclass, PyCharm has no issues understanding that it is an instance of SubClass.

Thanks in advance for any help.


Solution

  • I suspect it's not really a bug, but a side effect of not providing any type hints. PyCharm starts with the assumption that class_type as the type Any, meaning any value could be assigned to the name and that value can be used with any operation, regardless of type.

    So what does PyCharm do with the assert statement? It tries to apply type narrowing, assigning class_type a more specific type than Any when the assertion is true. The most general type that makes the assertion True is BaseClass, so in the code that follows the assertion, it assumes that class_type has the type BaseClass, not Any. (If the assertion failed, it doesn't need to assume anything, because none of the following code would execute.)

    Once the type has been narrowed to BaseClass, the rest of the warnings are self-explanatory. BaseClass isn't provide the argument BaseClass.__init__ expects, and direct instances of BaseClass don't have bar attributes.

    At runtime, everything is fine because there are no assumptions about the type of class_type; it is SubClass, and so the code executes as expected.


    That said, I don't have PyCharm installed to test. mypy does nothing, which leads me to suspect that PyCharm is being overly aggressive in performing type narrowing compared to mypy. What happens if you provide explicit type hints, for example,

    from typing import TypeVar
    T = TypeVar('T', bound=BaseClass)
    
    def generate_sub_class(class_type: type[T]) -> T:
        assert issubclass(class_type, BaseClass)
        new_obj = class_type()
        return new_obj
    

    Here, the type hint provides the same amount of information that could be inferred from the assert statement, but since the type hint already implies that T could be bound to BaseClass or a subclass of BaseClass, no type narrowing should be necessary or applied.