순환 참조(Circular Import) 이해하기와 해결 방법

2024. 12. 6. 10:45·파이썬/Basic
반응형

소개

Python 개발을 하다 보면 "most likely due to a circular import" 라는 에러를 자주 만나게 됩니다. 이는 모듈 간 순환 참조로 인해 발생하는 문제입니다. 이번 글에서는 순환 참조가 무엇인지, 어떤 문제를 일으키는지, 그리고 해결 방법에 대해 알아보겠습니다.

순환 참조란?

순환 참조는 두 개 이상의 모듈이 서로를 import할 때 발생합니다.

문제가 되는 코드 예시

# user.py
from post import Post

class User:
    def __init__(self, name):
        self.name = name
        self.posts = []

    def create_post(self, content):
        post = Post(content, self)
        self.posts.append(post)
        return post

# post.py
from user import User

class Post:
    def __init__(self, content, author: User):
        self.content = content
        self.author = author

순환 참조가 발생하는 일반적인 상황

1. 모델 간 상호 참조

# models/user.py
from models.team import Team

class User:
    def __init__(self):
        self.team: Team = None

# models/team.py
from models.user import User

class Team:
    def __init__(self):
        self.members: List[User] = []

2. 서비스 레이어 간 상호 의존

# services/user_service.py
from services.notification_service import NotificationService

class UserService:
    def __init__(self):
        self.notification_service = NotificationService()

# services/notification_service.py
from services.user_service import UserService

class NotificationService:
    def __init__(self):
        self.user_service = UserService()

해결 방법

1. 지연 임포트 (Lazy Import)

class User:
    def create_post(self, content):
        # 필요한 시점에 임포트
        from post import Post
        post = Post(content, self)
        self.posts.append(post)
        return post

2. 의존성 주입 (Dependency Injection)

# user.py
class User:
    def __init__(self, name):
        self.name = name
        self.posts = []

    def create_post(self, post_class, content):
        post = post_class(content, self)
        self.posts.append(post)
        return post

# post.py
class Post:
    def __init__(self, content, author):
        self.content = content
        self.author = author

# usage
from user import User
from post import Post

user = User("John")
post = user.create_post(Post, "Hello World")

3. 중간 레이어 도입

# models.py (중간 레이어)
class UserModel:
    name: str
    posts: list

class PostModel:
    content: str
    author_id: int

# user.py
from models import PostModel

class User:
    def create_post(self, content):
        return PostModel(content=content, author_id=self.id)

# post.py
from models import UserModel

class Post:
    def get_author(self):
        return UserModel.get(self.author_id)

4. Type Hints에서 문자열 사용

from typing import List, TYPE_CHECKING

if TYPE_CHECKING:
    from post import Post
    from user import User

class User:
    posts: List['Post']  # 문자열로 타입 힌트 제공

class Post:
    author: 'User'  # 문자열로 타입 힌트 제공

5. 공통 인터페이스 사용

# interfaces.py
from abc import ABC, abstractmethod

class UserInterface(ABC):
    @abstractmethod
    def create_post(self, content): pass

class PostInterface(ABC):
    @abstractmethod
    def get_author(self): pass

# user.py
from interfaces import UserInterface, PostInterface

class User(UserInterface):
    def create_post(self, content):
        # 구현

# post.py
from interfaces import PostInterface

class Post(PostInterface):
    def get_author(self):
        # 구현

각 해결 방법의 장단점

해결 방법 장점 단점
지연 임포트 - 구현이 간단함
- 기존 코드 수정이 최소화됨
- 성능 오버헤드 발생 가능
- 코드 가독성 저하
의존성 주입 - 느슨한 결합
- 테스트 용이성
- 추가 코드 작성 필요
- 복잡도 증가
중간 레이어 - 명확한 구조
- 모듈간 의존성 감소
- 추가 코드 필요
- 오버엔지니어링 가능성
문자열 타입 힌트 - 타입 힌트 유지
- 최소한의 코드 변경
- IDE 지원 제한적
- 런타임 타입 체크 불가
공통 인터페이스 - 명확한 계약
- 유지보수성 향상
- 많은 보일러플레이트
- 복잡도 증가

권장하는 접근 방법

  1. 먼저 아키텍처 설계 단계에서 순환 참조를 피하도록 구조 검토
  2. 작은 규모의 순환 참조는 지연 임포트로 해결
  3. 큰 규모의 애플리케이션에서는 의존성 주입 또는 중간 레이어 도입 고려
  4. 타입 힌트가 중요한 경우 문자열 타입 힌트 사용
  5. 확장성이 중요한 경우 인터페이스 기반 설계 고려

마치며

순환 참조는 피할 수 있다면 피하는 것이 좋지만, 때로는 불가피한 경우가 있습니다. 이런 경우 프로젝트의 규모와 요구사항에 맞는 적절한 해결 방법을 선택하는 것이 중요합니다. 특히 큰 규모의 프로젝트에서는 순환 참조 문제를 해결하기 위해 아키텍처 수준의 결정이 필요할 수 있습니다.

저작자표시 비영리 변경금지 (새창열림)

'파이썬 > Basic' 카테고리의 다른 글

자료구조 : deque, Queue, heapq  (0) 2024.12.10
next()와 제너레이터 표현식  (1) 2024.12.09
파이썬에서 디자인 패턴 적용하기  (8) 2024.10.24
다양한 이미지 확장자와 확장자 변경하기  (6) 2024.10.23
Python 데코레이터 : 정적 메서드부터 데이터 클래스까지 알아보기  (0) 2024.09.16
'파이썬/Basic' 카테고리의 다른 글
  • 자료구조 : deque, Queue, heapq
  • next()와 제너레이터 표현식
  • 파이썬에서 디자인 패턴 적용하기
  • 다양한 이미지 확장자와 확장자 변경하기
코샵
코샵
나의 코딩 일기장
    반응형
  • 코샵
    끄적끄적 코딩 공방
    코샵
    • 분류 전체보기 (720) N
      • 상품 추천 (220) N
      • MongoDB (4)
      • 하드웨어 (15) N
      • 일기장 (4)
      • Unity (138)
        • Tip (41)
        • Project (1)
        • Design Pattern (8)
        • Firebase (6)
        • Asset (2)
      • 파이썬 (13)
        • Basic (41)
        • OpenCV (8)
        • Pandas (15)
        • PyQT (3)
        • SBC(Single Board Computer) (1)
        • 크롤링 (14)
        • Fast API (29)
        • Package (6)
      • Linux (4)
      • C# (97)
        • Algorithm (11)
        • Window (7)
      • TypeScript (41)
        • CSS (10)
      • Git (11)
      • SQL (5)
      • Flutter (10)
        • Tip (1)
      • System (1)
      • BaekJoon (6)
      • Portfolio (2)
      • MacOS (1)
      • 유틸리티 (1)
      • 서비스 (6)
      • 자동화 (3)
      • Hobby (10)
        • 물생활 (10)
        • 식집사 (0)
  • 인기 글

  • 태그

    Python
    cv2
    appdevelopment
    스크립트 실행
    codingtips
    쇼핑몰리뷰
    리뷰관리
    codingcommunity
    unity
    rtsp
    긴유통기한우유
    카페24리뷰이관
    programmerlife
    리뷰이관
    파이썬
    상품 리뷰 크롤링
    스마트스토어리뷰
    list
    라떼우유
    믈레코비타멸균우유
    스크립트 실행 순서
    셀레니움
    devlife
    유니티
    learntocode
    카페24리뷰
    리스트
    programming101
    C#
    ipcamera
  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
코샵
순환 참조(Circular Import) 이해하기와 해결 방법
상단으로

티스토리툴바