1. ホーム

Pythonがエラーを報告 循環的なインポートが原因である可能性が高い Solution

2022-03-02 14:46:42

<スパン これは、個々のPythonファイルが互いに参照し合うことによって引き起こされる循環参照問題が原因です。

解決方法 参照する必要のあるファイルを1つのファイルに分離し、一方的に参照されるようにする

Pythonで少し大きなプロジェクトを書くと、circular import、つまりcicular importの問題によくぶつかります。この投稿は、flaskで遭遇した問題をモデルにしています
cicular importが発生する理由と、pythonでimportファイルを使用する際にpythonが具体的に何をしているのかを説明します。

1. 循環型インポートの例

以前、循環型インポートの問題に遭遇したのですが、プロジェクトのファイル構成は大体以下のような感じでした。

flask_demo\
  app\
    auth\
      __init__.py
    __init__.py
  run_server.py


app ディレクトリの __init__.py ファイルには、次のように記述されています。

from flask import Flask
from flask_login import LoginManager

def create_app():
    app = Flask(__name__)

    from app.auth import auth_bp
    app.register_blueprint(auth_bp)

    return app
    
app = create_app()
login_manager = LoginManager(app)


app/auth ディレクトリの __init__.py ファイルには、次のように記述されています。

from flask import Blueprint
from app import login_manager

# Register Blueprint
auth_bp = Blueprint('auth_bp', __name__)


最後に run_server.py ファイルを実行します。

from app import app

app.run()


今度は、flaskのWebアプリが正常に起動せず、以下のようなエラーが報告されます。

Traceback (most recent call last):
  File "/Users/caoxin/work/python_project/flask_demo/run_server.py", line 10, in <module>
    from app import app
  File "/Users/caoxin/work/python_project/flask_demo/app/__init__.py", line 24, in <module>
    app = create_app()
  File "/Users/caoxin/work/python_project/flask_demo/app/__init__.py", line 17, in create_app
    from app.auth import auth_bp
  File "/Users/caoxin/work/python_project/flask_demo/app/auth/__init__.py", line 11, in <module>
    from app import login_manager
ImportError: cannot import name 'login_manager' from 'app' (/Users/caoxin/work/python_project/flask_demo/app/__init__.py)


これは典型的なcicular importの問題で、これを解決するには、pythonでimportを使用する際にコードが実際にどのように動作するかをよく理解する必要があります。

2.インポート実行プロセス

ファイルをインポートするとき、pythonはまずそのファイルが以前にインポートされたことがあるかどうかを調べ、以前にインポートされたことがある場合は、再びインポートされることはありません。ですから、もしモジュールA
コード内でBモジュールがインポートされ、Aモジュールの中でBモジュールがインポートされると、pythonの実行順序は次のようになります。

  • モジュールAの実行開始
  • Aの実行がインポートBに到達すると、モジュールA以降のコードの実行を停止し、代わりにモジュールBのコードの実行を開始する
  • モジュールBが最初からimport Aがあるところまで実行されると、この時点でpythonは はしません。 は戻ってAのコードの残りを実行し、Aモジュールを プロパティが初期化され、中断する前に はBモジュールに読み込まれます

上の例で分割してみましょう、app/ イニット .py の create_app() メソッドで from auth import auth_bp を使用すると、app/ が壊れます。 イニット .py が実行され、代わりに
auth/ イニット ここで重要なのは、この時点でapp/ イニット .py は app と login_manager の両方の属性で宣言されています。また、auth/ イニット .py は再び app モジュールからのインポートを希望しています。
login_managerプロパティです。明らかに、ここでエラーが報告されます。これを解決するには、コード構造を変更して、auth/のlogin_managerプロパティが、以下のようになるようにする必要があるでしょう。 イニット .py に到達する前に実行され、from app import
login_managerが実行された時点で、すでにappモジュールに定義されているため、以下のようになります。

from flask import Flask
from flask_login import LoginManager

login_manager = LoginManager() # Declare login_manager before the auth module runs

def create_app():
    app = Flask(__name__)
    login_manager.init_app(app)

    from app.auth import auth_bp
    app.register_blueprint(auth_bp)

    return app

app = create_app()


Pythonがインポートするときにどのように動作するかを理解することで、この循環インポート問題を簡単に分析し解決することができるのです。