Python döngüsel içe aktarma?


102

Bu yüzden bu hatayı alıyorum

Traceback (most recent call last):
  File "/Users/alex/dev/runswift/utils/sim2014/simulator.py", line 3, in <module>
    from world import World
  File "/Users/alex/dev/runswift/utils/sim2014/world.py", line 2, in <module>
    from entities.field import Field
  File "/Users/alex/dev/runswift/utils/sim2014/entities/field.py", line 2, in <module>
    from entities.goal import Goal
  File "/Users/alex/dev/runswift/utils/sim2014/entities/goal.py", line 2, in <module>
    from entities.post import Post
  File "/Users/alex/dev/runswift/utils/sim2014/entities/post.py", line 4, in <module>
    from physics import PostBody
  File "/Users/alex/dev/runswift/utils/sim2014/physics.py", line 21, in <module>
    from entities.post import Post
ImportError: cannot import name Post

ve aynı import ifadesini daha sonra kullandığımı ve işe yaradığını görebilirsiniz. Döngüsel ithalatla ilgili yazılı olmayan bir kural var mı? Çağrı yığınının altında aynı sınıfı nasıl kullanabilirim?

Yanıtlar:


167

Sanırım jpmc26'nın cevabı, hiçbir şekilde yanlış olmasa da , dairesel ithalatlarda çok ağır geliyor. Doğru şekilde kurarsanız, gayet iyi çalışabilirler.

Bunu yapmanın en kolay yolu import my_moduleyerine sözdizimi kullanmaktır from my_module import some_object. İlki, my_modulebizi geri ithal etse bile, neredeyse her zaman çalışacaktır . İkincisi, yalnızca my_objecthalihazırda tanımlanmışsa çalışır my_module, bu döngüsel bir içe aktarmada söz konusu olmayabilir.

Davanızı özgü olmak için: değiştirmeyi deneyin entities/post.pyyapmak import physicsve daha sonra başvurmak physics.PostBodysadece ziyade PostBodydoğrudan. Benzer şekilde, physics.pyyapmak için değiştirin import entities.postve sonra entities.post.Postsadece yerine kullanın Post.


5
Bu cevap, göreli ithalatlarla uyumlu mu?
Joe

19
Bu neden oluyor?
Juan Pablo Santos

5
fromSözdizimsizin her zaman işe yarayacağını söylemek yanlıştır . Ben varsa class A(object): pass; class C(b.B): passmodül a'da ve class B(a.A): passmodül b ardından dairesel ithalat hala bir sorun ve bu işi olmaz.
CrazyCasta

1
Haklısınız, modüllerin üst düzey kodundaki herhangi bir döngüsel bağımlılık (örneğinizdeki sınıf bildirimlerinin temel sınıfları gibi) bir sorun olacaktır. Bu, jpmc'nin modül organizasyonunu yeniden düzenlemeniz gereken cevabının muhtemelen% 100 doğru olduğu türden bir durumdur. Ya sınıfı Bmodüle ataşıyın ya da sınıfı Cmodüle taşıyın, bböylece döngüyü kırabilirsiniz. Bu dairenin yalnızca bir yönü katılan üst düzey koduna sahip olsa bile (sınıf eğer örneğin bu da fazlalaştı Colmasaydı), sen belki diğer kod tarafından ilk ithal var hangi modül bağlı bir hata alıyorum.
Blckknght

2
@TylerCrompton: "Modül ithalatı mutlak olmalıdır" derken ne demek istediğinizden emin değilim. Döngüsel göreli içe aktarmalar, modülleri içe aktardığınız sürece, içeriklerini değil (örneğin from . import sibling_module, değil from .sibling_module import SomeClass) çalışabilir . Bir paket __init__.pydosyası döngüsel içe aktarmaya dahil olduğunda biraz daha incelikli olur , ancak sorun hem nadirdir hem de importuygulamadaki bir hatadır . Bir yama gönderdiğim Python bug 23447'ye bakın (ki ne yazık ki zayıflıyor ).
Blckknght

51

Bir modülü (veya bir üyesini) ilk kez içe aktardığınızda, modül içindeki kod diğer kodlar gibi sırayla yürütülür; örneğin, bir fonksiyonun bedeninden farklı bir şekilde ele alınmaz. An import, diğerleri gibi yalnızca bir komuttur (atama, işlev çağrısı def, class). İçe aktarmalarınızın komut dosyasının en üstünde gerçekleştiğini varsayarsak, işte olanlar:

  • İçe çalıştığınızda Worldgelen world, worldsenaryo çalıştırılmaktadır.
  • worldSenaryo ithalatı Fieldneden olur entities.fieldsenaryoyu idam etmek.
  • Bu işlem, entities.postkomut dosyasına ulaşana kadar devam eder, çünkü içeri aktarmaya çalıştınızPost
  • entities.postKomut nedenleri physicsbunlar içe aktarma çalışması nedeniyle modül yürütülecekPostBody
  • Son olarak, physicsiçe çalışır Postdanentities.post
  • entities.postModülün bellekte olup olmadığından henüz emin değilim , ama gerçekten önemli değil. Ya modül bellekte değil ya da modülün henüz bir Postüyesi yok çünkü tanımlamayı bitirmedi.Post
  • Her iki durumda da, Postiçe aktarılacak olmadığından bir hata oluşur

Yani hayır, "çağrı yığınında daha fazla çalışmıyor". Bu, hatanın meydana geldiği yerin bir yığın izlemesidir, bu Post, o sınıfa aktarmaya çalışırken hata yaptığı anlamına gelir . Döngüsel içe aktarma kullanmamalısınız. En iyi ihtimalle, ihmal edilebilir faydası vardır (tipik olarak faydası yoktur ) ve bunun gibi sorunlara neden olur. Herhangi bir geliştiricinin onu sürdürmesini zorlaştırır ve onları kırmamak için yumurta kabukları üzerinde yürümeye zorlar. Modül organizasyonunuzu yeniden düzenleyin.


1
Olmalı isinstance(userData, Post). Her şeye rağmen, seçeneğin yok. Döngüsel içe aktarma çalışmaz. Dairesel ithalatınız olması benim için bir kod kokusu. Üçüncü bir modüle taşınması gereken bazı işlevlere sahip olduğunuzu gösterir. Her iki sınıfa da bakmadan ne olduğunu söyleyemezdim.
jpmc26

3
@CpILL Bir süre sonra çok hackli bir seçenek aklıma geldi. Eğer (zaman kısıtlamaları nedeniyle ya da ne olduğun için) artık bunu yapıyor etrafında alamıyorsanız, o zaman olabilir kullandıysanız bunu yöntem içinde yerel olarak ithalat yapmak. İçerideki bir işlev gövdesi def, işlev çağrılana kadar çalıştırılmaz, bu nedenle siz işlevi gerçekten çağırana kadar içe aktarma gerçekleşmez. O zamana kadar, importmodüllerden biri çağrıdan önce tamamen içe aktarılmış olacağından, s'ler çalışmalıdır. Bu kesinlikle iğrenç bir hack'tir ve önemli bir süre boyunca kod tabanınızda kalmamalıdır.
jpmc26

15
Bence cevabınız dairesel ithalatta çok sert geliyor. Sadece yaparsanız dairesel ithalatı genellikle işe import fooziyade from foo import Bar. Bunun nedeni, çoğu modülün daha sonra çalışan şeyleri (işlevler ve sınıflar gibi) tanımlamasıdır. Bunları içe aktardığınızda önemli şeyler yapan modüller (korunmayan bir komut dosyası gibi if __name__ == "__main__") yine de sorun olabilir, ancak bu çok yaygın değildir.
Blckknght

6
@Blckknght Diğer insanların araştırmak zorunda kalacağı ve döngüsel ithalatı kullanırsanız kafanızın karışacağı garip sorunlara zaman ayırmak için kendinizi ayarladığınızı düşünüyorum. Sizi, onların üzerinden geçmemeye dikkat ederek zaman harcamaya zorlarlar ve bunun da ötesinde, tasarımınızın yeniden düzenleme gerektirdiği bir kod kokusu vardır. Teknik olarak uygulanabilir olup olmadıkları konusunda yanılmış olabilirim, ancak er ya da geç sorunlara yol açacak korkunç bir tasarım seçimi. Açıklık ve basitlik, programlamada kutsal ölçülerdir ve döngüsel içe aktarmalar her ikisini de kitabımda ihlal eder.
jpmc26

6
Alternatif olarak; işlevselliğinizi çok fazla böldünüz ve döngüsel içe aktarmanın nedeni budur. Eğer birbirlerine güvenmek iki şey var her zaman ; bunları tek bir dosyaya koymak en iyisi olabilir. Python Java değildir; garip içe aktarma mantığını önlemek için işlevselliği / sınıfları tek bir dosyada gruplamamak için hiçbir neden yok. :-)
Mark Ribau

41

Döngüsel bağımlılıkları anlamak için Python'un aslında bir betik dili olduğunu hatırlamanız gerekir. Yöntemlerin dışındaki ifadelerin yürütülmesi derleme zamanında gerçekleşir. Import deyimleri tıpkı yöntem çağrıları gibi yürütülür ve onları anlamak için onları yöntem çağrıları gibi düşünmelisiniz.

İçe aktarma yaptığınızda, ne olacağı, içe aktardığınız dosyanın modül tablosunda zaten mevcut olup olmamasına bağlıdır. Varsa, Python o anda sembol tablosunda olanı kullanır. Değilse, Python modül dosyasını okumaya başlar, orada bulduğu her şeyi derler / yürütür / içe aktarır. Derleme zamanında referans verilen semboller, derleyici tarafından görülüp görülmediklerine veya henüz görülmeyeceklerine bağlı olarak bulunur veya bulunmaz.

İki kaynak dosyanız olduğunu hayal edin:

X.py dosyası

def X1:
    return "x1"

from Y import Y2

def X2:
    return "x2"

Y.py dosyası

def Y1:
    return "y1"

from X import X1

def Y2:
    return "y2"

Şimdi X.py dosyasını derlediğinizi varsayalım. Derleyici, X1 yöntemini tanımlayarak başlar ve ardından X.py'deki içe aktarma ifadesine ulaşır. Bu, derleyicinin X.py derlemesini duraklatmasına ve Y.py'yi derlemeye başlamasına neden olur. Kısa bir süre sonra derleyici Y.py'deki import ifadesine ulaşır. X.py zaten modül tablosunda olduğundan, Python, istenen tüm referansları karşılamak için mevcut tamamlanmamış X.py sembol tablosunu kullanır. X.py'de import ifadesinden önce görünen herhangi bir sembol artık sembol tablosundadır, ancak ondan sonraki semboller değildir. X1 artık import ifadesinden önce göründüğünden, başarıyla içe aktarılmıştır. Python daha sonra Y.py'yi derlemeye devam eder. Bunu yaparken Y2'yi tanımlar ve Y.py'yi derlemeyi bitirir. Daha sonra X.py'nin derlenmesine devam eder ve Y.py sembol tablosunda Y2'yi bulur. Derleme sonunda hatasız olarak tamamlanır.

Y.py'yi komut satırından derlemeye çalışırsanız çok farklı bir şey olur. Y.py'yi derlerken, derleyici Y2'yi tanımlamadan önce import ifadesine ulaşır. Ardından X.py'yi derlemeye başlar. Yakında X.py'deki Y2 gerektiren import ifadesine ulaşır. Ancak Y2 tanımsızdır, bu nedenle derleme başarısız olur.

Lütfen X.py'yi Y1'i içe aktaracak şekilde değiştirirseniz, hangi dosyayı derlerseniz derleyin, derlemenin her zaman başarılı olacağını unutmayın. Ancak, X2 sembolünü içe aktarmak için Y.py dosyasını değiştirirseniz, hiçbir dosya derlenmez.

X modülü veya X tarafından içe aktarılan herhangi bir modül mevcut modülü içe aktarabilirse, şunları KULLANMAYIN:

from X import Y

Döngüsel bir içe aktarım olabileceğini düşündüğünüz her zaman, diğer modüllerdeki değişkenlere derleme zamanı referanslarından da kaçınmalısınız. Masum görünen kodu düşünün:

import X
z = X.Y

Diyelim ki modül X, bu modül X'i içe aktarmadan önce bu modülü içe aktarıyor. Ayrıca, Y'nin X'de import ifadesinden sonra tanımlandığını varsayalım. O zaman bu modül içe aktarıldığında Y tanımlanmayacak ve bir derleme hatası alacaksınız. Bu modül önce Y'yi içe aktarırsa, bundan kurtulabilirsiniz. Ancak iş arkadaşlarınızdan biri üçüncü bir modüldeki tanımların sırasını masum bir şekilde değiştirdiğinde, kod bozulur.

Bazı durumlarda, bir import ifadesini diğer modüllerin ihtiyaç duyduğu sembol tanımlarının altına taşıyarak döngüsel bağımlılıkları çözebilirsiniz. Yukarıdaki örneklerde, import ifadesinden önceki tanımlar asla başarısız olmaz. Import ifadesinden sonraki tanımlar, derleme sırasına bağlı olarak bazen başarısız olur. Derleme sırasında içe aktarılan sembollerin hiçbirine ihtiyaç duyulmadığı sürece, içe aktarma ifadelerini bir dosyanın sonuna bile koyabilirsiniz.

Bir modülde içe aktarma ifadelerinin aşağı taşınmasının, yaptığınız şeyi gizlediğini unutmayın. Bunu, modülünüzün üst kısmına aşağıdaki gibi bir yorum ekleyerek telafi edin:

#import X   (actual import moved down to avoid circular dependency)

Genelde bu kötü bir uygulamadır, ancak bazen kaçınılması zordur.



6
Python yapar bir derleyici var ve bir derleme sadece genellikle kullanıcıdan uzak gizlidir, @pkqxdd derlenmiş. Bu biraz kafa karıştırıcı olabilir, ancak yazarın, Python'un biraz anlaşılmaz "derleme zamanı" na atıfta bulunmadan neler olup bittiğine dair bu hayranlık uyandıracak kadar açık tanımını vermesi zor olacaktır.
Hank


Bunu makinemde denemeye devam ettim ve farklı bir sonuç aldım. X.py çalıştırıldı ancak "" Y "den" Y2 "adı içe aktarılamıyor" hatası aldı. Yine de sorunsuz koştu. Python 3.7.5 kullanıyorum, buradaki sorunun ne olduğunu açıklamaya yardımcı olabilir misiniz?
xuefeng huang

18

Benim gibi Django'dan bu konuya gelenleriniz için, belgelerin bir çözüm sağladığını bilmelisiniz: https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey

"... Başka bir uygulamada tanımlanan modellere başvurmak için, tam uygulama etiketine sahip bir modeli açıkça belirtebilirsiniz. Örneğin, yukarıdaki Üretici modeli, üretim adı verilen başka bir uygulamada tanımlanmışsa, şunları kullanmanız gerekir:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',
        on_delete=models.CASCADE,
)

Bu tür bir referans, iki uygulama arasındaki döngüsel içe aktarma bağımlılıklarını çözerken yararlı olabilir. ... "


6
"Teşekkür etmek" için yorum kullanmamam gerektiğini biliyorum, ama bu birkaç saattir beni rahatsız ediyor. Teşekkürler teşekkürler teşekkürler!!!
MikeyE

@MikeyE'ye katılıyorum. Bunu PonyORM ile düzeltmeye çalışan birkaç blog ve Stackoverflow okudum. Başkalarının bunun kötü bir uygulama olduğunu söylediği veya neden sınıflarınızı döngüsel olarak kodlayasınız ki, ORM'ler tam olarak bunun gerçekleştiği yerdir. Birçok örnek tüm modelleri aynı dosyaya koyduğundan ve bu örnekleri takip ettiğimizden, dosya başına bir model kullanmamız dışında, Python derleme yapamadığında sorun net değildir. Yine de cevap çok basit. Mike'ın da işaret ettiği gibi, çok teşekkür ederim.
trash80

4

Modülü, bu modüldeki nesneleri gerektiren işlev (yalnızca) içinde içe aktarabildim:

def my_func():
    import Foo
    foo_instance = Foo()

ne kadar zarif bir piton
Yaro

2

Oldukça karmaşık bir uygulamada bu sorunla karşılaşırsanız, tüm içe aktarmalarınızı yeniden düzenlemek külfetli olabilir. PyCharm, bunun için içe aktarılan sembollerin tüm kullanımlarını da otomatik olarak değiştirecek bir hızlı düzeltme sunar.

görüntü açıklamasını buraya girin


0

Aşağıdakileri kullanıyordum:

from module import Foo

foo_instance = Foo()

ama kurtulmak circular referenceiçin aşağıdakileri yaptım ve işe yaradı:

import module.foo

foo_instance = foo.Foo()
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.