Python'da dairesel (veya döngüsel) ithalatlar


353

İki modül birbirini içe aktarırsa ne olur?

Sorunu genelleştirmek için Python'daki döngüsel ithalatlar ne olacak?



1
ayrıca bir referans olarak, python 3.5 (ve muhtemelen ötesinde) üzerinde dairesel ithalata izin verilmiş gibi görünüyor, ancak 3.4 (ve muhtemelen feryat) değil.
Charlie Parker

4
Python 3.7.2 kullanıyorum ve hala dairesel bağımlılıklar nedeniyle bir çalışma zamanı hatası yaşıyorum.
Richard Whitehead

Yanıtlar:


282

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.


13
@meawoppl Bu yorumu genişletebilir misiniz, lütfen? Ne kadar spesifik olarak değiştiler?
Dan Schien

3
Şu an itibariyle, python3'teki dairesel ithalatlara tek referans "Yenilikler neler?" sayfalar 3,5'dedir . "Göreli ithalat içeren dairesel ithalatlar artık destekleniyor" diyor. @meawoppl bu sayfalarda listelenmeyen başka bir şey buldunuz mu?
zezollo

4
Onlar def. 3.0-3.4'te desteklenmez. Ya da en azından başarıya ilişkin anlambilim farklıdır. İşte 3.5 değişiklikten bahsetmediğim bir özet. gist.github.com/datagrok/40bf84d5870c41a77dc6
meawoppl

Lütfen bunu genişletebilirsiniz "Son olarak, yürütme komut dosyası main adlı bir modülde çalışır , komut dosyasını kendi adı altında içe aktarmak ana ile ilgisiz yeni bir modül oluşturur ." Diyelim ki dosya a.py ve ana giriş noktası olarak çalıştığında, onun bir değişkeninden içe aktarma gibi bir kodu varsa şimdi onun ana . Peki aynı dosya 'a.py' sys modülleri tablosuna yüklenir mi? Yani, print deyimi varsa, o zaman iki kez çalışacağı anlamına mı geliyor? Bir kez ana dosya için ve tekrar alma ile karşılaşıldığında?
değişken

Bu yanıt 10 yaşında ve Python, 2.x veya 3.x'in çeşitli sürümlerinde doğru kalmasını sağlamak için modern bir güncelleme istiyorum
Fallenreaper

296

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).


27
Öyle görünüyor from foo import *ve from bar import *aynı zamanda iyi çalışacak.
Akavall

1
A.py/b.py komutunu kullanarak yukarıdaki yazının düzenlemesini kontrol edin. Kullanmıyor from x import yve yine de dairesel alma hatası alıyor
Greg Ennis

2
Bu tamamen doğru değil. Tıpkı import * from öğesinde olduğu gibi, dairesel içe aktarmadaki bir öğeye en üst düzeyde erişmeye çalışırsanız, komut dosyası çalışmasını tamamlamadan önce aynı sorunla karşılaşırsınız. Örneğin, bir paketi bir paketten diğerine global olarak ayarlıyorsanız ve her ikisi de birbirini içerir. Bunu, bu nesnenin bir dizi alt sınıftan biri olabileceği ve kullanma kodunun gerçekte yaratıldığını bilmesine gerek olmadığı temel sınıftaki bir nesne için özensiz bir fabrika oluşturmak için yapıyordum.
AaronM

3
@Akavall Pek değil. Bu, yalnızca 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.
augurar

3
Eğer yaparsanız 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ı ...
Martian2049

100

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.


14
bu sorunu büyük ölçüde açıklıyor, ama çözüm ne olacak? x'i nasıl düzgün bir şekilde içe aktarabilir ve yazdırabiliriz? yukarıdaki diğer çözüm benim için işe yaramadı
mehmet

Ben kullandığınız takdirde bu yanıt çok fayda sağlayacağını düşünüyorum __name__yerine 'a'. Başlangıçta, bir dosyanın neden iki kez yürütüleceği konusunda tamamen kafam karıştı.
Bergi

30

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:

Uygulama A

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

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

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

Uygulama B

# 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.


9
Modül zaten içe aktarılmışsa ImportError yükseltilmez. Modüller istediğiniz kadar içe aktarılabilir, yani "içe aktarma; içe aktarma; tamam.
Yuras

9

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

2
Bunu nasıl düzelttiniz? Yaptığım şeye çok benzeyen bir sorunu düzeltmek için dairesel içe
aktarmayı

12
Eee ... Sanırım bu inanılmaz çirkin hack ile sorunumu çözdüm. {{{sys.modules içinde 'foo.bar' değilse: foo import bar'dan: bar = sys.modules ['foo.bar']}}} Şahsen, dairesel ithalat kötü kod üzerinde BÜYÜK bir uyarı işareti tasarım ...
c089

5
@ c089, ya da sadece hareket olabilir import barde foo.pysonu için
warvariuc

5
Eğer 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.)
Tim Wilder

2
Tim'in iyi bir anlamı var. Temelde foo içinde barbile bulamıyor çünkü gX. dairesel içe aktarma tek başına iyidir, ancak gXiçe aktarıldığında tanımlanmamıştır.
1717

9

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:

  1. İlk satır import b . böylece modül b'yi ziyaret edecek
  2. Modül b'deki ilk satır import a . böylece modül a'yı ziyaret edecek
  3. Modül a'daki ilk satır, import 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".
  4. Modül a'nın tamamını modül b'den ziyaret ettikten sonra, hala modül b'deyiz. böylece bir sonraki satır yazdırılacak"This is from module b"
  5. Modül b hatları tamamen yürütülür. bu yüzden modül b'yi başlattığımız a modülüne geri döneceğiz.
  6. içe aktarma b satırı zaten yürütülmüştür ve tekrar yürütülmez. bir sonraki satır yazdırılacak "This is from module a"ve program tamamlanacaktır.

4

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!


3

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.


2

Dairesel ithalat kafa karıştırıcı olabilir çünkü ithalat iki şey yapar:

  1. içe aktarılan modül kodunu yürütür
  2. içe aktarılan modülü, içe aktarılan modül global sembol tablosuna ekler

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

1

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 ()

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

Aldığım çıktı

>>> b out 
>>> a out 
>>> 5

0

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:

Dosya:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

Dosya 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.


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.
Matthias Fripp
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.