Search code examples
pythonmixinstype-hintingmetaclass

How to typehint mixins if the target class for the mixin inherits from a metaclass?


Consider the following class and mixin:

class Target(ClassThatUsesAMetaclass):

    def foo(self):
        pass

class Mixin:

    def __init__(self):
        self.foo()  # type error: type checker doesn't know Mixin will have
                    # access to foo once in use.

class Combined(Mixin, Target):

    def __init__(self):
        Target.__init__(self)
        Mixin.__init__(self)

I'm trying to avoid the type checker error in the above scenario. One option is this:

from typing import Protocol

class Fooable(Protocol):
    def foo(self): ...

class Mixin(Fooable):

    def __init__(self):
        self.foo()

Would've worked great, except that Target inherits from a class that uses a metaclass, so Combined can't inherit from both Target and Mixin. So now I'm trying an alternative, annotating self in Mixin:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .this import Mixin, Target
    Mixin_T = type('Mixin_T', (Mixin, Target), {})


class Mixin:

    def __init__(self: Mixin_T):
        self.foo()  # No longer an error

class Combined(Mixin, Target):

    def __init__(self):
        Target.__init__(self)
        Mixin.__init__(self)  # Now this is an error: "Type[Mixin]" is not
                              # assignable to parameter "self"
                              # "Mixin" is incompatible with "Mixin_T"

So how am I supposed to win this aside from using # type: ignore?


Solution

  • I found a very simple solution:

    if TYPE_CHECKING:
        from .this import Target
        Mixin_T = Target
    else:
        Mixin_T = object
    
    class Mixin(Mixin_T):
        ...
    

    Now all of Target's methods are recognized within Mixin by the type checker, and there's no need to override the type of self into something imcompatible with Mixin. This might be a little awkward if the mixin is destined to all kinds of Target classes, but for my uses this is perfectly acceptable, since my case is a group of mixins extending a very specific target class.