Как обрабатывать циклически зависимые классы в Python

#python

Вопрос:

У меня два класса Img , и ImgAnalyzer у каждого свой модуль. ImgAnalyzer принимает Img объект в качестве параметра и выполняет над ним различные анализы, но я хочу, чтобы Img объект «принадлежал» ImgAnalyzer объекту, т. Е. Если я хочу использовать анализатор, мне нужно использовать его через изображение.

экс:

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
 

Затем я мог получить доступ к анализатору с изображения:

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

Это неизбежно приводит к циклической ошибке импорта.
Классы должны быть в отдельных файлах, потому что они имеют разные функции и в любом случае слишком длинные, чтобы их можно было разумно объединить. Я посмотрел здесь и на других сайтах, но не смог понять, как это исправить.
Как я могу управлять классом и зависеть от другого класса, не вызывая ошибок?

Комментарии:

1. ImgAnalyzer Действительно ли нужно импортировать Img ? Представляется вероятным, что все, что делает анализатор, он делает с помощью метода или атрибута Img переданного ему объекта, который не требует импорта.

2. @jasonharper Разве то, как я это структурировал, не является плохой практикой? И, если мне нужно проверить, что переданный параметр является Img объектом, например, для обработки ошибок, мне понадобится инструкция import.

Ответ №1:

Короче говоря, не надо.

Циклический импорт уже является достаточно плохой попыткой совместного использования кода между двумя файлами/модулями. Похоже, вам нужно что-то более похожее на директиву include, найденную на других языках, но даже в этом случае циклическое включение вызывает ошибки. Ваше дело усугубляется тем фактом, что у вас есть циклически зависимые экземпляры. Создание экземпляра циклического класса в Python законно, но это беспорядочно и не может быть разделено на отдельные файлы.

В вашем случае кажется, что вы используете ImgAnalyzer расширенный пакет методов для Img ; вы даже называете его таковым img.analyzer.func() . Вы можете выполнить ту же функциональность, отделив классы друг от друга и сделав импорт односторонним. Идентичность каждого экземпляра ImgAnalyzer полностью зависела от экземпляра Img , поэтому, как только мы его разделим, нам даже не ПОНАДОБИТСЯ класс:

 # 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)
 

Синтаксически это не похоже Img на owns img_analyzer или func , но в вашем исходном коде этого тоже не было. func раньше принадлежал к ImgAnalyzer классу и мог использоваться как ImgAnalyzer.func(ImgAnalyzer(img), img) , для чего img.analyzer.func() был просто синтаксический сахар. img_analyzer.func(pic1) так же аккуратно и лаконично, избегая при этом циклической зависимости. func все еще принадлежит Img в том смысле, что он был разработан специально для Img экземпляров.

Ответ №2:

Предполагая, что вам нужно импортировать Img только img_analyzer.py для проверки типа

Вы можете использовать константу TYPE_CHECKING модуля ввода

 from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from img import Img

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

 

Согласно typing документации, typing.TYPECHECKING это специальная константа, которая предполагается True сторонними контролерами статического типа. Это происходит False во время выполнения