Mengimpor melingkar Python?

101

Jadi saya mendapatkan kesalahan ini

Traceback (most recent call last):
  File "/Users/alex/dev/runswift/utils/sim2014/simulator.py", line 3, in <module>
    from world import World
  File "/Users/alex/dev/runswift/utils/sim2014/world.py", line 2, in <module>
    from entities.field import Field
  File "/Users/alex/dev/runswift/utils/sim2014/entities/field.py", line 2, in <module>
    from entities.goal import Goal
  File "/Users/alex/dev/runswift/utils/sim2014/entities/goal.py", line 2, in <module>
    from entities.post import Post
  File "/Users/alex/dev/runswift/utils/sim2014/entities/post.py", line 4, in <module>
    from physics import PostBody
  File "/Users/alex/dev/runswift/utils/sim2014/physics.py", line 21, in <module>
    from entities.post import Post
ImportError: cannot import name Post

dan Anda dapat melihat bahwa saya menggunakan pernyataan impor yang sama lebih jauh dan berfungsi? Apakah ada aturan tidak tertulis tentang impor melingkar? Bagaimana cara menggunakan kelas yang sama di bawah tumpukan panggilan?

CpILL
sumber

Jawaban:

162

Saya pikir jawaban dari jpmc26, meskipun tidak salah , turun terlalu berat pada impor sirkuler. Mereka dapat bekerja dengan baik, jika Anda mengaturnya dengan benar.

Cara termudah untuk melakukannya adalah dengan menggunakan import my_modulesintaks, bukan from my_module import some_object. Yang pertama akan hampir selalu berhasil, bahkan jika my_moduletermasuk mengimpor kita kembali. Yang terakhir hanya berfungsi jika my_objectsudah ditentukan di my_module, yang dalam impor melingkar mungkin tidak demikian.

Untuk lebih spesifik untuk kasus Anda: Coba ubah entities/post.pyuntuk melakukan import physicsdan kemudian lihat physics.PostBodydaripada hanya PostBodysecara langsung. Demikian pula, ubah physics.pyuntuk melakukan import entities.postdan kemudian gunakan, entities.post.Postbukan hanya Post.

Blckknght
sumber
5
Apakah jawaban ini kompatibel dengan impor relatif?
Joe
19
Mengapa ini terjadi?
Juan Pablo Santos
5
Salah jika mengatakan bahwa non- fromsintaks akan selalu berfungsi. Jika saya memiliki class A(object): pass; class C(b.B): passdi modul a dan class B(a.A): passdi modul b maka impor melingkar masih menjadi masalah dan ini tidak akan berhasil.
CrazyCasta
1
Anda benar, semua dependensi melingkar dalam kode level atas modul (seperti kelas dasar deklarasi kelas dalam contoh Anda) akan menjadi masalah. Itu adalah jenis situasi di mana jawaban jpmc bahwa Anda harus merefaktor organisasi modul mungkin 100% benar. Pindahkan kelas Bke modul a, atau pindahkan kelas Cke modul bsehingga Anda dapat memutus siklus. Perlu juga dicatat bahwa meskipun hanya satu arah lingkaran yang melibatkan kode tingkat atas (misalnya jika kelas Ctidak ada), Anda mungkin mendapatkan kesalahan, bergantung pada modul mana yang diimpor pertama kali oleh kode lain.
Blckknght
2
@TylerCrompton: Saya tidak yakin apa yang Anda maksud dengan "importasi modul harus mutlak". Impor relatif melingkar dapat berfungsi, selama Anda mengimpor modul, bukan isinya (misalnya from . import sibling_module, tidak from .sibling_module import SomeClass). Ada lebih banyak kehalusan ketika __init__.pyfile paket terlibat dalam impor melingkar, tetapi masalahnya jarang terjadi, dan mungkin bug dalam importimplementasi. Lihat bug Python 23447 , yang saya kirimkan tambalannya (yang sayangnya telah merana).
Blckknght
51

Saat Anda mengimpor modul (atau anggotanya) untuk pertama kali, kode di dalam modul dijalankan secara berurutan seperti kode lainnya; misalnya, tidak diperlakukan berbeda dengan tubuh suatu fungsi. An importhanyalah sebuah perintah seperti yang lainnya (tugas, panggilan fungsi def,, class). Dengan asumsi impor Anda terjadi di bagian atas skrip, inilah yang terjadi:

  • Saat Anda mencoba mengimpor Worlddari world, worldskrip dijalankan.
  • The worldimpor Script Field, yang menyebabkan entities.fieldscript untuk dijalankan.
  • Proses ini berlanjut hingga Anda mencapai entities.postskrip karena Anda mencoba untuk mengimporPost
  • Itu entities.post penyebab Script physicsmodul akan dieksekusi karena mencoba untuk imporPostBody
  • Terakhir, physicscoba impor Postdarientities.post
  • Saya tidak yakin apakah entities.postmodul tersebut sudah ada di memori, tetapi itu tidak masalah. Bisa jadi modul tidak ada di memori, atau modul belum memiliki Postanggota karena belum selesai mengeksekusi untuk mendefinisikanPost
  • Either way, kesalahan terjadi karena Posttidak ada untuk diimpor

Jadi tidak, ini tidak "bekerja lebih jauh di tumpukan panggilan". Ini adalah jejak tumpukan tempat terjadinya kesalahan, yang berarti terjadi kesalahan saat mencoba mengimpor Postdi kelas itu. Anda tidak boleh menggunakan impor melingkar. Paling-paling, itu memiliki manfaat yang dapat diabaikan (biasanya, tidak ada manfaat), dan itu menyebabkan masalah seperti ini. Ini membebani pengembang mana pun yang memeliharanya, memaksa mereka berjalan di atas cangkang telur agar tidak pecah. Refactor organisasi modul Anda.

jpmc26.dll
sumber
1
Seharusnya isinstance(userData, Post). Terlepas dari itu, Anda tidak punya pilihan. Impor melingkar tidak akan berfungsi. Fakta bahwa Anda memiliki impor melingkar adalah bau kode bagi saya. Ini menyarankan Anda memiliki beberapa fungsionalitas yang harus dipindahkan ke modul ketiga. Saya tidak bisa mengatakan apa tanpa melihat kedua kelas.
jpmc26
3
@CpILL Setelah beberapa saat, opsi yang sangat hacky terjadi pada saya. Jika Anda tidak dapat melakukan ini untuk saat ini (karena kendala waktu atau apa pun), maka Anda dapat melakukan impor secara lokal di dalam metode tempat Anda menggunakannya. Badan fungsi di dalamnya deftidak dijalankan hingga fungsi dipanggil, jadi impor tidak akan terjadi hingga Anda benar-benar memanggil fungsi tersebut. Pada saat itu, imports harus berfungsi karena salah satu modul akan sepenuhnya diimpor sebelum panggilan. Itu adalah peretasan yang benar-benar menjijikkan, dan itu tidak boleh berada di basis kode Anda untuk jangka waktu yang lama.
jpmc26
15
Saya pikir jawaban Anda terlalu keras pada impor melingkar. Impor melingkar biasanya berfungsi jika Anda melakukan import foolebih dari from foo import Bar. Itu karena kebanyakan modul hanya mendefinisikan hal-hal (seperti fungsi dan kelas) yang berjalan nanti. Modul yang melakukan hal-hal penting saat Anda mengimpornya (seperti skrip yang tidak dilindungi oleh if __name__ == "__main__") mungkin masih menjadi masalah, tetapi itu tidak terlalu umum.
Blckknght
6
@Blckknght Saya pikir Anda menyiapkan diri Anda untuk menghabiskan waktu untuk masalah aneh yang harus diselidiki orang lain dan dibuat bingung jika Anda menggunakan impor melingkar. Mereka memaksa Anda untuk menghabiskan waktu dengan hati-hati agar tidak tersandung mereka, dan di atas itu adalah bau kode bahwa desain Anda perlu refactoring. Saya mungkin salah tentang apakah secara teknis layak, tetapi itu adalah pilihan desain yang buruk yang ditakdirkan untuk menyebabkan masalah cepat atau lambat. Kejelasan dan kesederhanaan adalah cawan suci dalam pemrograman, dan impor melingkar melanggar keduanya dalam buku saya.
jpmc26
6
Kalau tidak; Anda telah membagi fungsionalitas Anda terlalu banyak dan itulah penyebab impor melingkar. Jika Anda memiliki dua hal yang bergantung satu sama lain sepanjang waktu ; mungkin yang terbaik adalah memasukkannya ke dalam satu file. Python bukanlah Java; tidak ada alasan untuk tidak mengelompokkan fungsionalitas / kelas ke dalam satu file untuk mencegah logika impor yang aneh. :-)
Mark Ribau
41

Untuk memahami dependensi melingkar, Anda perlu ingat bahwa Python pada dasarnya adalah bahasa scripting. Eksekusi pernyataan di luar metode terjadi pada waktu kompilasi. Pernyataan impor dijalankan seperti pemanggilan metode, dan untuk memahaminya Anda harus memikirkannya seperti pemanggilan metode.

Saat Anda melakukan impor, apa yang terjadi bergantung pada apakah file yang Anda impor sudah ada di tabel modul. Jika ya, Python menggunakan apa pun yang saat ini ada di tabel simbol. Jika tidak, Python mulai membaca file modul, mengkompilasi / mengeksekusi / mengimpor apa pun yang ditemukannya di sana. Simbol yang direferensikan pada waktu kompilasi ditemukan atau tidak, bergantung pada apakah simbol telah dilihat, atau belum dilihat oleh kompilator.

Bayangkan Anda memiliki dua file sumber:

Berkas X.py

def X1:
    return "x1"

from Y import Y2

def X2:
    return "x2"

Berkas Y.py

def Y1:
    return "y1"

from X import X1

def Y2:
    return "y2"

Sekarang misalkan Anda mengkompilasi file X.py. Kompilator memulai dengan mendefinisikan metode X1, dan kemudian menekan pernyataan import di X.py. Ini menyebabkan kompilator menghentikan sementara kompilasi X.py dan mulai mengkompilasi Y.py. Tak lama kemudian kompilator menekan pernyataan import di Y.py. Karena X.py sudah ada di tabel modul, Python menggunakan tabel simbol X.py yang tidak lengkap untuk memenuhi semua referensi yang diminta. Simbol apapun yang muncul sebelum pernyataan import di X.py sekarang ada di tabel simbol, tapi simbol apapun setelahnya tidak. Karena X1 sekarang muncul sebelum pernyataan impor, itu berhasil diimpor. Python kemudian melanjutkan kompilasi Y.py. Dengan melakukan itu, ia mendefinisikan Y2 dan menyelesaikan kompilasi Y.py. Kemudian melanjutkan kompilasi X.py, dan menemukan Y2 di tabel simbol Y.py. Kompilasi akhirnya selesai tanpa kesalahan.

Sesuatu yang sangat berbeda terjadi jika Anda mencoba mengkompilasi Y.py dari baris perintah. Saat mengkompilasi Y.py, kompilator menekan pernyataan import sebelum mendefinisikan Y2. Kemudian ia mulai mengkompilasi X.py. Segera muncul pernyataan import di X.py yang membutuhkan Y2. Tapi Y2 tidak terdefinisi, jadi kompilasi gagal.

Harap perhatikan bahwa jika Anda memodifikasi X.py untuk mengimpor Y1, kompilasi akan selalu berhasil, apa pun file yang Anda kompilasi. Namun jika Anda memodifikasi file Y.py untuk mengimpor simbol X2, tidak ada file yang akan dikompilasi.

Kapan pun modul X, atau modul apa pun yang diimpor oleh X mungkin mengimpor modul saat ini, JANGAN gunakan:

from X import Y

Setiap kali Anda merasa mungkin ada impor melingkar, Anda juga harus menghindari referensi waktu kompilasi ke variabel di modul lain. Pertimbangkan kode yang tampak tidak bersalah:

import X
z = X.Y

Misalkan modul X mengimpor modul ini sebelum modul ini mengimpor X. Selanjutnya anggaplah Y didefinisikan dalam X setelah pernyataan import. Maka Y tidak akan ditentukan ketika modul ini diimpor, dan Anda akan mendapatkan kesalahan kompilasi. Jika modul ini mengimpor Y terlebih dahulu, Anda bisa lolos begitu saja. Namun jika salah satu rekan kerja Anda dengan polosnya mengubah urutan definisi di modul ketiga, kode tersebut akan rusak.

Dalam beberapa kasus, Anda dapat menyelesaikan dependensi melingkar dengan memindahkan pernyataan import ke bawah definisi simbol yang dibutuhkan oleh modul lain. Dalam contoh di atas, definisi sebelum pernyataan import tidak pernah gagal. Definisi setelah pernyataan import terkadang gagal, tergantung pada urutan kompilasi. Anda bahkan dapat meletakkan pernyataan import di akhir file, selama tidak ada simbol yang diimpor diperlukan pada waktu kompilasi.

Perhatikan bahwa memindahkan pernyataan import ke bawah dalam modul mengaburkan apa yang Anda lakukan. Ganti rugi ini dengan komentar di bagian atas modul Anda seperti berikut ini:

#import X   (actual import moved down to avoid circular dependency)

Secara umum ini adalah praktik yang buruk, tetapi terkadang sulit untuk dihindari.

Gene Olson
sumber
2
Saya tidak berpikir ada kompiler atau waktu kompilasi di python sama sekali
Jerie Wang
6
Python tidak memiliki compiler, dan yang disusun @pkqxdd, kompilasi hanya biasanya tersembunyi dari pengguna. Ini mungkin sedikit membingungkan, tetapi akan sulit bagi penulis untuk memberikan deskripsi yang sangat jelas tentang apa yang terjadi tanpa referensi ke "waktu kompilasi" Python, yang agak tidak jelas.
Hank
Saya melanjutkan untuk mencoba ini di komputer saya dan mendapatkan hasil yang berbeda. Menjalankan X.py tetapi mendapat kesalahan "tidak dapat mengimpor nama 'Y2' dari 'Y'". Jalankan Y.py tanpa masalah. Saya menggunakan Python 3.7.5, dapatkah Anda membantu menjelaskan apa masalahnya di sini?
xuefeng huang
18

Bagi Anda yang, seperti saya, datang ke masalah ini dari Django, Anda harus tahu bahwa dokumen menyediakan solusi: https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey

"... Untuk merujuk ke model yang ditentukan dalam aplikasi lain, Anda dapat secara eksplisit menentukan model dengan label aplikasi lengkap. Misalnya, jika model Pabrikan di atas ditentukan dalam aplikasi lain yang disebut produksi, Anda perlu menggunakan:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',
        on_delete=models.CASCADE,
)

Referensi semacam ini dapat berguna saat menyelesaikan dependensi impor melingkar antara dua aplikasi. ... "

Malik A. Rumi
sumber
6
Saya tahu saya tidak seharusnya menggunakan komentar untuk mengatakan "terima kasih", tetapi ini telah mengganggu saya selama beberapa jam. Terima kasih terima kasih terima kasih!!!
MikeyE
Saya setuju dengan @MikeyE. Saya telah membaca beberapa blog dan Stackoverflow mencoba memperbaiki ini dengan PonyORM. Di mana orang lain mengatakan itu praktik yang buruk, atau mengapa Anda mengkodekan kelas Anda menjadi melingkar, ORM tepat di tempat ini terjadi. Karena banyak contoh meletakkan semua model dalam file yang sama, dan kami mengikuti contoh tersebut, kecuali kami menggunakan model per file, masalahnya tidak jelas ketika Python gagal dikompilasi. Namun, jawabannya sangat sederhana. Seperti yang Mike tunjukkan, terima kasih banyak.
sampah80
4

Saya dapat mengimpor modul dalam fungsi (hanya) yang akan membutuhkan objek dari modul ini:

def my_func():
    import Foo
    foo_instance = Foo()
Alexander Shubert
sumber
betapa elegannya python
Yaro
2

Jika Anda mengalami masalah ini dalam aplikasi yang cukup kompleks, mungkin sulit untuk memfaktor ulang semua impor Anda. PyCharm menawarkan perbaikan cepat untuk ini yang secara otomatis akan mengubah semua penggunaan simbol yang diimpor juga.

masukkan deskripsi gambar di sini

Andreas Bergström
sumber
0

Saya menggunakan yang berikut ini:

from module import Foo

foo_instance = Foo()

tetapi untuk menyingkirkan circular referencesaya melakukan hal berikut dan berhasil:

import module.foo

foo_instance = foo.Foo()
MKJ
sumber