Skip to content Skip to sidebar Skip to footer

Typehint Importing Module Dynamically Using Importlib

Give something as follows: import importlib module_path = 'mod' mod = importlib.import_module(module_path, package=None) print(mod.Foo.Bar.x) where mod.py is: class Foo: clas

Solution 1:

As was alluded to by @MisterMiyagi in the comments, I think the solution here is to use structural, rather than nominal, subtyping. Nominal subtyping is where we use direct class inheritance to define type relationships. For example, collections.Counter is a subtype of dict because it directly inherits from dict. Structural subtyping, however, is where we define types based on certain properties a class has or certain behaviours it displays. int is a subtype of typing.SupportsFloat not because it directly inherits from SupportsFloat (it doesn't), but because SupportsFloat is defined as a certain interface, and int satisfies that interface.

When type-hinting, we can define structural types using typing.Protocol. You could satisfy MyPy in this situation like this:

import importlib
from typing import cast, Protocol

classBarProto(Protocol):
    x: intclassFooProto(Protocol):
    Bar: type[BarProto]
    
classModProto(Protocol):
    Foo: type[FooProto]

module_path = "mod"
mod = cast(ModProto, importlib.import_module(module_path, package=None))

print(mod.Foo.Bar.x)

reveal_type(mod)
reveal_type(mod.Foo)
reveal_type(mod.Foo.Bar)
reveal_type(mod.Foo.Bar.x)

We've defined several interfaces here:

  • BarProto: in order to satisfy this interface, a type has to have an attribute x that's of type int.
  • FooProto: in order to satisfy this interface, a type has to have an attribute Bar that is a class of which instances satisfy the BarProto protocol.
  • ModProto: in order to satisfy this interface, a type has to have an attribute Foo that is a class of which instances satisfy the FooProto protocol.

Then, when importing the module, we use typing.cast to assert to the type-checker that the module we're importing satisfies the ModProto protocol.

Run it through MyPy, and it informs us it has inferred the following types:

main.py:18: note: Revealed type is "__main__.ModProto"  
main.py:19: note: Revealed type is "Type[__main__.FooProto]"
main.py:20: note: Revealed type is "Type[__main__.BarProto]"
main.py:21: note: Revealed type is "builtins.int"

Read more about structural subtyping in python here and here.

Post a Comment for "Typehint Importing Module Dynamically Using Importlib"