Impor melingkar (atau siklik) dengan Python

352

Apa yang akan terjadi jika dua modul saling mengimpor?

Untuk menggeneralisasi masalah, bagaimana dengan impor siklik di Python?

Xolve
sumber
1
Lihat juga stackoverflow.com/questions/158268/…
Constantin
1
juga hanya sebagai referensi, tampaknya impor melingkar diizinkan pada python 3.5 (dan mungkin di luar) tetapi tidak 3.4 (dan mungkin di bawah).
Charlie Parker
4
Saya menggunakan python 3.7.2 dan saya masih memiliki kesalahan runtime karena dependensi melingkar.
Richard Whitehead

Jawaban:

281

Ada diskusi yang sangat bagus tentang ini di comp.lang.python tahun lalu. Ini menjawab pertanyaan Anda dengan cukup teliti.

Impor benar-benar sangat mudah. Ingat saja yang berikut ini:

'import' dan 'from xxx import yyy' adalah pernyataan yang dapat dieksekusi. Mereka mengeksekusi ketika program yang sedang berjalan mencapai garis itu.

Jika modul tidak ada di sys.modules, maka impor membuat entri modul baru di sys.modules dan kemudian mengeksekusi kode dalam modul. Itu tidak mengembalikan kontrol ke modul panggilan sampai eksekusi selesai.

Jika suatu modul ada di sys.modules maka impor hanya mengembalikan modul itu apakah sudah selesai dijalankan. Itulah alasan mengapa impor siklik dapat mengembalikan modul yang tampaknya sebagian kosong.

Akhirnya, skrip pelaksana berjalan dalam modul bernama __main__, mengimpor skrip dengan namanya sendiri akan membuat modul baru yang tidak terkait dengan __main__.

Bawa banyak itu bersama-sama dan Anda tidak akan mendapatkan kejutan ketika mengimpor modul.

Shane C. Mason
sumber
13
@meawoppl Bisakah Anda memperluas komentar ini? Seberapa spesifik mereka berubah?
Dan Schien
3
Sampai sekarang, satu-satunya referensi untuk impor melingkar di python3 "Apa yang baru?" halaman dalam 3,5 satu . Dikatakan "Impor sirkuler yang melibatkan impor relatif sekarang didukung". @meawoppl apakah Anda menemukan hal lain yang tidak tercantum di halaman ini?
zezollo
4
Mereka def. tidak didukung di 3.0-3.4. Atau setidaknya semantik untuk sukses berbeda. Berikut adalah sinopsis yang saya temukan yang tidak menyebutkan perubahan 3.5. gist.github.com/datagrok/40bf84d5870c41a77dc6
meawoppl
Anda dapat memperluas ini "Akhirnya, skrip yang dijalankan berjalan dalam modul bernama main , mengimpor skrip dengan namanya sendiri akan membuat modul baru yang tidak terkait dengan main .". Jadi katakanlah file tersebut adalah a.py dan ketika dijalankan sebagai titik masuk utama, file tersebut sekarang menjadi utama jika memiliki kode seperti dari impor beberapa variabel. Lalu apakah file yang sama 'a.py' akan dimuat dalam tabel modul sys? Jadi apakah ini berarti bahwa jika telah mengatakan pernyataan cetak maka itu akan berjalan dua kali? Sekali untuk file utama dan lagi ketika impor ditemukan?
variabel
Jawaban ini berusia 10 tahun, dan saya ingin pembaruan yang dimodernisasi untuk memastikan bahwa itu tetap benar dalam berbagai versi Python, 2.x atau 3.x
Fallenreaper
296

Jika Anda melakukannya import foodi dalam bardan import bardi dalam foo, itu akan berfungsi dengan baik. Pada saat segala sesuatu benar-benar berjalan, kedua modul akan terisi penuh dan akan memiliki referensi satu sama lain.

Masalahnya adalah kapan Anda melakukannya from foo import abcdan from bar import xyz. Karena sekarang setiap modul memerlukan modul lain untuk sudah diimpor (sehingga nama yang kita impor ada) sebelum dapat diimpor.

user2357112 mendukung Monica
sumber
27
Tampaknya from foo import *dan from bar import *juga akan berfungsi dengan baik.
Akavall
1
Periksa edit ke posting di atas menggunakan a.py/b.py. Dia tidak menggunakan from x import y, namun masih mendapatkan kesalahan impor melingkar
Greg Ennis
2
Ini tidak sepenuhnya benar. Sama seperti impor * dari, jika Anda mencoba mengakses elemen dalam impor melingkar, di tingkat atas, jadi sebelum skrip menyelesaikan prosesnya, maka Anda akan memiliki masalah yang sama. Sebagai contoh jika Anda menetapkan paket global dalam satu paket dari yang lain, dan keduanya termasuk satu sama lain. Saya melakukan ini untuk membuat pabrik ceroboh untuk objek di kelas dasar di mana objek itu bisa menjadi salah satu dari sejumlah subkelas dan kode yang digunakan tidak perlu menyadari yang sebenarnya dibuatnya.
AaronM
3
@Akavall Tidak juga. Itu hanya akan mengimpor nama-nama yang tersedia ketika importpernyataan dijalankan. Jadi itu tidak akan kesalahan tetapi Anda mungkin tidak mendapatkan semua variabel yang Anda harapkan.
augurar
3
Catatan, jika Anda lakukan from foo import *dan from bar import *, semua yang dieksekusi di foodalam fase inisialisasi bar, dan fungsi sebenarnya barbelum didefinisikan ...
Martian2049
100

Impor siklik berakhir, tetapi Anda harus berhati-hati untuk tidak menggunakan modul yang diimpor secara siklik selama inisialisasi modul.

Pertimbangkan file-file berikut:

a.py:

print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"

b.py:

print "b in"
import a
print "b out"
x = 3

Jika Anda menjalankan a.py, Anda akan mendapatkan yang berikut:

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out

Pada impor kedua dari b.py (yang kedua a in), juru bahasa Python tidak mengimpor blagi, karena sudah ada dalam modul dikt.

Jika Anda mencoba mengakses b.xdari aselama inisialisasi modul, Anda akan mendapatkan AttributeError.

Tambahkan baris berikut ke a.py:

print b.x

Kemudian, outputnya adalah:

$ python a.py
a in                    
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
  File "a.py", line 4, in <module>
    import b
  File "/home/shlomme/tmp/x/b.py", line 2, in <module>
    import a
 File "/home/shlomme/tmp/x/a.py", line 7, in <module>
    print b.x
AttributeError: 'module' object has no attribute 'x'

Ini karena modul dieksekusi saat impor dan pada saat b.xdiakses, saluran x = 3belum dieksekusi, yang hanya akan terjadi setelahnya b out.

Torsten Marek
sumber
14
ini sangat menjelaskan masalahnya, tetapi bagaimana dengan solusinya? bagaimana kita bisa mengimpor dan mencetak x dengan benar? solusi lain di atas tidak berhasil untuk saya
mehmet
Saya pikir jawaban ini akan mendapatkan keuntungan banyak jika Anda menggunakan __name__bukan 'a'. Pada awalnya, saya benar-benar bingung mengapa file akan dieksekusi dua kali.
Bergi
30

Seperti jawaban lain yang menggambarkan pola ini dapat diterima dalam python:

def dostuff(self):
     from foo import bar
     ...

Yang akan menghindari pelaksanaan pernyataan impor ketika file diimpor oleh modul lain. Hanya jika ada ketergantungan melingkar logis, ini akan gagal.

Sebagian besar Impor Circular sebenarnya bukan impor melingkar yang logis tetapi lebih meningkatkan ImportErrorkesalahan, karena cara import()mengevaluasi pernyataan tingkat atas dari seluruh file ketika dipanggil.

Ini ImportErrorshampir selalu dapat dihindari jika Anda ingin impor Anda positif :

Pertimbangkan impor melingkar ini:

Aplikasi A

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

Aplikasi B

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

Dari David Beazleys, Modul dan Paket Pembicaraan yang luar biasa : Live and Let Die! - PyCon 2015 ,, 1:54:00berikut adalah cara untuk menangani impor melingkar dengan python:

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

Ini mencoba mengimpor SimplifiedImageSerializerdan jika ImportErrordinaikkan, karena sudah diimpor, itu akan menariknya dari importcache.

PS: Anda harus membaca seluruh kiriman ini dengan suara David Beazley.

Sebastian Wozny
sumber
9
ImportError tidak dimunculkan jika modul sudah diimpor. Modul dapat diimpor sebanyak yang Anda inginkan yaitu "import a; import a;" tidak apa-apa.
Yuras
9

Saya mendapat contoh di sini yang mengejutkan saya!

foo.py

import bar

class gX(object):
    g = 10

bar.py

from foo import gX

o = gX()

main.py

import foo
import bar

print "all done"

Di baris perintah: $ python main.py

Traceback (most recent call last):
  File "m.py", line 1, in <module>
    import foo
  File "/home/xolve/foo.py", line 1, in <module>
    import bar
  File "/home/xolve/bar.py", line 1, in <module>
    from foo import gX
ImportError: cannot import name gX
Xolve
sumber
2
Bagaimana Anda memperbaikinya? Saya mencoba memahami impor melingkar untuk memperbaiki masalah saya sendiri yang terlihat sangat mirip dengan apa yang Anda lakukan ...
c089
12
Errm ... Saya rasa saya memperbaiki masalah saya dengan hack yang sangat jelek ini. {{{Jika bukan 'foo.bar' di sys.modules: dari foo import bar lain: bar = sys.modules ['foo.bar']}}} Secara pribadi, saya pikir impor melingkar adalah tanda peringatan BESAR pada kode buruk design ...
c089
5
@ c089, atau Anda hanya bisa bergerak import bardi foo.pyakhir
warvariuc
5
Jika bardan fookeduanya harus menggunakan gX, solusi 'terbersih' adalah memasukkan gXmodul lain dan memiliki keduanya foodan barmengimpor modul itu. (terbersih dalam arti bahwa tidak ada dependensi semantik yang tersembunyi.)
Tim Wilder
2
Tim punya poin bagus. Pada dasarnya itu karena barbahkan tidak dapat menemukan gXdi foo. impor melingkar baik-baik saja dengan sendirinya, tetapi hanya saja itu gXtidak ditentukan ketika diimpor.
Martian2049
9

Modul a.py:

import b
print("This is from module a")

Modul b.py

import a
print("This is from module b")

Menjalankan "Modul a" akan menampilkan:

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 

Ini output 3 baris ini sementara itu seharusnya menghasilkan infinitival karena impor melingkar. Apa yang terjadi baris demi baris saat menjalankan "Modul a" tercantum di sini:

  1. Baris pertama adalah import b. jadi akan mengunjungi modul b
  2. Baris pertama pada modul b adalah import a. jadi akan mengunjungi modul a
  3. Baris pertama pada modul a adalah import btetapi perhatikan bahwa baris ini tidak akan dieksekusi lagi , karena setiap file dalam python mengeksekusi baris impor hanya untuk sekali, tidak masalah di mana atau kapan dijalankan. sehingga akan lolos ke baris berikutnya dan mencetak "This is from module a".
  4. Setelah selesai mengunjungi seluruh modul a dari modul b, kita masih di modul b. jadi baris berikutnya akan dicetak"This is from module b"
  5. Modul b baris dieksekusi sepenuhnya. jadi kita akan kembali ke modul di mana kita mulai modul b.
  6. garis impor b telah dieksekusi dan tidak akan dieksekusi lagi. baris berikutnya akan dicetak "This is from module a"dan program akan selesai.
Mohsen Haddadi
sumber
4

Saya sepenuhnya setuju dengan jawaban pythoneer di sini. Tetapi saya telah menemukan beberapa kode yang cacat dengan impor melingkar dan menyebabkan masalah ketika mencoba untuk menambahkan tes unit. Jadi untuk menambalnya dengan cepat tanpa mengubah semua yang Anda bisa menyelesaikan masalah dengan melakukan impor dinamis.

# Hack to import something without circular import issue
def load_module(name):
    """Load module using imp.find_module"""
    names = name.split(".")
    path = None
    for name in names:
        f, path, info = imp.find_module(name, path)
        path = [path]
    return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")

Sekali lagi, ini bukan perbaikan permanen tetapi dapat membantu seseorang yang ingin memperbaiki kesalahan impor tanpa mengubah terlalu banyak kode.

Bersulang!

radtek
sumber
3

Ada banyak jawaban bagus di sini. Walaupun biasanya ada solusi cepat untuk masalah ini, beberapa di antaranya terasa lebih pythonic daripada yang lain, jika Anda memiliki kemewahan melakukan beberapa refactoring, pendekatan lain adalah menganalisis organisasi kode Anda, dan mencoba untuk menghapus ketergantungan melingkar. Anda dapat menemukan, misalnya, bahwa Anda memiliki:

File a.py

from b import B

class A:
    @staticmethod
    def save_result(result):
        print('save the result')

    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

File b.py

from a import A

class B:
    @staticmethod
    def do_something_b_ish(param):
        A.save_result(B.use_param_like_b_would(param))

Dalam hal ini, hanya memindahkan satu metode statis ke file terpisah, katakan c.py:

File c.py

def save_result(result):
    print('save the result')

akan memungkinkan menghapus save_resultmetode dari A, dan dengan demikian memungkinkan menghapus impor A dari a di b:

File Refactored a.py

from b import B
from c import save_result

class A:
    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

File Refactored b.py

from c import save_result

class B:
    @staticmethod
    def do_something_b_ish(param):
        save_result(B.use_param_like_b_would(param))

Singkatnya, jika Anda memiliki alat (misalnya pylint atau PyCharm) yang melaporkan metode yang bisa statis, hanya melemparkan staticmethoddekorator pada mereka mungkin bukan cara terbaik untuk membungkam peringatan. Meskipun metode ini tampaknya terkait dengan kelas, mungkin lebih baik untuk memisahkannya, terutama jika Anda memiliki beberapa modul terkait yang mungkin membutuhkan fungsionalitas yang sama dan Anda bermaksud untuk mempraktikkan prinsip KERING.

lebih lagi
sumber
2

Impor sirkuler dapat membingungkan karena impor melakukan dua hal:

  1. itu mengeksekusi kode modul yang diimpor
  2. menambahkan modul yang diimpor ke mengimpor tabel simbol global modul

Yang pertama dilakukan hanya sekali, sedangkan yang terakhir di setiap pernyataan impor. Impor sirkuler menciptakan situasi ketika mengimpor modul menggunakan modul yang diimpor dengan kode yang dieksekusi sebagian. Karena itu tidak akan melihat objek yang dibuat setelah pernyataan impor. Contoh kode di bawah ini menunjukkannya.

Impor sirkular bukanlah kejahatan utama yang harus dihindari dengan cara apa pun. Dalam beberapa kerangka kerja seperti Flask mereka sangat alami dan mengubah kode Anda untuk menghilangkannya tidak membuat kode lebih baik.

main.py

print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
    print 'imports done'
    print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)

oleh

print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"

a.py

print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"

output python main.py dengan komentar

import b
b in, __name__ = b    # b code execution started
b imports a
a in, __name__ = a    # a code execution started
a imports b           # b code execution is already in progress
b has x True
b has y False         # b defines y after a import,
a out
b out
a in globals() False  # import only adds a to main global symbol table 
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available
Jacek Błocki
sumber
1

Saya memecahkan masalah dengan cara berikut, dan berfungsi dengan baik tanpa kesalahan. Pertimbangkan dua file a.pydan b.py.

Saya menambahkan ini ke a.pydan itu berhasil.

if __name__ == "__main__":
        main ()

a.py:

import b
y = 2
def main():
    print ("a out")
    print (b.x)

if __name__ == "__main__":
    main ()

b.py:

import a
print ("b out")
x = 3 + a.y

Output yang saya dapatkan adalah

>>> b out 
>>> a out 
>>> 5
Irfanuddin
sumber
0

Ok, saya pikir saya punya solusi yang cukup keren. Katakanlah Anda memiliki file adan file b. Anda memiliki defatau classdalam file byang ingin Anda gunakan dalam modul a, tetapi Anda memiliki sesuatu yang lain, baik def, classatau variabel dari file ayang Anda butuhkan dalam definisi Anda atau kelas dalam file b. Apa yang dapat Anda lakukan adalah, di bagian bawah file a, setelah memanggil fungsi atau kelas dalam file ayang diperlukan dalam file b, tetapi sebelum memanggil fungsi atau kelas dari file byang Anda butuhkan untuk file a, katakan import b Lalu, dan inilah bagian kuncinya , dalam semua definisi atau kelas dalam file byang memerlukan defatau classdari filea(sebut saja CLASS), katamufrom a import CLASS

Ini berfungsi karena Anda dapat mengimpor file btanpa Python mengeksekusi salah satu pernyataan impor dalam file b, dan dengan demikian Anda menghindari impor melingkar.

Sebagai contoh:

Ajukan:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

File b:

class B(object):

     def __init__(self, number):

         self.number = number

     def dostuff(self):

         from a import CLASS

         print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."

Voila.

Cary Shindell
sumber
from a import CLASSsebenarnya tidak melewatkan mengeksekusi semua kode dalam a.py. Inilah yang sebenarnya terjadi: (1) Semua kode di a.py dijalankan sebagai modul khusus "__main__". (2) Pada import b, kode tingkat atas dalam b.py dijalankan (mendefinisikan kelas B) dan kemudian kontrol kembali ke "__main__". (3) "__main__" akhirnya memberikan kontrol ke go.dostuff(). (4) ketika dostuff () muncul import a, ia menjalankan semua kode dalam a.py lagi , kali ini sebagai modul "a"; kemudian mengimpor objek CLASS dari modul baru "a". Jadi sebenarnya, ini akan bekerja dengan baik jika Anda digunakan import adi mana saja di b.py.
Matthias Fripp