İki modül birbirini içe aktarırsa ne olur?
Sorunu genelleştirmek için Python'daki döngüsel ithalatlar ne olacak?
İki modül birbirini içe aktarırsa ne olur?
Sorunu genelleştirmek için Python'daki döngüsel ithalatlar ne olacak?
Yanıtlar:
Geçen yıl bu konuda comp.lang.python'da gerçekten iyi bir tartışma vardı . Sorunuza oldukça iyi cevap veriyor.
İthalat gerçekten çok basit. Aşağıdakileri hatırlamanız yeterlidir:
'import' ve 'xxx import yyy'den' çalıştırılabilir ifadelerdir. Çalışan program bu satıra ulaştığında çalışırlar.
Bir modül sys.modules içinde değilse, bir içe aktarma sys.modules içinde yeni modül girdisini oluşturur ve sonra modüldeki kodu yürütür. Yürütme işlemi tamamlanana kadar denetimi çağıran modüle döndürmez.
Sys.modules içinde bir modül varsa, bir içe aktarma işlemi, işlemi tamamlamış olsun ya da olmasın, o modülü döndürür. Döngüsel ithalatın kısmen boş görünen modülleri döndürebilmesinin nedeni budur.
Son olarak, yürütme komut dosyası __main__ adlı bir modülde çalışır, komut dosyasını kendi adı altında içe aktarmak __main__ ile ilgisiz yeni bir modül oluşturur.
Bu parçayı bir araya getirin ve modülleri alırken sürprizler yaşamamalısınız.
import foo
İçeride bar
ve import bar
içeride yaparsanız foo
, iyi çalışır. Bir şey gerçekten çalıştığında, her iki modül de tamamen yüklenecek ve birbirlerine referanslar olacaktır.
Sorun bunun yerine from foo import abc
ve from bar import xyz
. Çünkü artık her modül, içe aktarılabilmesi için diğer modülün içe aktarılmasını gerektirir (böylece içe aktardığımız ad var olur).
from foo import *
ve from bar import *
aynı zamanda iyi çalışacak.
from x import y
ve yine de dairesel alma hatası alıyor
import
deyim yürütüldüğünde kullanılabilir olan adları alır . Bu yüzden hata yapmaz, ancak beklediğiniz tüm değişkenleri alamayabilirsiniz.
from foo import *
ve from bar import *
, yapılan her şey foo
başlangıç aşamasında olduğunu bar
ve gerçek fonksiyonları bar
henüz tanımlanmadı ...
Döngüsel içe aktarmalar sona erer, ancak modülün başlatılması sırasında döngüsel olarak içe aktarılan modülleri kullanmamaya dikkat etmeniz gerekir.
Aşağıdaki dosyaları göz önünde bulundurun:
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
A.py çalıştırırsanız, aşağıdakileri alırsınız:
$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out
İkinci b.py içe aktarma işleminde (ikincisinde a in
), Python yorumlayıcısı b
modül içeriğinde zaten var olduğu için tekrar içe aktarılmaz .
Modül başlatılırken b.x
buradan erişmeye çalışırsanız a
, bir AttributeError
.
Aşağıdaki satırı ekleyin a.py
:
print b.x
Sonra çıktı:
$ 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'
Bunun nedeni, modüllerin içe aktarma sırasında yürütülmesi ve b.x
erişildiği anda , hattın x = 3
henüz yürütülmemiş olmasıdır, ancak bu daha sonra gerçekleşecektir b out
.
__name__
yerine 'a'
. Başlangıçta, bir dosyanın neden iki kez yürütüleceği konusunda tamamen kafam karıştı.
Diğer cevapların açıkladığı gibi bu model python'da kabul edilebilir:
def dostuff(self):
from foo import bar
...
Bu, dosya diğer modüller tarafından içe aktarıldığında içe aktarma ifadesinin yürütülmesini engelleyecektir. Sadece mantıksal bir dairesel bağımlılık varsa, bu başarısız olur.
Çoğu Dairesel İthalat aslında mantıksal dairesel ithalat değildir ImportError
, aksineimport()
çağrıldığında tüm dosyanın üst düzey ifadelerini değerlendirme .
Bunlar ImportErrors
size olumlu üstüne ithalatlarınızı istiyorsanız hemen hemen her zaman önlenebilir :
Bu dairesel içe aktarmayı düşünün:
# 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()
David Beazleys mükemmel konuşma Modülleri ve Paketleri: Live and Let Die! - PyCon 2015 , python'da1:54:00
dairesel ithalatla başa çıkmanın bir yolu:
try:
from images.serializers import SimplifiedImageSerializer
except ImportError:
import sys
SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']
Bu içe aktarmaya çalışır SimplifiedImageSerializer
ve ImportError
yükseltilmişse, zaten içe aktarılmış olduğundan içe aktarma önbelleğinden çeker.
Not: Bu yazının tamamını David Beazley'nin sesiyle okumalısın.
Burada beni etkileyen bir örnek var!
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"
Komut satırında: $ 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
de foo.py
sonu için
bar
vefoo
ikisini de kullanmak gerekir gX
, 'en temiz' çözüm koymaktır gX
başka modülde ve her ikisine de sahip foo
ve bar
ithalat modülü mü. (hiçbir gizli semantik bağımlılık olmaması açısından en temiz.)
bar
bile bulamıyor çünkü gX
. dairesel içe aktarma tek başına iyidir, ancak gX
içe aktarıldığında tanımlanmamıştır.
Modül a.py:
import b
print("This is from module a")
Modül b.py
import a
print("This is from module b")
"Module a" çalıştırıldığında:
>>>
'This is from module a'
'This is from module b'
'This is from module a'
>>>
Dairesel içe aktarma nedeniyle sonsuzluk üretmesi gerekiyordu, bu 3 satır çıktı. "Modül a" çalıştırılırken satır satır ne olur burada listelenir:
import b
. böylece modül b'yi ziyaret edecekimport a
. böylece modül a'yı ziyaret edecekimport b
ancak bu satırın artık yürütülmeyeceğini unutmayın , çünkü python'daki her dosya bir kez bir içe aktarma satırı yürüttüğü için, nerede veya ne zaman yürütüldüğü önemli değildir. böylece bir sonraki satıra geçecek ve yazdıracaktır "This is from module a"
."This is from module b"
"This is from module a"
ve program tamamlanacaktır.Burada pythoneer'in cevabına tamamen katılıyorum. Ama dairesel ithalat ile kusurlu ve birim testleri eklemek çalışırken sorunlara neden olan bazı kod tökezledi. Böylece her şeyi değiştirmeden hızlı bir şekilde yama yapmak için dinamik bir içe aktarma yaparak sorunu çözebilirsiniz.
# 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")
Yine, bu kalıcı bir düzeltme değildir, ancak kodu çok fazla değiştirmeden bir içe aktarma hatasını düzeltmek isteyen birine yardımcı olabilir.
Şerefe!
Burada birçok harika cevap var. Soruna genellikle hızlı çözümler olsa da, bazıları diğerlerinden daha pitoniktir, ancak biraz yeniden düzenleme yapma lüksüne sahipseniz, başka bir yaklaşım kodunuzun organizasyonunu analiz etmek ve dairesel bağımlılığı ortadan kaldırmaktır. Örneğin, aşağıdakilere sahip olduğunuzu görebilirsiniz:
Dosya 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)
Dosya 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))
Bu durumda, sadece bir statik yöntemi ayrı bir dosyaya taşıyın c.py
:
C.py dosyası
def save_result(result):
print('save the result')
kaldırma sağlar save_result
ve böylece A yöntemi, ve b bir ile mesafe ithal kaldırma sağlar:
Yeniden Düzenlenen Dosya 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)
Yeniden Düzenlenen Dosya 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))
Özetle, statik olabilecek yöntemler hakkında rapor veren bir aracınız (örn. Pylint veya PyCharm) varsa staticmethod
, bunlara bir dekoratör atmak uyarıyı susturmanın en iyi yolu olmayabilir. Yöntem sınıfla ilgili görünse de, özellikle aynı işlevselliğe ihtiyaç duyabilecek yakından ilişkili birkaç modülünüz varsa ve DRY ilkelerini uygulamak istiyorsanız, ayırmak daha iyi olabilir.
Dairesel ithalat kafa karıştırıcı olabilir çünkü ithalat iki şey yapar:
Birincisi yalnızca bir kez yapılırken, ikincisi her ithalat ifadesinde yapılır. Dairesel içe aktarma modülü içe aktarma modülü, içe aktarılan kodu kısmen yürütülen kodla kullandığında durum yaratır. Sonuç olarak, import ifadesinden sonra oluşturulan nesneleri görmez. Aşağıdaki kod örneği bunu göstermektedir.
Dairesel ithalat, ne pahasına olursa olsun kaçınılması gereken nihai kötülük değildir. Flask gibi bazı çerçevelerde oldukça doğaldır ve bunları ortadan kaldırmak için kodunuzu değiştirmek kodu daha iyi yapmaz.
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)
b.by
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"
yorumlarla python main.py çıktısı
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
Sorunu şu şekilde çözdüm ve herhangi bir hata olmadan iyi çalışıyor. İki dosyayı düşünün a.py
ve b.py
.
Bunu ekledim a.py
ve işe yaradı.
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
Aldığım çıktı
>>> b out
>>> a out
>>> 5
Tamam, bence oldukça havalı bir çözümüm var. Diyelim ki dosyanız a
ve dosyanız var b
. Sende vardef
ya bir class
dosyada b
size modülünde kullanmak istediğiniz a
, ancak başka bir şey, ya bir def
, class
dosyadan veya değişken a
Eğer dosyada tanımı ya da sınıfta gerektiğini b
. Ne yapabilirsiniz dosyanın dibinde olduğu a
, dosyada işlevi veya sınıfı çağrıldıktan sonra a
dosyada gereklidir b
, ancak dosyadan işlev veya sınıf çağırmadan önce b
dosya için gereken a
söylemek import b
Sonra ve burada önemli bir parçası dosyada tanımları veya sınıfların tümünde b
ihtiyaç def
veyaclass
dosyadana
(diyelim CLASS
)from a import CLASS
Bunun nedeni, b
Python'u dosyadaki içe aktarma ifadelerinden herhangi birini yürütmeden içe aktarabileceğiniz b
ve böylece dairesel içe aktarma işlemlerinden kaçabileceğiniz için çalışır.
Örneğin:
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
aslında a.py dosyasındaki tüm kodu yürütmeyi atlamaz. Gerçekten olan budur: (1) a.py içindeki tüm kod özel bir modül "__main__" olarak çalıştırılır. (2) import b
b.py'deki en üst düzey kod çalıştırılır (B sınıfını tanımlar) ve sonra kontrol "__main__" e döner. (3) "__main__" nihayetinde kontrole geçer go.dostuff()
. (4) dostuff () geldiğinde import a
, bu kodu "a" modülü olarak tekrar a.py'deki tüm kodu çalıştırır ; daha sonra CLASS nesnesini yeni "a" modülünden alır. Aslında, import a
b.py'de herhangi bir yerde kullandıysanız, bu eşit derecede iyi çalışır.