shishkebab
shishkebab

Reputation: 55

How to Handle Circularly Dependent Classes in Python

I have two classes Img and ImgAnalyzer each in their own module. ImgAnalyzer accepts an Img object as a parameter and performs various analyses on it, but I want to have the Img object to 'own' the ImgAnalyzer object, ie if I want to use the analyzer, I need to use it through the image.

ex:

img.py

from img_analyzer import ImgAnalyzer
class Img:
    def __init__(self, *args, **kwargs):
        # do thing
        self.analyzer = ImgAnalyzer(self)
    # other funcs

img_analyzer.py

from img import Img
class ImgAnalyzer:
    def __init__(self, img: Image):
        # do thing
    # other funcs

Then I could access the analyzer from the image:

img = Img(params)
img.analyzer.func()

This inevitably causes a circular import error. The classes should be in separate files because they have different functions and are too long to reasonably combine anyway. I looked around here and on other sites but couldn't figure out how to fix it. How can I have a class operated through and dependent on another class without causing errors?

Upvotes: 1

Views: 153

Answers (2)

Jim
Jim

Reputation: 458

Assuming that you only need to import Img in img_analyzer.py for the type checking

You can use the typing module's TYPE_CHECKING constant

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from img import Img

class ImgAnalyzer:
    def __init__(self, img: 'Image'):
        # do thing
    # other funcs

Per the typing documentation, typing.TYPECHECKING is a special constant that is assumed to be True by 3rd party static type checkers. It is False at runtime

Upvotes: 0

BatWannaBe
BatWannaBe

Reputation: 4510

In short, don't.

Circular imports are already a bad enough attempt at sharing code between two files/modules. It seems like you want something more like an include directive found in other languages but even then circular include causes errors. Your case is compounded by the fact that you have circularly dependent instances. Circular class instantiation is legal in Python, but it's messy and can't be split into separate files.

In your case it seem like you're using ImgAnalyzer as an extended methods package for Img; you even call it as such img.analyzer.func(). You can accomplish the same functionality by decoupling the classes from each other and making the import one-way. The identity of each instance of ImgAnalyzer was completely dependent on an instance of Img, so once we decouple that, we don't even NEED a class:

# img_analyzer.py

def func(img):
    # do stuff on img, intended to be Img instance
import img_analyzer

class Img:
    def __init__(self, *args, **kwargs):
        # do thing
    # other funcs

pic1 = Img(params)
img_analyzer.func(pic1)

Syntactically it does not look like Img owns img_analyzer or func, but it didn't in your original code either. func used to belong to the ImgAnalyzer class and could be used like ImgAnalyzer.func(ImgAnalyzer(img), img), for which img.analyzer.func() was just syntactic sugar. img_analyzer.func(pic1) is just as neat and concise while avoiding circular dependency. func still belongs to Img in the sense that it was designed specifically for Img instances.

Upvotes: 1

Related Questions