質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
LINE Messaging API

LINE Messaging APIは、メッセージの送信・返信ができるAPIです。Web APIを経由しアプリケーションサーバとLINEのAPIでやり取りが可能。複数のメッセージタイプや分かりやすいAPIリファレンスを持ち、グループチャットにも対応しています。

Django

DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

1回答

1407閲覧

Djangoの循環インポートエラーの回避について

TAK0811

総合スコア2

LINE Messaging API

LINE Messaging APIは、メッセージの送信・返信ができるAPIです。Web APIを経由しアプリケーションサーバとLINEのAPIでやり取りが可能。複数のメッセージタイプや分かりやすいAPIリファレンスを持ち、グループチャットにも対応しています。

Django

DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2022/10/02 09:56

編集2022/10/03 07:25

前提

Django・LINE messagingAPI(SDK)によるチャットボット開発
Django:3.2.8
Pyrhon:3.9.13

実現したいこと

views.pyにて記載のdef sendMessage()を別途、作成したutilsディレクトリ内create_screen.py に移したいが、cannot import のエラーが発生する。

エラーコードを解消するために、どのように書き換えれば良いかご教示頂けますと幸いです。

発生している問題・エラーメッセージ

ImportError: cannot import name 'line_bot_api' from partially initialized module 'bot.views' (most likely due to a circular import)(/Users/TAK/Desktop/sdk/djangobot/bot/views.py)

該当のソースコード

<views.py> import os from django.http import HttpResponseForbidden, HttpResponse from django.views.decorators.csrf import csrf_exempt from linebot import LineBotApi, WebhookHandler from linebot.exceptions import InvalidSignatureError from linebot.models import MessageEvent, TextMessage from dotenv import load_dotenv from utils.create_screen import carouselTemplate load_dotenv() line_bot_api = LineBotApi(os.environ["CHANNEL_ACCESS_TOKEN"]) handler = WebhookHandler(os.environ["CHANNEL_SECRET"]) @csrf_exempt def callback(request): signature = request.META['HTTP_X_LINE_SIGNATURE'] body = request.body.decode('utf-8') try: handler.handle(body, signature) except InvalidSignatureError: HttpResponseForbidden() return HttpResponse('OK', status=200) @handler.add(MessageEvent, message=TextMessage) def handle_image_message(event): line_bot_api.reply_message( event.reply_token, messages = carouselTemplate() ) #以下の関数をこちらの配置で実行するとエラーは発生しないが、create_screen.pyに移動させるとエラーが発生します。 def sendMessage(): line_bot_api.broadcast(TextSendMessage(text='Hello World!')) sendMessage()
<create_screen.py> from linebot.models import TextSendMessage from bot.views import line_bot_api #上記view.pyに記述していたsendMessage()関数をここに移行しエラーなく実行させたいがImportErrorが発生。 def sendMessage(): line_bot_api.broadcast(TextSendMessage(text='Hello World!')) sendMessage()

試したこと

エラーメッセージの most likely due to a circular import から以下を試しましたが変わりませんでした。
from utils.create_screen import carouselTemplate → * 
(反対にcreate_screen.py の import line_bot_api → * )
参考元:https://www.kthksgy.com/python/python-circular-dependency/
※【解決策1】は勉強不足により、本課題に置き換えられず現状試せておりません。

何卒よろしくお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

quickquip

2022/10/03 02:40

質問が書かれていなくて、回答しづらいと感じました。
TAK0811

2022/10/03 07:27

ご指摘頂き誠にありがとうございます。 稚拙な説明で大変失礼致しました。文章修正致しましたので、改めてご確認頂けますと幸いです。 お手数をお掛け致しますが、よろしくお願い致します。
guest

回答1

0

ベストアンサー

スクリプトをimportした場合、1回目のimportではスクリプトが実行され、2回目のimportでは何も実行されずに前のimportされたモジュールオブジェクトが参照されます。
そのことを踏まえて、以下のコードを見てください。

python

1# hoge.py 2 3def A(): 4 pass 5import piyo 6def B(): 7 pass

このhoge.pyが他からimportされたとします。
import piyo」の前まで実行されると、その前にあるAの関数は定義されますが、その後にあるBの関数はまだ定義されていません。
つまり、この時点ではまだhoge.Bという関数は存在していないのです。

この状態で「import piyo」が実行され、どこかにある「piyo.py」が実行され、その中で「from hoge import B」が実行されたとしましょう。
2回目のhogeのimportですから、「hoge.py」の中のコードは実行されず、前の状態、つまりまだhoge.Bという関数が存在していないモジュールを参照することになり、そのモジュールからBというオブジェクトを参照しようとしてエラーになるわけです。


質問者さんのコードは、

python

1line_bot_api = LineBotApi(os.environ["CHANNEL_ACCESS_TOKEN"]) 2handler = WebhookHandler(os.environ["CHANNEL_SECRET"]) 3 4from utils.create_screen import carouselTemplate

というように、views.pyの中で先にline_bot_apiの初期化を行ってからutilsモジュールをインポートすれば回避できますが、そもそもこんなimportする順番を考えなければならないことが問題であり、循環importをせずに対処する方法を考えるべきです。
utilsという名前からして、他からimportして使うモジュールですから、こちらで(linebotなどの他人のモジュールは別として)他のモジュールをインポートせずに済む方法を考えた方がいいでしょう。
linebotの扱うをしているものを全てutilsモジュールに移し、viewsモジュールからはそれらを参照して実行するように変えるのが望ましいのではないでしょうか。

まぁ色々と考えて、どうにも手立てがない、というところまで考えた結果であれば、上記のような対処もやむなしかな、と思います。


ちなみに、

参考元:https://www.kthksgy.com/python/python-circular-dependency/

参考にしているサイトで、確かにワイルドカードを使用すると問題は解決できますが、

つまり、名指しでインポートを行うと、インポート名を明示的に指定している関係でインポートガードが行えないのではないかと考えられます。

は間違いです。

先にも書きましたが、pkg1/mod1.pyからmain.pyをimportしている時点ではまだpkg1/mod1.pyの中のfunc2は定義されていないので、pkg1/mod1.pyfunc2を参照しようとするとエラーになるだけの話です。
決して名指しでインポートすると何か特別な処理が行われるわけでもなく、ワイルドカードを使えば循環importは解決するという勘違いをなさらないようご注意ください。

では、main.pyの中で、

python

1if __name__ == '__main__': 2 func2()

pkg1/mod1.pyfunc2を参照してもエラーにならないか、ということが疑問になりますが、これは__main__モジュールとmainモジュールとの違いが理由なのですが、__main__モジュールの説明をすると長くなるので、「__main__モジュール」について調べてみてください、とだけ言っておきます。すぐに理解するのは難しいと思います。
(一応、似たような話は自分もQiitaに投稿しているので、よかったらどうぞ)

投稿2022/10/03 13:54

katsuko

総合スコア3471

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TAK0811

2022/10/05 14:14

ご丁寧に例も交えてご説明いただき大変ありがとうございます。 import が辿る経路を理解せずに、呼び出そうとしていたことがエラーの原因と認識しました。 また、ご教示頂きましたおかげでどういった仕組みでそうなるのか?の部分も理解できた気がします。 非常に勉強になりました。utilsでの配置とviewからの呼び出しといった、あるべき論(views, utils の役割的なところ)も改めてDjangoドキュメントを読み、理解を深めようと思いました。 漠然とでは無く、個々の意味を理解した上で使用できるように心掛けます。 解決策だけでは無く、色々見直すご助言を頂きありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問