Apa yang akan terjadi jika dua modul saling mengimpor?
Untuk menggeneralisasi masalah, bagaimana dengan impor siklik di Python?
Apa yang akan terjadi jika dua modul saling mengimpor?
Untuk menggeneralisasi masalah, bagaimana dengan impor siklik di Python?
Jawaban:
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.
Jika Anda melakukannya import foo
di dalam bar
dan import bar
di 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 abc
dan from bar import xyz
. Karena sekarang setiap modul memerlukan modul lain untuk sudah diimpor (sehingga nama yang kita impor ada) sebelum dapat diimpor.
from foo import *
dan from bar import *
juga akan berfungsi dengan baik.
from x import y
, namun masih mendapatkan kesalahan impor melingkar
import
pernyataan dijalankan. Jadi itu tidak akan kesalahan tetapi Anda mungkin tidak mendapatkan semua variabel yang Anda harapkan.
from foo import *
dan from bar import *
, semua yang dieksekusi di foo
dalam fase inisialisasi bar
, dan fungsi sebenarnya bar
belum didefinisikan ...
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 b
lagi, karena sudah ada dalam modul dikt.
Jika Anda mencoba mengakses b.x
dari a
selama 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.x
diakses, saluran x = 3
belum dieksekusi, yang hanya akan terjadi setelahnya b out
.
__name__
bukan 'a'
. Pada awalnya, saya benar-benar bingung mengapa file akan dieksekusi dua kali.
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 ImportError
kesalahan, karena cara import()
mengevaluasi pernyataan tingkat atas dari seluruh file ketika dipanggil.
Ini ImportErrors
hampir selalu dapat dihindari jika Anda ingin impor Anda positif :
Pertimbangkan impor melingkar ini:
# profiles/serializers.py
from images.serializers import SimplifiedImageSerializer
class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()
class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)
# 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:00
berikut 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 SimplifiedImageSerializer
dan jika ImportError
dinaikkan, karena sudah diimpor, itu akan menariknya dari importcache.
PS: Anda harus membaca seluruh kiriman ini dengan suara David Beazley.
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
import bar
di foo.py
akhir
bar
dan foo
keduanya harus menggunakan gX
, solusi 'terbersih' adalah memasukkan gX
modul lain dan memiliki keduanya foo
dan bar
mengimpor modul itu. (terbersih dalam arti bahwa tidak ada dependensi semantik yang tersembunyi.)
bar
bahkan tidak dapat menemukan gX
di foo. impor melingkar baik-baik saja dengan sendirinya, tetapi hanya saja itu gX
tidak ditentukan ketika diimpor.
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:
import b
. jadi akan mengunjungi modul bimport a
. jadi akan mengunjungi modul aimport b
tetapi 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"
."This is from module b"
"This is from module a"
dan program akan selesai.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!
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_result
metode 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 staticmethod
dekorator 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.
Impor sirkuler dapat membingungkan karena impor melakukan dua hal:
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
Saya memecahkan masalah dengan cara berikut, dan berfungsi dengan baik tanpa kesalahan. Pertimbangkan dua file a.py
dan b.py
.
Saya menambahkan ini ke a.py
dan itu berhasil.
if __name__ == "__main__":
main ()
import b
y = 2
def main():
print ("a out")
print (b.x)
if __name__ == "__main__":
main ()
import a
print ("b out")
x = 3 + a.y
Output yang saya dapatkan adalah
>>> b out
>>> a out
>>> 5
Ok, saya pikir saya punya solusi yang cukup keren. Katakanlah Anda memiliki file a
dan file b
. Anda memiliki def
atau class
dalam file b
yang ingin Anda gunakan dalam modul a
, tetapi Anda memiliki sesuatu yang lain, baik def
, class
atau variabel dari file a
yang 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 a
yang diperlukan dalam file b
, tetapi sebelum memanggil fungsi atau kelas dari file b
yang Anda butuhkan untuk file a
, katakan import b
Lalu, dan inilah bagian kuncinya , dalam semua definisi atau kelas dalam file b
yang memerlukan def
atau class
dari filea
(sebut saja CLASS
), katamufrom a import CLASS
Ini berfungsi karena Anda dapat mengimpor file b
tanpa Python mengeksekusi salah satu pernyataan impor dalam file b
, dan dengan demikian Anda menghindari impor melingkar.
Sebagai contoh:
class A(object):
def __init__(self, name):
self.name = name
CLASS = A("me")
import b
go = B(6)
go.dostuff
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.
from a import CLASS
sebenarnya 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 a
di mana saja di b.py.