İ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 barve import bariç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 abcve 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 yve yine de dairesel alma hatası alıyor
importdeyim 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 foobaşlangıç aşamasında olduğunu barve gerçek fonksiyonları barhenü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ı bmodül içeriğinde zaten var olduğu için tekrar içe aktarılmaz .
Modül başlatılırken b.xburadan 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.xerişildiği anda , hattın x = 3henü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 ImportErrorssize 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 SimplifiedImageSerializerve ImportErroryü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 barde foo.pysonu için
barvefoo ikisini de kullanmak gerekir gX, 'en temiz' çözüm koymaktır gXbaşka modülde ve her ikisine de sahip foove barithalat modülü mü. (hiçbir gizli semantik bağımlılık olmaması açısından en temiz.)
barbile bulamıyor çünkü gX. dairesel içe aktarma tek başına iyidir, ancak gXiç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 bancak 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_resultve 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.pyve b.py.
Bunu ekledim a.pyve 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 ave dosyanız var b. Sende vardef ya bir classdosyada bsize modülünde kullanmak istediğiniz a, ancak başka bir şey, ya bir def, classdosyadan veya değişken aEğ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 adosyada gereklidir b, ancak dosyadan işlev veya sınıf çağırmadan önce bdosya için gereken asöylemek import b
Sonra ve burada önemli bir parçası dosyada tanımları veya sınıfların tümünde bihtiyaç defveyaclass dosyadana(diyelim CLASS)from a import CLASS
Bunun nedeni, bPython'u dosyadaki içe aktarma ifadelerinden herhangi birini yürütmeden içe aktarabileceğiniz bve 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 CLASSaslı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 bb.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 ab.py'de herhangi bir yerde kullandıysanız, bu eşit derecede iyi çalışır.