göreli içe aktarmada üst düzey paket hatasının ötesinde


316

Görünüşe göre burada python 3'te göreceli ithalat hakkında bazı sorular var, ancak birçoğundan geçtikten sonra hala sorunumun cevabını bulamadım. işte soru.

Aşağıda gösterilen bir paketim var

package/
   __init__.py
   A/
      __init__.py
      foo.py
   test_A/
      __init__.py
      test.py

test.py'de tek bir satır var:

from ..A import foo

şimdi, klasöründeyim packageve koşuyorum

python -m test_A.test

Mesajım var

"ValueError: attempted relative import beyond top-level package"

ama eğer üst klasörde package, örneğin, ben çalıştırmak:

cd ..
python -m package.test_A.test

herşey yolunda.

Şimdi sorum şu: klasöründe packageolduğumda, ve test_A alt paketinin içindeki modülü çalıştırdığımda, test_A.testanlayışım temelinde ..A, hala packageklasör içinde olan sadece bir düzey yukarı çıkıyor , neden mesaj söyleyerek veriyor beyond top-level package. Bu hata iletisine neden olan neden tam olarak nedir?


49
bu yazı benim "üst düzey paket ötesinde" hata açıklanmadı
sığınak

4
Burada bir düşüncem var, bu yüzden test_A.test'i modül olarak çalıştırdığınızda, '..' zaten ithal test_A.test'in en yüksek seviyesi olan test_A'nın üzerine çıktığında, paket seviyesinin dizin seviyesi değil, kaç tane olduğunu düşünüyorum seviyeleri içe aktarırsınız.
sığınak

2
Bu cevabı izledikten sonra göreceli ithalatla ilgili her şeyi anlayacağınıza söz veriyorum stackoverflow.com/a/14132912/8682868 .
pzjzeason


Göreli ithalat yapmaktan kaçınmanın bir yolu var mı? Eclipse'deki PyDev'in <PydevProject> / src içindeki tüm paketleri görmesi gibi?
Mushu909

Yanıtlar:


172

EDIT: Bu soruya diğer sorularda daha iyi / daha tutarlı cevaplar var:


Neden çalışmıyor? Çünkü python bir paketin nereden yüklendiğini kaydetmez. Yani bunu yaptığınızda python -m test_A.test, aslında sadece test_A.testiçinde saklanan bilgiyi atar package(yani packagebir paket olarak kabul edilmez). Deneme from ..A import foo, artık sahip olmadığı bilgilere erişmeye çalışıyor (yani yüklü bir konumun kardeş dizinleri). Kavramsal from ..os import patholarak bir dosyaya izin vermeye benzer math. Bu kötü olurdu çünkü paketlerin farklı olmasını istiyorsunuz. Onlar başka paketten bir şeyler kullanmanız gerekiyorsa, o zaman birlikte küresel onlara başvurmalıdır from os import pathve bununla nerede piton çalışmaları izin $PATHve $PYTHONPATH.

Eğer kullandığınız zaman python -m package.test_A.test, o zaman kullanarak from ..A import fooiçeri ne olduğunu takip tuttu çünkü sadece iyi giderir packageve sadece yüklü bir yerin bir çocuk dizine erişimini ediyoruz.

Python neden geçerli çalışma dizinini bir paket olarak görmüyor? CLUE YOK , ama tanrım faydalı olur.


2
Cevabımı, aynı anlama gelen bir soruya daha iyi bir yanıt vermek için düzenledim. Sadece geçici çözümler var. Aslında iş gördüğüm tek şey OP'nin yaptığı, -mbayrağı kullanmak ve yukarıdaki dizinden çalıştırmak.
Multihunter

1
Multihunter tarafından verilen bağlantıdan gelen bu cevabınsys.path hack içermediğini , ancak bence çok daha ilginç olan kurulum araçlarının kullanımını belirtmek gerekir .
Angelo Cardellicchio

157
import sys
sys.path.append("..") # Adds higher directory to python modules path.

Bunu dene. Benim için çalıştı.


10
Hımm ... bu nasıl işler? Her bir test dosyasında bu olur mu?
George Mauer

Buradaki sorun, örneğin, eğer A/bar.pyvar ve foo.pysizin içinizde from .bar import X.
user1834164

9
Ben sys.path.append ("..") ekledikten sonra .. "" ..A ithalat ... "dan kaldırmak zorunda kaldı
Jake OPJ

2
Komut dosyası bulunduğu dizinin dışından yürütülürse, bu işe yaramaz. Bunun yerine, söz konusu komut dosyasının mutlak yolunu belirtmek için bu yanıtı değiştirmeniz gerekir .
Manavalan Gajapathy

Bu en iyi, en az karmaşık seçenektir
Alex R

43

Varsayım:
Eğer varsa packagedizin Ave test_Aayrı paketler.

Sonuç:
..Aithalata sadece paket içinde izin verilmektedir.

Ek notlar:
Göreli içe aktarmaları yalnızca paketler içinde kullanılabilir yapmak, paketlerin üzerinde bulunan herhangi bir yola yerleştirilmesini zorlamak istiyorsanız yararlıdır sys.path.

DÜZENLE:

Bunun deli olduğunu düşünen tek kişi ben miyim? Neden dünyadaki mevcut dizin bir paket olarak kabul edilmiyor? - Çoklu avcı

Geçerli çalışma dizini genellikle sys.path dosyasında bulunur. Yani, tüm dosyalar içe aktarılabilir. Bu henüz Python 2 paketleri olmadığı zamanki davranıştır. Çalışan dizinin bir paket haline getirilmesi, modüllerin "import .A" ve "import A" olarak içe aktarılmasına izin verecek ve bu da iki farklı modül olacaktır. Belki de bu dikkate alınması gereken bir tutarsızlıktır.


86
Bunun deli olduğunu düşünen tek kişi ben miyim? Dünyada neden çalışan dizin bir paket olarak kabul edilmiyor?
Multihunter

13
Sadece delilik değil, bu da yararsızdır ... peki o zaman nasıl test yaparsınız? Açıkçası OP'nin sorduğu şey ve neden birçok insanın burada olduğundan da eminim.
George Mauer

Çalışan dizin genellikle sys.path içinde bulunur. Yani, tüm dosyalar içe aktarılabilir. Bu henüz Python 2 paketleri olmadığı zamanki davranıştır. - düzenlenmiş cevap.
Kullanıcı

Tutarsızlığı takip etmiyorum. Davranışı python -m package.test_A.testistenen şeyi yapıyor gibi görünüyor ve benim argüman varsayılan olması gerektiğidir. Bana bu tutarsızlığa bir örnek verebilir misiniz?
Multihunter

Aslında düşünüyorum, bunun için bir özellik isteği var mı? Bu gerçekten delilik. C / C ++ tarzı #includeçok yararlı olurdu!
Nicholas Humphrey

29

Bu çözümlerin hiçbiri 3.6'da benim için işe yaramadı, aşağıdaki gibi bir klasör yapısı ile:

package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py

Amacım modül1'den modül2'ye aktarmaktı. Sonunda benim için işe yarayan, garip bir şekilde:

import sys
sys.path.append(".")

Şimdiye kadar belirtilen iki noktalı çözümlerin aksine tek noktayı not edin.


Düzenleme: Aşağıdaki benim için bu açıklığa yardımcı oldu:

import os
print (os.getcwd())

Benim durumumda, çalışma dizini (beklenmedik bir şekilde) projenin kökü idi.


2
yerel olarak çalışıyor ancak aws ec2 örneğinde çalışmıyor, bir anlam ifade ediyor mu?
thebeancounter

Bu benim için de işe yaradı - benim durumumda çalışma dizini de aynı şekilde proje kökü idi. Bir programlama editöründen bir çalışma kısayolu kullanıyordum (TextMate)
JeremyDouglass

@thebeancounter Aynı! Mac'imde yerel olarak çalışıyor ancak ec2'de çalışmıyor, o zaman komutu ec2'de bir alt dizinde çalıştırdığımı ve yerel olarak kökte çalıştırdığımı fark ettim. Bir kez ec2 üzerinde kökten çalıştırdı.
Logan Yang

Bu da benim için çok çalıştı. Bu sys yönteminden şimdi ".." gerek kalmadan paketi çağırabilirsiniz
RamWill

sys.path.append(".")üst dizinde çağırdığınız için çalıştı, .her zaman python komutunu çalıştırdığınız dizini temsil ettiğini unutmayın .
KevinZhou

13

from package.A import foo

Bence daha net

import sys
sys.path.append("..")

4
emin daha okunabilir ama yine de ihtiyacı var sys.path.append(".."). 3.6
MFA

Daha eski yanıtlarla aynı
nrofis

12

En popüler yanıtın da belirttiği gibi, temel olarak bunun nedeni sizin paketiniz için yolunuzu içermesi PYTHONPATHveya içermemesidir. Göreli içe aktarma, içe aktarmanın gerçekleştiği dosyaya değil, geçerli çalışma dizininize göredir; garip bir şekilde.sys.path.

Bunu, önce göreli içe aktarma işleminizi mutlak olarak değiştirip ardından şunlarla başlatarak düzeltebilirsiniz:

PYTHONPATH=/path/to/package python -m test_A.test

VEYA bu şekilde çağrıldığında python yolunu zorlar, çünkü:

İle python -m test_A.testEğer infaz ediyoruz test_A/test.pyile __name__ == '__main__'ve__file__ == '/absolute/path/to/test_A/test.py'

Bu test.py, mutlak importyarı korumalı ana durum koşullarında kullanabileceğiniz ve ayrıca bir kerelik Python yolu manipülasyonu yapabileceğiniz anlamına gelir :

from os import path

def main():

if __name__ == '__main__':
    import sys
    sys.path.append(path.join(path.dirname(__file__), '..'))
    from A import foo

    exit(main())

8

Düzenleme: 2020-05-08: Alıntı yapılan web sitesi artık tavsiye yazdı kişi tarafından kontrol edilmiyor gibi görünüyor, bu yüzden sitenin bağlantısını kaldırıyorum. Bana haber verdiğin için teşekkürler baxx.


Birisi zaten verilen harika cevaplardan sonra hala biraz mücadele ediyorsa, bir web sitesinde artık mevcut olmayan tavsiyeler buldum.

Bahsettiğim siteden önemli alıntı:

"Aynı şey programlı olarak şu şekilde belirtilebilir:

ithalat sys

sys.path.append ( '..')

Tabii ki yukarıdaki kod diğer ithalat ifadesinden önce yazılmalıdır .

Aslında böyle düşünerek, bu şekilde olması gerektiği çok açık. Testlerimde sys.path.append ('..') kullanmaya çalışıyordum, ancak OP tarafından yayınlanan sorunla karşılaştım. Diğer ithalatlarımdan önce import ve sys.path tanımını ekleyerek sorunu çözebildim.


gönderdiğiniz bağlantı öldü.
baxx

Bilmeme izin verdiğin için teşekkürler. Alan adının artık aynı kişi tarafından kontrol edilmediği anlaşılıyor. Bağlantıyı kaldırdım.
Mierpo

5

Bir varsa __init__.pybir üst klasörde, siz ithalat başlatabilir import file/path as aliasyani init dosyasında. Daha sonra alt komut dosyalarında şu şekilde kullanabilirsiniz:

import alias

0

Benim düşünceme göre, bu soruyu şu şekilde anlıyorum:

[CASE 1] Aşağıdaki gibi mutlak bir içe aktarma başlattığınızda

python -m test_A.test

veya

import test_A.test

veya

from test_A import test

aslında kuruyorsunuz ithalat çapa olma test_A, diğer kelime, üst düzey bir pakettir test_A. Yani, test.py do yaptığımız zaman, çapadan kaçıyorsunuz from ..A import xxxve Python buna izin vermiyor.

[DURUM 2] Bunu yaptığınızda

python -m package.test_A.test

veya

from package.test_A import test

çapanız olur package, bu nedenle package/test_A/test.pyyapmak from ..A import xxxçapadan (hala packageklasör içinde ) kaçmaz ve Python bunu memnuniyetle kabul eder.

Kısacası:

  • Mutlak içe aktarma geçerli bağlantıyı değiştirir (= üst düzey paketin ne olduğunu yeniden tanımlar);
  • Bağıl-ithalat ankrajı değiştirmez ancak onunla sınırlıdır.

Ayrıca, bu sorunu incelemek için tam nitelikli modül adını (FQMN) kullanabiliriz.

Her durumda FQMN'yi kontrol edin:

  • [CASE2] test.__name__=package.test_A.test
  • [CASE1] test.__name__=test_A.test

Bu nedenle, CASE2 için , kabul edilebilir olan from .. import xxxFQMN = package.xxxolan yeni bir modül ile sonuçlanacaktır .

CASE1 için, içeriden ..gelen from .. import xxx, başlangıç ​​düğümünden (çapa) dışarı atlar test_Ave buna Python tarafından izin verilmez.


2
Bu, olması gerekenden çok daha karmaşık. Python Zen için çok fazla.
AtilioA

0

Python 2.x'te emin değilim, ancak python 3.6'da, tüm paketi çalıştırmaya çalıştığınızı varsayarsak, sadece -t

-t, --top-level-directory dizini Projenin en üst düzey dizini (varsayılan dizini başlatma)

Yani,

project_root
  |
  |----- my_module
  |          \
  |           \_____ my_class.py
  |
  \ tests
      \___ test_my_func.py

Örneğin aşağıdakiler kullanılabilir:

python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/

Ve hala my_module.my_classbüyük dramalar olmadan ithal .

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.