"ImportError: cannot import name 'User' from partially initialized module"
이런 에러 메시지를 본 순간, Django 개발자라면 누구나 한숨이 나올 것입니다. 특히 프로젝트가 커질수록 자주 마주치게 되는 순환 참조(Circular Import) 문제입니다.
27년간 다양한 프로젝트를 경험하면서 이 문제로 고생하는 개발자들을 정말 많이 봤습니다. 오늘은 이 문제가 왜 발생하는지, 어떻게 해결하는지 실제 코드 예시와 함께 정리해보겠습니다.
🔥 문제 상황: 이런 코드 본 적 있나요?
케이스 1: 모델 간 순환 참조
models/user.py
from django.db import models
from .post import Post # 이 부분이 문제!
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
def get_recent_posts(self):
return Post.objects.filter(author=self)[:5]
models/post.py
from django.db import models
from .user import User # 여기서도 User를 import!
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
def get_author_info(self):
return f"{self.author.name} ({self.author.email})"
실행하면?
ImportError: cannot import name 'Post' from partially initialized module 'myapp.models.post'
케이스 2: 뷰와 유틸리티 함수 간 순환 참조
views.py
from django.shortcuts import render
from .utils import send_notification
def create_post(request):
# 포스트 생성 로직
post = Post.objects.create(...)
send_notification(post) # utils의 함수 호출
return render(request, 'success.html')
utils.py
from django.core.mail import send_mail
from .views import get_user_context # 뷰에서 함수를 가져옴
def send_notification(post):
context = get_user_context(post.author) # 순환 참조 발생!
send_mail(...)
💡 해결책 1: 지연 임포트(Lazy Import) 사용
가장 간단한 해결책은 함수 내부에서 import하는 것입니다.
수정된 models/user.py
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
def get_recent_posts(self):
from .post import Post # 함수 내부에서 import
return Post.objects.filter(author=self)[:5]
장점: 빠르고 간단한 해결
단점: 함수가 호출될 때마다 import 비용 발생
💡 해결책 2: 문자열 참조 사용
Django ORM에서는 모델 관계를 문자열로 참조할 수 있습니다.
models/post.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
# 문자열로 참조 (앱이름.모델명 형식)
author = models.ForeignKey('myapp.User', on_delete=models.CASCADE)
def get_author_info(self):
return f"{self.author.name} ({self.author.email})"
같은 앱 내에서는 모델명만 사용 가능:
author = models.ForeignKey('User', on_delete=models.CASCADE)
💡 해결책 3: 모델 구조 재설계
가장 근본적인 해결책은 모델 관계를 명확히 정의하는 것입니다.
models.py (단일 파일)
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
# 비즈니스 로직은 별도 서비스 파일로 분리
services/user_service.py
from ..models import User, Post
class UserService:
@staticmethod
def get_recent_posts(user_id, limit=5):
return Post.objects.filter(author_id=user_id)[:limit]
@staticmethod
def get_user_stats(user_id):
user = User.objects.get(id=user_id)
post_count = Post.objects.filter(author=user).count()
return {
'user': user,
'post_count': post_count
}
💡 해결책 4: Django의 get_model() 활용
Django가 제공하는 get_model() 함수를 사용하는 방법입니다.
from django.apps import apps
class User(models.Model):
name = models.CharField(max_length=100)
def get_recent_posts(self):
Post = apps.get_model('myapp', 'Post')
return Post.objects.filter(author=self)[:5]
🚨 실제 프로젝트에서 자주 보는 복잡한 케이스
케이스: Signals에서 발생하는 순환 참조
models/user.py
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from .notification import create_welcome_notification
class User(models.Model):
name = models.CharField(max_length=100)
@receiver(post_save, sender=User)
def user_created(sender, instance, created, **kwargs):
if created:
create_welcome_notification(instance) # 순환 참조 위험!
해결책: signals.py 분리
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender='myapp.User') # 문자열 참조 사용
def user_created(sender, instance, created, **kwargs):
if created:
from .services import NotificationService
NotificationService.create_welcome_notification(instance)
⚡ 프로 팁: 순환 참조를 예방하는 프로젝트 구조
myproject/
├── models/
│ ├── __init__.py
│ ├── base.py # 공통 Abstract 모델
│ ├── user.py # User 관련 모델만
│ └── content.py # Post, Comment 등
├── services/ # 비즈니스 로직 분리
│ ├── user_service.py
│ └── content_service.py
├── utils/ # 순수 유틸리티 함수
└── views/ # 뷰 로직만
핵심 원칙:
- 단일 책임: 각 파일은 하나의 관심사만 담당
- 의존성 방향: 항상 한 방향으로만 의존
- 레이어 분리: 모델 → 서비스 → 뷰 순서로 의존
🛠️ 디버깅 팁: 순환 참조 빠르게 찾기
Python의 importlib 모듈로 순환 참조를 확인할 수 있습니다:
import sys
import importlib
def check_circular_import(module_name):
try:
importlib.import_module(module_name)
print(f"✅ {module_name} 정상 import")
except ImportError as e:
print(f"❌ {module_name} import 실패: {e}")
# sys.modules를 확인해서 부분적으로 로드된 모듈 찾기
for name, module in sys.modules.items():
if hasattr(module, '__file__') and module.__file__ is None:
print(f"🔍 부분 로드된 모듈: {name}")
🎯 결론: 순환 참조는 설계의 문제
순환 참조는 대부분 잘못된 아키텍처 설계에서 발생합니다.
- ✅ 모델은 데이터 구조만 정의
- ✅ 비즈니스 로직은 서비스 레이어로 분리
- ✅ 문자열 참조나 지연 import 적극 활용
- ✅ 의존성 방향을 명확히 설계
💬 여전히 해결이 안 되시나요?
복잡한 프로젝트일수록 순환 참조 문제는 더욱 골치 아픕니다. 특히 레거시 코드에서는 더욱 그렇죠.
27년 경력의 시니어 개발자가 여러분의 Django 프로젝트를 직접 진단하고 해결해드립니다.
- 🔍 코드 구조 분석 및 문제점 파악
- ⚡ 긴급 버그 수정 (24시간 내)
- 🏗️ 아키텍처 리팩토링 제안
- 📚 팀원 교육 및 가이드 제공
"혼자 고민하지 마세요. 문제를 빠르게 해결하고 다음 스텝으로 나아가세요!"

'프로그래밍 > Python' 카테고리의 다른 글
| 🐌 Django에서 쿼리가 느린 진짜 이유: 인덱스 누락 문제 해결하기 (0) | 2025.09.16 |
|---|---|
| 🐌 Django 성능의 가장 큰 적: N+1 쿼리 문제 완전 정복 (0) | 2025.09.12 |
| 🚨 Django 프로덕션에서 DEBUG=True? 당신의 서비스가 위험합니다! (0) | 2025.09.11 |
| [Python] 로또 번호를 맞춰 볼까? #2 (0) | 2024.07.22 |
| [Python] 로또 번호를 만들어 볼까? #1 (1) | 2024.07.15 |