순환 참조(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
파이썬에서 디자인 패턴 적용하기  (7) 2024.10.24
다양한 이미지 확장자와 확장자 변경하기  (6) 2024.10.23
Python 데코레이터 : 정적 메서드부터 데이터 클래스까지 알아보기  (0) 2024.09.16
'파이썬/Basic' 카테고리의 다른 글
  • 자료구조 : deque, Queue, heapq
  • next()와 제너레이터 표현식
  • 파이썬에서 디자인 패턴 적용하기
  • 다양한 이미지 확장자와 확장자 변경하기
코샵
코샵
나의 코딩 일기장
    반응형
  • 코샵
    끄적끄적 코딩 공방
    코샵
  • 전체
    오늘
    어제
    • 분류 전체보기 (517) N
      • 상품 추천 (33)
      • MongoDB (4)
      • 하드웨어 (5) N
      • 일기장 (4)
      • Unity (138)
        • Tip (41)
        • Project (1)
        • Design Pattern (8)
        • Firebase (6)
        • Asset (2)
      • 파이썬 (127)
        • Basic (40)
        • 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 (48)
        • 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)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 다비즈
  • 공지사항

  • 인기 글

  • 태그

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

  • 최근 글

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

티스토리툴바