반응형
소개
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 지원 제한적 - 런타임 타입 체크 불가 |
공통 인터페이스 | - 명확한 계약 - 유지보수성 향상 |
- 많은 보일러플레이트 - 복잡도 증가 |
권장하는 접근 방법
- 먼저 아키텍처 설계 단계에서 순환 참조를 피하도록 구조 검토
- 작은 규모의 순환 참조는 지연 임포트로 해결
- 큰 규모의 애플리케이션에서는 의존성 주입 또는 중간 레이어 도입 고려
- 타입 힌트가 중요한 경우 문자열 타입 힌트 사용
- 확장성이 중요한 경우 인터페이스 기반 설계 고려
마치며
순환 참조는 피할 수 있다면 피하는 것이 좋지만, 때로는 불가피한 경우가 있습니다. 이런 경우 프로젝트의 규모와 요구사항에 맞는 적절한 해결 방법을 선택하는 것이 중요합니다. 특히 큰 규모의 프로젝트에서는 순환 참조 문제를 해결하기 위해 아키텍처 수준의 결정이 필요할 수 있습니다.
'파이썬 > Basic' 카테고리의 다른 글
자료구조 : deque, Queue, heapq (0) | 2024.12.10 |
---|---|
next()와 제너레이터 표현식 (1) | 2024.12.09 |
파이썬에서 디자인 패턴 적용하기 (7) | 2024.10.24 |
다양한 이미지 확장자와 확장자 변경하기 (5) | 2024.10.23 |
Python 데코레이터 : 정적 메서드부터 데이터 클래스까지 알아보기 (0) | 2024.09.16 |