Typing objects in interdependent modules

The title of the issue might be cryptic, but here's what I was trying to mean. I have two modules located in the same subpackage, something like this: ./objects /message.py /chat.py message.py has functions that take in objects from chat.py as parameters and so does message.py. I tried to import objects from message.py import chat.py to use for type annotations and also from chat.py to message.py for the same reason but I get circular import errors. Is there a way to create types for these objects that can still be used by the type checker to track the actual objects without importing the objects from either class? I'm thinking of creating a separate module called types and put the types there, but I can't figure out how to map the types I'll create to the objects in those two files so that they can be used without circular import errors. Perhaps if there's some runtime check that can be done, I'm not so sure.

Assuming your circular imports are only because you are annotating types. You would likely want to use `typing.TYPE_CHECKING` and `from __future__ import annotations` (PEP 563). You can also use `"message.Object"` if you need to reference the objects outside of annotations, such as `TypeVar("T", bound="message.Object")`. https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING https://peps.python.org/pep-0563/ An example: ``` from __future__ import annotations from typing import TYPE_CHECKING, TypeVar if TYPE_CHECKING: from . import message T = TypeVar("T", bound="message.Object") def foo() -> message.Object: ... ``` On 07/11/2022 11:23, TobiasHT wrote:

If this is your own code and have the ability to change it, I recommend refactoring it to eliminate the circular dependency. You can typically do so by combining interdependent types into one module or by creating a common module that abstracts out types that are shared among other modules. It sounds like you were exploring the latter option, and I think that's a worthwhile exploration. The alternative, as others have pointed out, is to use TYPE_CHECKING conditionals to avoid a runtime circular dependency, but this simply masks the fact that you have an architectural cycle in your code — something that I try to avoid in my projects. Ideally, all module dependencies (implied or explicit) should form a DAG. Pyright has an optional check called `reportImportCycles` that performs this analysis and reports an error when a cycle is detected. I enable this check for my team's code bases, and we strictly adhere to the "no architectural cycles" policy. -- Eric Traut Contributor to Pyright and Pylance Microsoft

Assuming your circular imports are only because you are annotating types. You would likely want to use `typing.TYPE_CHECKING` and `from __future__ import annotations` (PEP 563). You can also use `"message.Object"` if you need to reference the objects outside of annotations, such as `TypeVar("T", bound="message.Object")`. https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING https://peps.python.org/pep-0563/ An example: ``` from __future__ import annotations from typing import TYPE_CHECKING, TypeVar if TYPE_CHECKING: from . import message T = TypeVar("T", bound="message.Object") def foo() -> message.Object: ... ``` On 07/11/2022 11:23, TobiasHT wrote:

If this is your own code and have the ability to change it, I recommend refactoring it to eliminate the circular dependency. You can typically do so by combining interdependent types into one module or by creating a common module that abstracts out types that are shared among other modules. It sounds like you were exploring the latter option, and I think that's a worthwhile exploration. The alternative, as others have pointed out, is to use TYPE_CHECKING conditionals to avoid a runtime circular dependency, but this simply masks the fact that you have an architectural cycle in your code — something that I try to avoid in my projects. Ideally, all module dependencies (implied or explicit) should form a DAG. Pyright has an optional check called `reportImportCycles` that performs this analysis and reports an error when a cycle is detected. I enable this check for my team's code bases, and we strictly adhere to the "no architectural cycles" policy. -- Eric Traut Contributor to Pyright and Pylance Microsoft
participants (3)
-
Eric Traut
-
Peilonrayz
-
TobiasHT