1

In order to split a large class into multiple files, this answer, recommends using the import command at the class level to load methods whose definitions can be moved to other modules. As a minimal example,

class_def.py:

class C:
    from _methods import m

_methods.py:

def m(self):
    return "hello"

Normally, most IDEs which feature code completion will recognize functions which are defined in some class as bound methods, and self will automatically be recognized as having the type of the class where the method was defined. Unfortunately, in the above situation, I have not defined m inside of a class. It is impossible to tell from looking at just _methods.py that self should have type C.

In the definition of m, if I insert a line which starts with self., my IDE cannot suggest m or any other methods which I might have implemented in C.

The obvious solution would be to add a type hint:

from class_def import C

def m(self: C):
    return "hello"

But now we have a circular import: the definition of C imports _methods, but _methods imports C. How can I create a type hint without introducing a circular import?

I am currently using Python 3.7, but I would also be interested in solutions requiring later versions.

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
Ben Mares
  • 1,764
  • 1
  • 15
  • 26
  • 1
    If a class becomes that big, that you have to split it, there's a good chance that something in your design is suboptimal. I would suggest spending some time in refactoring. – finswimmer Aug 03 '20 at 18:15
  • I find it hard to imagine a more ideal way than this to refactor a large class which represents an object that should have a large amount of functionality. But that could be my ignorance (I'm not a programmer). – Ben Mares Aug 03 '20 at 19:53
  • Does this answer your question? [Python type hinting without cyclic imports](https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports) – Georgy Aug 03 '20 at 21:26
  • @Georgy, almost. I think the answer below answers that question. – Ben Mares Aug 04 '20 at 07:00

1 Answers1

4
  1. Fix the circular import by using the typing.TYPE_CHECKING flag to import C only during type checking.

  2. This leaves the value of C undefined at runtime. Either enclose it in quotes ("C") or import __future__.annotations:

_methods.py variant 1:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from class_def import C

def m(self: "C"):
    return "hello"

_methods.py variant 2:

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from class_def import C

def m(self: C):
    return "hello"
Ben Mares
  • 1,764
  • 1
  • 15
  • 26