Search code examples
pythonpycharmpython-typing

Pycharm type hints warning for classes instead of instances


I am trying to understand why pycharm warns me of wrong type when using an implementation of an abstract class with static method as parameter.

To demonstrate I will make a simple example. Let's say I have an abstract class with one method, a class that implements (inherits) this interface-like abstract class, and a method that gets the implementation it should use as parameter.

import abc


class GreetingMakerBase(abc.ABC):
    @abc.abstractmethod
    def make_greeting(self, name: str) -> str:
        """ Makes greeting string with name of person """


class HelloGreetingMaker(GreetingMakerBase):
    def make_greeting(self, name: str) -> str:
        return "Hello {}!".format(name)


def print_greeting(maker: GreetingMakerBase, name):
    print(maker.make_greeting(name))

hello_maker = HelloGreetingMaker()
print_greeting(hello_maker, "John")

Notice that in the type hinting of print_greeting I used GreetingMakerBase, and because isinstance(hello_maker, GreetingMakerBase) is True Pycharm is not complaining about it.

The problem is that I have many implementations of my class and dont want to make an instance of each, so I will make this make_greeting method static, like this:

class GreetingMakerBase(abc.ABC):
    @staticmethod
    @abc.abstractmethod
    def make_greeting(name: str) -> str:
        """ Makes greeting string with name of person """


class HelloGreetingMaker(GreetingMakerBase):
    @staticmethod
    def make_greeting(name: str) -> str:
        return "Hello {}!".format(name)


def print_greeting(maker: GreetingMakerBase, name):
    print(maker.make_greeting(name))


print_greeting(HelloGreetingMaker, "John")

This still works the same way, but apparently because the parameter in the function call is now the class name instead of an instance of it, Pycharm complains that: Expected type 'GreetingMakerBase', got 'Type[HelloGreetingMaker]' instead.

Is there a way I can solve this warning without having to instantiate the HelloGreetingMaker class?


Solution

  • When you are doing print_greeting(HelloGreetingMaker, "John"), you are not trying to pass in an instance of HelloGreetingMaker. Rather, you're passing in the class itself.

    The way we type this is by using Type[T], which specifies you want the type of T, rather then an instance of T. So for example:

    from typing import Type
    import abc
    
    class GreetingMakerBase(abc.ABC):
        @staticmethod
        @abc.abstractmethod
        def make_greeting(name: str) -> str:
            """ Makes greeting string with name of person """
    
    
    class HelloGreetingMaker(GreetingMakerBase):
        @staticmethod
        def make_greeting(name: str) -> str:
            return "Hello {}!".format(name)
    
    
    def print_greeting(maker: Type[GreetingMakerBase], name):
        print(maker.make_greeting(name))
    
    
    # Type checks!
    print_greeting(HelloGreetingMaker, "John")
    

    Note that Type[HelloGreetingMaker] is considered to be compatible with Type[GreetingMakerBase] -- Type[T] is covariant with respect to T.

    The Python docs on the typing module and the mypy docs have more details and examples if you want to learn more.