Lỗi xuất hiện ngay khi khởi động
Ứng dụng của bạn đang chạy tốt. Bạn thêm một import mới, và bây giờ nó không thể khởi động:
ImportError: cannot import name 'UserService' from partially initialized module 'app.services' (most likely due to a circular import)
Câu "most likely due to a circular import" là Python đang nói lịch sự. Đó chính xác là circular import. Module A import từ module B, và module B lại import ngược lại từ module A. Python bắt đầu tải A, gặp lệnh import B, bắt đầu tải B, lại gặp lệnh import A, và nhận được một module chưa hoàn chỉnh. Bất kỳ tên nào bạn cố import đều chưa tồn tại.
Tìm vòng lặp trước
Trước khi chỉnh sửa bất cứ thứ gì, hãy đọc toàn bộ traceback — không chỉ dòng cuối:
Traceback (most recent call last):
File "main.py", line 1, in <module>
from app.routes import user_routes
File "/app/routes.py", line 3, in <module>
from app.services import UserService
File "/app/services.py", line 2, in <module>
from app.models import User
File "/app/models.py", line 4, in <module>
from app.services import UserService
ImportError: cannot import name 'UserService' from partially initialized module 'app.services'
Đọc từ dưới lên. models.py import từ services.py, nhưng services.py đang trong quá trình tải dở khi Python đến đây. Vòng lặp tìm thấy: services → models → services.
Với các dự án lớn hơn khi chuỗi không rõ ràng, thêm một dòng debug tạm thời ở đầu các module nghi ngờ:
# Debug tạm thời — xóa sau khi tìm ra vòng lặp
import sys
print(f"Loading {__name__}, already loaded: {[m for m in sys.modules if 'app' in m]}")
Chạy entry point và quan sát thứ tự in ra. Vòng lặp sẽ lộ ra trong vài dòng.
Ba cách phá vỡ vòng lặp
Cách 1: Chuyển import vào trong hàm
Cách sửa nhanh nhất. Thay vì import ở cấp module, hãy import bên trong hàm thực sự cần dùng nó:
# services.py — TRƯỚC (gây circular import)
from app.models import User
class UserService:
def get_user(self, user_id):
return User.query.get(user_id)
# services.py — SAU (deferred import)
class UserService:
def get_user(self, user_id):
from app.models import User # Import ở đây, không phải ở cấp module
return User.query.get(user_id)
Python chỉ thực thi lệnh import khi get_user() được gọi, lúc đó cả hai module đã được tải hoàn toàn. Đây không phải pattern sạch nhất, nhưng nó rõ ràng và hoạt động ngay.
Cách 2: Tách code dùng chung ra module thứ ba
Cách sửa kiến trúc đúng đắn. Khi A và B import lẫn nhau, hầu như chúng luôn chia sẻ một khái niệm nên thuộc về module riêng. Tách nó ra:
# Trước:
# models.py import từ services.py
# services.py import từ models.py
# Sau: tạo app/base.py hoặc app/types.py
# base.py
from dataclasses import dataclass
@dataclass
class UserData:
id: int
email: str
# models.py
from app.base import UserData # Không cần import services nữa
# services.py
from app.base import UserData # Không cần import models nữa
Circular import là dấu hiệu cảnh báo. Hai module đó đang liên kết quá chặt chẽ, và việc tách ra này sửa thiết kế gốc, không chỉ là triệu chứng.
Cách 3: Dùng TYPE_CHECKING cho type hints
Nhiều circular import trong Python hiện đại tồn tại hoàn toàn chỉ cho type annotation — import đó không bao giờ cần thiết lúc runtime. Bảo vệ nó với TYPE_CHECKING:
from __future__ import annotations # Biến tất cả annotation thành string lúc runtime
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from app.services import UserService # Chỉ import khi type checking
class User:
def get_service(self) -> 'UserService': # Hoạt động bình thường
...
TYPE_CHECKING luôn là False lúc runtime — mypy và pyright coi nó là True khi phân tích tĩnh. Không có circular import lúc runtime, type safety đầy đủ lúc kiểm tra.
Pattern TYPE_CHECKING đầy đủ
Đáng để xem ví dụ trước/sau hoàn chỉnh, vì pattern này loại bỏ được một số lượng đáng ngạc nhiên các vòng lặp thực tế:
# Chưa sửa — circular import
# order.py
from app.customer import Customer
class Order:
def __init__(self, customer: Customer):
self.customer = customer
# customer.py
from app.order import Order
class Customer:
def get_orders(self) -> list[Order]:
...
# Đã sửa
# order.py
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from app.customer import Customer
class Order:
def __init__(self, customer: Customer): # Annotation giờ là string
self.customer = customer
# customer.py
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from app.order import Order
class Customer:
def get_orders(self) -> list[Order]: # Hoạt động
...
Kiểm tra lại sau khi sửa
Đừng chỉ khởi động lại và hi vọng. Kiểm tra từng module riêng lẻ từ thư mục gốc dự án:
python -c "from app.services import UserService; print('OK')"
python -c "from app.models import User; print('OK')"
python -c "from app.routes import user_routes; print('OK')"
Sau đó chạy toàn bộ test suite:
python -m pytest tests/ -x # -x dừng lại ở lỗi đầu tiên
Để tự động phát hiện import cycles, pylint có tính năng phát hiện vòng lặp tích hợp sẵn:
pip install pylint
pylint app/ --disable=all --enable=R0401 # R0401 = cyclic-import
Ngăn không để lỗi quay lại
Circular import thường lén lút quay lại trong các team lớn. Một vài thói quen giúp kiểm soát điều này:
- Phân lớp import: chọn một hướng phụ thuộc (ví dụ: routes → services → models → base) và không bao giờ import ngược chiều
- Thêm phát hiện vòng lặp vào CI: chạy
pylint --enable=R0401trong pipeline để vòng lặp bị phát hiện trước khi merge - Giữ
__init__.pygọn nhẹ: re-export tất cả từ__init__.pycủa một package là nguồn gốc phổ biến nhất của các vòng lặp ẩn - Mặc định dùng
TYPE_CHECKINGcho annotation — thêm vào checklist code review của bạn
Vẫn còn lỗi? Kiểm tra cache .pyc
Nếu vòng lặp đã được gỡ nhưng lỗi vẫn còn, bytecode cũ có thể đang phát lại import graph cũ. Xóa nó đi:
find . -name '*.pyc' -delete
find . -name '__pycache__' -type d -exec rm -rf {} +
python main.py
Chín trong mười trường hợp, một trong ba cách trên sẽ giúp bạn vượt qua lỗi này mà không cần chạm vào logic cốt lõi.

