Python'da göreli ithalat nasıl yapılır?


527

Bu dizin yapısını düşünün:

app/
   __init__.py
   sub1/
      __init__.py
      mod1.py
   sub2/
      __init__.py
      mod2.py

Kod mod1yazıyorum ve bir şey içe aktarmam gerekiyor mod2. Bunu nasıl yapmalıyım?

Denedim from ..sub2 import mod2ama bir "paket olmayan göreceli ithalat denedi " alıyorum.

Etrafta dolaştım ama sadece " sys.pathmanipülasyon" kesmek buldum . Temiz bir yol yok mu?


Düzenleme: tümü benim __init__.pyşu anda boş

Edit2: Ben sub2 alt paketleri (arasında paylaşılır sınıfları içerdiğinden bu yapmaya çalışıyorum sub1, subXvs.).

Edit3: anlatıldığı gibi aradığım davranış aynıdır PEP 366 (teşekkürler John B)


8
PEP 366'da ele alınan sorunu açıkladığınızı daha açık hale getirmek için sorunuzu güncellemenizi öneririz.
John B

2
Uzun soluklu bir açıklama ama buraya bakın: stackoverflow.com/a/10713254/1267156 Çok benzer bir soruyu yanıtladı. Dün geceye kadar aynı problemi yaşadım.
Sevvy325

3
Rasgele bir yolda bulunan bir modülü yüklemek isteyenler için,
şuna

2
İlgili bir notta, Python 3 varsayılan olarak içe aktarma işlemlerini varsayılan olarak mutlak olacak şekilde değiştirecektir; göreli ithalatların açıkça belirtilmesi gerekir.
Ross

Yanıtlar:


337

Herkes size sadece soruyu cevaplamak yerine ne yapmanız gerektiğini söylemek istiyor gibi görünüyor.

Sorun, mod1.py'yi yorumlayıcıya argüman olarak ileterek modülü '__main__' olarak çalıştırmanızdır.

Kaynaktan PEP 328 :

Göreli içe aktarmalar, modülün paket hiyerarşisindeki konumunu belirlemek için bir modülün __name__ özelliğini kullanır. Modülün adı herhangi bir paket bilgisi içermiyorsa (örn. '__Main__' olarak ayarlanmışsa), göreli ithalatlar, modülün gerçekte dosya sisteminde nerede bulunduğuna bakılmaksızın modülün en üst düzey bir modülmiş gibi çözümlenir.

Python 2.6'da, ana modüle göre modüllere referans verme yeteneği ekliyorlar. PEP 366 değişikliği açıklar.

Güncelleme : Nick Coghlan'a göre önerilen alternatif, -m anahtarını kullanarak modülü paketin içinde çalıştırmaktır.


2
Bu sorunun cevabı, programınızın her giriş noktasında sys.path ile uğraşmayı içerir. Sanırım bunu yapmanın tek yolu bu.
Nick Retallack

76
Önerilen alternatif, -mdosya adlarını doğrudan belirtmek yerine , anahtarları kullanarak paketleri paket içinde çalıştırmaktır .
ncoghlan

127
Anlamıyorum: cevap burada mı? Böyle bir dizin yapısında modüller nasıl içe aktarılabilir?
Tom

27
@Tom: Bu örnekte mod1 olurdu from sub2 import mod2. Ardından, mod1'i çalıştırmak için uygulama içinden yapın python -m sub1.mod1.
Xiong Chiamiov

11
@XiongChiamiov: bu, python'unuz bir uygulamaya yerleştirilmişse bunu yapamayacağınız anlamına mı geliyor, bu nedenle python komut satırı anahtarlarına erişiminiz yok mu?
LarsH

130

İşte benim için çalışan çözüm:

Ben göreceli ithalat olarak yapmak from ..sub2 import mod2 ve sonra, eğer çalıştırmak istiyorsanız mod1.pyo zaman üst dizinine gidin appve python -m anahtarını kullanarak modülü çalıştırın python -m app.sub1.mod1.

Bu sorunun göreli ithalatlarda ortaya çıkmasının asıl nedeni, göreli ithalatın __name__modülün özelliğini alarak çalışmasıdır . Modül doğrudan çalıştırılıyorsa, __name__olarak ayarlanır __main__ve paket yapısı hakkında herhangi bir bilgi içermez. Ve bu yüzden python relative import in non-packagehatadan şikayet ediyor .

Böylece, -m anahtarını kullanarak göreli içe aktarma işlemlerini başarıyla çözebileceği python'a paket yapısı bilgilerini sağlarsınız.

Göreceli ithalat yaparken bu problemle birçok kez karşılaştım. Ve önceki tüm cevapları okuduktan sonra, tüm dosyalara bobin plakası kodu koymaya gerek kalmadan, temiz bir şekilde nasıl çözüleceğini anlayamadım. (@Ncoghlan ve @XiongChiamiov sayesinde yorumların bazıları gerçekten yardımcı olsa da)

Umarım bu göreceli ithalat problemi ile mücadele eden birine yardımcı olur, çünkü PEP'den geçmek gerçekten eğlenceli değildir.


9
En iyi cevap IMHO: Sadece OP'nin neden sorunu olduğunu açıklamakla kalmıyor, aynı zamanda modüllerinin ithalat yapma şeklini değiştirmeden sorunu çözmenin bir yolunu buluyor . Afterall, OP'nin göreli ithalatı iyiydi. Suçlu, doğrudan komut dosyası olarak çalışırken dış paketlere erişim eksikliğiydi, -mçözmek için bir şeyler tasarlandı.
MestreLion

26
Ayrıca dikkat edin: bu cevap sorudan 5 yıl sonraydı. Bu özellikler şu anda mevcut değildi.
JeremyKun

1
Aynı dizinden bir modül almak isterseniz, bunu yapabilirsiniz from . import some_module.
Rotareti

124
main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. Sen koş python main.py.
  2. main.py yapar: import app.package_a.module_a
  3. module_a.py yapar import app.package_b.module_b

Alternatif olarak 2 veya 3 şunları kullanabilir: from app.package_a import module_a

Bu appPYTHONPATH'nızda olduğu sürece çalışacaktır . main.pyo zaman her yerde olabilir.

Böylece setup.py, tüm uygulama paketini ve alt paketleri hedef sistemin python klasörlerine ve main.pyhedef sistemin komut dosyası klasörlerine kopyalamak (yüklemek) için bir yazarsınız .


3
Mükemmel cevap. Paketi PYTHONPATH'a yüklemeden bu şekilde içe aktarmanın bir yolu var mı?
auraham


6
daha sonra, bir gün, test_app için uygulamanın adını değiştirmeniz gerekir. ne olurdu? Tüm kaynak kodlarını değiştirmeniz, app.package_b.module_b -> test_app.package_b.module_b dosyasını içe aktarmanız gerekir. bu kesinlikle KÖTÜ bir uygulamadır ... Ve paket içinde göreceli içe aktarmayı kullanmaya çalışmalıyız.
Spybdai

49

"Guido, bir paket içinde çalışan komut dosyalarını bir anti-desen olarak görür" (reddedilen PEP-3122 )

Bir çözüm bulmaya çalışırken, Stack Overflow'daki ilgili yazıları okuyarak ve kendime "daha iyi bir yol olmalı!" Diyerek çok zaman harcadım. Görünmüyor gibi.


10
Not: Daha önce bahsedilen pep-366 ( pep-3122 ile aynı zamanda oluşturulmuştur ), aynı yetenekleri sağlar, ancak farklı bir geriye dönük uyumlu uygulama kullanır; örneğin, bir paketin içindeki bir modülü komut dosyası olarak çalıştırmak ve açık göreli içe aktarmalar kullanmak istiyorsanız daha sonra -mswitch: komutunu kullanarak çalıştırabilir python -m app.sub1.mod1veya app.sub1.mod1.main()üst düzey bir komut dosyasından (ör. setuptools'un setup.py içinde tanımlanan input_points öğelerinden oluşturulan) çağırabilirsiniz.
jfs

Kurulum araçlarını ve giriş noktalarını kullanmak için +1 - PYTHONPATH
RecencyEffect

38

Bu% 100 çözüldü:

  • Uygulamanın /
    • main.py
  • ayarları /
    • local_setings.py

Uygulama / main.py'deki ayarları / local_setting.py dosyasını içe aktarın:

main.py:

import sys
sys.path.insert(0, "../settings")


try:
    from local_settings import *
except ImportError:
    print('No Import')

2
teşekkür ederim! tüm ppl bana betiğin içinde nasıl çözüleceğini söylemek yerine betiğimi farklı çalıştırmaya zorluyordu. Ama kullanmak için kodu değiştirmek zorunda kaldı sys.path.insert(0, "../settings")ve sonrafrom local_settings import *
Vit Bernatik

25
def import_path(fullpath):
    """ 
    Import a file with full path specification. Allows one to
    import from anywhere, something __import__ does not do. 
    """
    path, filename = os.path.split(fullpath)
    filename, ext = os.path.splitext(filename)
    sys.path.append(path)
    module = __import__(filename)
    reload(module) # Might be out of date
    del sys.path[-1]
    return module

Modülleri yollardan içe aktarmak için bu snippet'i kullanıyorum, umarım yardımcı olur


2
Bu pasajı imp modülüyle birlikte kullanıyorum (burada açıklandığı gibi [1]). [1]: stackoverflow.com/questions/1096216/…
Xiong Chiamiov

7
Muhtemelen sys.path.append (yol), sys.path.insert (0, yol) ile değiştirilmeli ve sys.path [-1], sys.path [0] ile değiştirilmelidir. Aksi takdirde, arama yolunda aynı ada sahip bir modül varsa, işlev yanlış modülü içe aktarır. Örneğin, geçerli dizinde "some.py" varsa, import_path ("/ imports / some.py") yanlış dosyayı alır.
Alex Che

Katılıyorum! Bazen diğer göreceli ithalatlar öncelikli olacaktır. Kullan sys.path.insert
iElectric

X import y (veya *) 'dan davranışını nasıl çoğaltırsınız?
levesque

Açık değil, lütfen OP problemini çözmek için bu komut dosyasının tam kullanımını belirtin.
mrgloom

21

açıklaması nosklo'sörneklerle cevap

not: tüm __init__.pydosyalar boş.

main.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       fun_a.py
    package_b/ ->
       __init__.py
       fun_b.py

Uygulama / package_a / fun_a.py

def print_a():
    print 'This is a function in dir package_a'

Uygulama / package_b / fun_b.py

from app.package_a.fun_a import print_a
def print_b():
    print 'This is a function in dir package_b'
    print 'going to call a function in dir package_a'
    print '-'*30
    print_a()

main.py

from app.package_b import fun_b
fun_b.print_b()

Eğer çalıştırırsanız $ python main.pyşunu döndürür:

This is a function in dir package_b
going to call a function in dir package_a
------------------------------
This is a function in dir package_a
  • main.py şunları yapar: from app.package_b import fun_b
  • fun_b.py yapar from app.package_a.fun_a import print_a

Klasör dosya böylece package_bklasörde kullanılan dosyası package_ane istediğiniz. Sağ??


12

Bu ne yazık ki bir sys.path kesmek, ama oldukça iyi çalışıyor.

Başka bir katman ile bu sorunla karşılaştım: Zaten belirtilen adda bir modül vardı, ama yanlış modül oldu.

Yapmak istediğim şey şuydu (çalıştığım modül modül3 idi):

mymodule\
   __init__.py
   mymodule1\
      __init__.py
      mymodule1_1
   mymodule2\
      __init__.py
      mymodule2_1


import mymodule.mymodule1.mymodule1_1  

Daha önce mymodule'u kurduğumu unutmayın, ancak kurulumumda "mymodule1" yok

ve yüklü modüllerimden içe aktarmaya çalıştığı için bir ImportError alırdım.

Bir sys.path.append yapmaya çalıştım ve bu işe yaramadı. Ne işe yaradı sys.path.insert

if __name__ == '__main__':
    sys.path.insert(0, '../..')

Öyle bir hack, ama işe yaradı! Bu nedenle, kararınızın diğer yolları geçersiz kılmasını istiyorsanız, o zaman işe yaraması için sys.path.insert (0, yol adı) kullanmanız gerektiğini unutmayın! Bu benim için çok sinir bozucu bir yapışma noktasıydı, bir çok insan sys.path'e "append" işlevini kullandığını söylüyor, ancak zaten tanımlanmış bir modülünüz varsa işe yaramıyor (çok garip bir davranış buluyorum)


sys.path.append('../')benim için iyi çalışıyor (Python 3.5.2)
Nister

Bence bu yürütülebilir dosyayı yerelleştirir ve paketlerinize bağlı olabilecek diğer modülleri etkilemediğinden iyidir.
Tom Russell

10

Kendi referansım için buraya koyalım. İyi bir Python kodu olmadığını biliyorum, ama üzerinde çalıştığım bir proje için bir senaryoya ihtiyacım vardı ve senaryoyu bir scriptsdizine koymak istedim .

import os.path
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

9

@EvgeniSergeev'in OP yorumlarında söylediği gibi, .pykeyfi bir konumdaki bir dosyadan kodu aşağıdakilerle içe aktarabilirsiniz :

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

Bu SO cevabından alınmıştır .



2

Gönderen Python doc ,

Python 2.5'te, from __future__ import absolute_importyönerge kullanarak içe aktarma işleminin davranışını mutlak içe aktarma olarak değiştirebilirsiniz . Bu mutlak içe aktarma davranışı gelecekteki bir sürümde (muhtemelen Python 2.7) varsayılan değer olacaktır. Mutlak içe aktarmalar varsayılan olduğunda, import stringher zaman standart kitaplığın sürümünü bulur. Kullanıcıların olabildiğince mutlak ithalatı kullanmaya başlaması önerilir, bu nedenle from pkg import stringkodunuza yazmaya başlamak tercih edilir


1

En üst klasöre "PYTHONPATH" ortam değişkenini ayarlamanın daha kolay olduğunu gördüm:

bash$ export PYTHONPATH=/PATH/TO/APP

sonra:

import sub1.func1
#...more import

Tabii ki, PYTHONPATH "küresel", ama henüz benim için sorun yaratmadı.


Bu, temel olarak virtualenviçe aktarma ifadelerinizi yönetmenize olanak tanır.
byxor

1

John B'nin söylediklerinin üstüne, __package__değişkeni ayarlamak, __main__başka şeyleri berbat edebilecek olan değiştirmek yerine yardımcı olmalı gibi görünüyor . Ama test edebildiğim kadarıyla, olması gerektiği gibi çalışmıyor.

Aynı problemim var ve PEP 328 veya 366 da sorunu tamamen çözemiyor, çünkü her ikisi de, günün sonunda, sys.pathanlayabildiğim kadarıyla paketin başına dahil edilecek .

Ben de bu değişkenlere gitmesi gereken dizeyi biçimlendirmek nasıl bulamadık bahsetmeliyim. Öyle "package_head.subfolder.module_name"ya da ne?


0

Modülün yolunu aşağıdakilere eklemeniz gerekir PYTHONPATH:

export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"

1
Bu, manipüle ile kabaca aynıdır sys.path, çünkü sys.pathbaşlangıçtan başlarPYTHONPATH
Joril

@Joril Bu doğru, ancak bir ortam değişkeni olan ve dışa aktarılabilen sys.pathkaynak kodda sabit olarak kodlanması PYTHONPATHgerekiyor.
Giorgos Myrianthous
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.