Cara memperbaiki "Impor relatif yang diupayakan dalam non-paket" bahkan dengan __init__.py

744

Saya mencoba mengikuti PEP 328 , dengan struktur direktori berikut:

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

Dalam core_test.pySaya memiliki pernyataan impor berikut

from ..components.core import GameLoopEvents

Namun, ketika saya menjalankan, saya mendapatkan kesalahan berikut:

tests$ python core_test.py 
Traceback (most recent call last):
  File "core_test.py", line 3, in <module>
    from ..components.core import GameLoopEvents
ValueError: Attempted relative import in non-package

Mencari di sekitar saya menemukan " jalur relatif tidak bekerja bahkan dengan __init__.py " dan " Impor modul dari jalur relatif " tetapi mereka tidak membantu.

Apakah ada sesuatu yang saya lewatkan di sini?

skytreader
sumber
17
Saya juga sangat bingung dengan berbagai cara penataan unittestproyek, jadi saya menulis proyek sampel yang cukup lengkap ini yang mencakup bersarang modul yang mendalam, impor relatif dan absolut (di mana pekerjaan dan tidak), dan referensi relatif dan absolut dari dalam paket, serta impor kelas tingkat tunggal, ganda, dan paket. Membantu hal-hal yang jelas sampai untuk saya!
cod3monk3y
1
Saya tidak bisa menjalankan tes Anda. Terus dapatkan no module named myimports.foosaat saya menjalankannya.
Blairg23
@ Blairg23 Saya menduga permohonan yang dimaksudkan adalah cdke PyImports, dan menjalankan python -m unittest tests.test_abs, misalnya.
duozmo
7
Saya setuju dengan Gene. Saya berharap ada mekanisme untuk men-debug proses impor yang sedikit lebih membantu. Dalam kasus saya, saya memiliki dua file di direktori yang sama. Saya mencoba mengimpor satu file ke file lainnya. Jika saya memiliki file .py init di direktori itu, saya mendapatkan ValueError: Upaya impor relatif dalam kesalahan non-paket. Jika saya menghapus file init .py, maka saya mendapatkan kesalahan tanpa modul bernama kesalahan 'NAME'.
user1928764
Dalam kasus saya, saya memiliki dua file di direktori yang sama. Saya mencoba mengimpor satu file ke file lainnya. Jika saya memiliki file .py init di direktori itu, saya mendapatkan ValueError: Upaya impor relatif dalam kesalahan non-paket. Jika saya menghapus file init .py, maka saya mendapatkan kesalahan tanpa modul bernama kesalahan 'NAME'. Yang benar-benar membuat frustrasi adalah bahwa saya berhasil, dan kemudian saya menembak diri saya dengan menghapus file .bashrc, yang mengatur PYTHONPATH menjadi sesuatu, dan sekarang tidak berfungsi.
user1928764

Jawaban:

443

Iya. Anda tidak menggunakannya sebagai paket.

python -m pkg.tests.core_test
Ignacio Vazquez-Abrams
sumber
51
Gotcha: Perhatikan bahwa pada akhirnya tidak ada '.py'!
mindthief
497
Saya bukan salah satu dari downvoters, tetapi saya merasa ini bisa menggunakan sedikit lebih detail, mengingat popularitas pertanyaan dan jawaban ini. Memperhatikan hal-hal seperti dari direktori apa untuk mengeksekusi perintah shell di atas, fakta bahwa Anda perlu __init__.pysemua jalan turun, dan __package__tipuan -modifying (dijelaskan di bawah oleh BrenBarn) diperlukan untuk memungkinkan impor ini untuk skrip yang dapat dieksekusi (misalnya ketika menggunakan shebang dan lakukan ./my_script.pydi shell Unix) semuanya akan berguna. Seluruh masalah ini cukup sulit bagi saya untuk mencari tahu atau menemukan dokumentasi yang ringkas dan dapat dipahami.
Mark Amery
16
Catatan: Anda harus berada di luar direktori pkgpada titik di mana Anda memanggil saluran ini dari CLI. Maka, itu harus bekerja seperti yang diharapkan. Jika Anda di dalam pkgdan menelepon python -m tests.core_test, itu tidak akan berhasil. Setidaknya itu bukan untuk saya.
Blairg23
94
Serius, dapatkah Anda menjelaskan apa yang terjadi dalam jawaban Anda?
Pinocchio
18
@MarkAmery Hampir kehilangan akal saya mencoba grok bagaimana semua ini bekerja, impor relatif dalam proyek dengan subdirektori dengan file py yang memiliki __init__.pyfile namun Anda tetap mendapatkan ValueError: Attempted relative import in non-packagekesalahan. Saya akan membayar uang yang sangat baik untuk seseorang, di suatu tempat, untuk akhirnya menjelaskan dalam bahasa Inggris yang sederhana bagaimana semua ini bekerja.
AdjunctProfessorFalcon
635

Untuk menguraikan jawaban Ignacio Vazquez-Abrams :

Mekanisme impor Python bekerja relatif terhadap __name__file saat ini. Ketika Anda mengeksekusi file secara langsung, itu tidak memiliki nama yang biasa, tetapi memiliki "__main__"sebagai namanya. Jadi impor relatif tidak berfungsi.

Anda dapat, seperti yang disarankan Igancio, jalankan menggunakan -mopsi. Jika Anda memiliki bagian dari paket Anda yang dimaksudkan untuk dijalankan sebagai skrip, Anda juga dapat menggunakan __package__atribut untuk memberi tahu file itu nama apa yang seharusnya ada dalam hierarki paket.

Lihat http://www.python.org/dev/peps/pep-0366/ untuk detailnya.

BrenBarn
sumber
55
Butuh beberapa saat bagi saya untuk menyadari bahwa Anda tidak dapat lari python -m core_testdari dalam testssubdirektori - itu harus dari orangtua, atau Anda harus menambahkan induk ke jalan.
Aram Kocharyan
3
@DannyStaple: Tidak juga. Anda dapat menggunakan __package__untuk memastikan file skrip yang dapat dieksekusi relatif dapat mengimpor modul lain dari dalam paket yang sama. Tidak ada cara untuk mengimpor dari "keseluruhan sistem" secara relatif. Saya bahkan tidak yakin mengapa Anda ingin melakukan ini.
BrenBarn
2
Maksud saya jika __package__simbol diatur ke "parent.child" maka Anda akan dapat mengimpor "parent.other_child". Mungkin saya tidak mengucapkannya dengan baik.
Danny Staple
5
@DannyStaple: Ya, cara kerjanya dijelaskan dalam dokumentasi yang ditautkan. Jika Anda memiliki script script.pydalam paket pack.subpack, maka pengaturan itu __package__untuk pack.subpackakan membiarkan Anda melakukan from ..module import somethinguntuk impor sesuatu dari pack.module. Perhatikan bahwa, seperti yang dikatakan dalam dokumentasi, Anda masih harus memiliki paket tingkat atas di jalur sistem. Ini sudah merupakan cara kerja untuk modul yang diimpor. Satu-satunya hal yang __package__dilakukan adalah membiarkan Anda menggunakan perilaku itu untuk skrip yang dieksekusi langsung juga.
BrenBarn
3
Saya menggunakan __package__dalam skrip yang dieksekusi secara langsung tetapi Sayangnya, saya mendapatkan kesalahan berikut: "Modul induk 'xxx' tidak dimuat, tidak dapat melakukan impor relatif"
mononoke
202

Anda dapat menggunakan import components.coresecara langsung jika Anda menambahkan direktori saat ini ke sys.path:

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
ihm
sumber
35
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))ini juga akan bekerja
ajay
26
from os import syssepertinya curang :)
domba terbang
3
@Piotr: Ini mungkin dianggap lebih baik karena sedikit menunjukkan lebih jelas apa yang sedang ditambahkan ke sys.path- induk dari direktori file saat ini.
martineau
8
@flyingsheep: Setuju, saya hanya menggunakan biasa import sys, os.path as path.
martineau
10
FYI, untuk menggunakan ini dalam notebook ipython, saya diadaptasi jawaban ini ke: import os; os.sys.path.append(os.path.dirname(os.path.abspath('.'))). Kemudian langsung import components.corebekerja untuk saya, mengimpor dari direktori induk notebook seperti yang diinginkan.
Balap Kecebong
195

Itu tergantung pada bagaimana Anda ingin meluncurkan skrip Anda.

Jika Anda ingin meluncurkan UnitTest Anda dari baris perintah dengan cara klasik, yaitu:

python tests/core_test.py

Kemudian, karena dalam kasus ini 'komponen' dan 'tes' adalah folder saudara, Anda dapat mengimpor modul relatif baik menggunakan sisipan atau metode append dari modul sys.path . Sesuatu seperti:

import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents

Jika tidak, Anda dapat meluncurkan skrip Anda dengan argumen '-m' (perhatikan bahwa dalam kasus ini, kita berbicara tentang sebuah paket, dan dengan demikian Anda tidak boleh memberikan ekstensi '.py' ), yaitu:

python -m pkg.tests.core_test

Dalam kasus seperti itu, Anda cukup menggunakan impor relatif seperti yang Anda lakukan:

from ..components.core import GameLoopEvents

Anda akhirnya dapat mencampur dua pendekatan, sehingga skrip Anda akan berfungsi tidak peduli bagaimana namanya. Sebagai contoh:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        from components.core import GameLoopEvents
    else:
        from ..components.core import GameLoopEvents
Paolo Rovelli
sumber
3
apa yang harus saya lakukan jika saya mencoba menggunakan pdb untuk debugging? karena Anda gunakan python -m pdb myscript.pyuntuk meluncurkan sesi debugging.
danny
1
@dannynjust - Itu pertanyaan yang bagus karena Anda tidak dapat memiliki 2 modul utama. Secara umum ketika debugging, saya lebih suka masuk ke debugger secara manual pada titik pertama di mana saya ingin memulai debugging. Anda dapat melakukannya dengan memasukkan import pdb; pdb.set_trace()kode ke (inline).
mgilson
3
Apakah lebih baik menggunakan insertdaripada append? Yaitu,sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SparkAndShine
2
Menggunakan sisipan lebih cocok untuk semantik impor relatif, tempat nama paket lokal lebih diutamakan daripada paket yang diinstal. Khusus untuk pengujian, Anda biasanya ingin menguji versi lokal, bukan versi yang diinstal (kecuali infrastruktur pengujian Anda menginstal kode yang sedang diuji, dalam hal ini impor relatif tidak diperlukan dan Anda tidak akan memiliki masalah ini).
Alex Dupuy
1
Anda juga harus menyebutkan bahwa Anda tidak bisa berada di direktori yang berisi core_test ketika Anda menjalankan sebagai modul (itu akan terlalu mudah)
Joseph Garvin
25

Di core_test.py, lakukan hal berikut:

import sys
sys.path.append('../components')
from core import GameLoopEvents
Allan Mwesigwa
sumber
10

Jika use case Anda adalah untuk menjalankan tes, dan tampaknya benar, maka Anda dapat melakukan hal berikut. Alih-alih menjalankan skrip pengujian Anda seperti python core_test.pymenggunakan kerangka pengujian seperti pytest. Kemudian pada baris perintah Anda bisa masuk

$$ py.test

Itu akan menjalankan tes di direktori Anda. Ini mengatasi masalah __name__keberadaan __main__yang ditunjukkan oleh @BrenBarn. Selanjutnya, masukkan __init__.pyfile kosong ke direktori tes Anda, ini akan membuat bagian direktori tes paket Anda. Maka Anda akan dapat melakukannya

from ..components.core import GameLoopEvents

Namun, jika Anda menjalankan skrip pengujian sebagai program utama, maka semuanya akan gagal lagi. Jadi gunakan saja test runner. Mungkin ini juga bekerja dengan pelari tes lain seperti noseteststetapi saya belum memeriksanya. Semoga ini membantu.

deepak
sumber
9

Perbaikan cepat saya adalah menambahkan direktori ke path:

import sys
sys.path.insert(0, '../components/')
v4gil
sumber
6
Pendekatan Anda tidak akan berfungsi dalam semua kasus karena bagian '../' diselesaikan dari direktori tempat Anda menjalankan skrip Anda (core_test.py). Dengan pendekatan Anda, Anda dipaksa untuk cd ke 'tes' sebelum menjalankan scritp core_test.py.
xyman
7

Masalahnya adalah dengan metode pengujian Anda,

Anda mencoba python core_test.py

maka Anda akan mendapatkan kesalahan ini ValueError: Upaya impor relatif non-paket

Alasan: Anda menguji kemasan Anda dari sumber non-paket.

jadi uji modul Anda dari sumber paket.

jika ini adalah struktur proyek Anda,

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

cd pkg

python -m tests.core_test # dont use .py

atau dari luar pkg /

python -m pkg.tests.core_test

tunggal .jika Anda ingin mengimpor dari folder di direktori yang sama. untuk setiap langkah kembali tambahkan satu lagi.

hi/
  hello.py
how.py

di how.py

from .hi import hello

memetikan jika Anda ingin mengimpor bagaimana dari hello.py

from .. import how
Mohideen bin Mohammed
sumber
1
Jawaban yang lebih baik daripada yang diterima
GabrielBB
Pada contoh from .. import how, bagaimana Anda mengimpor kelas / metode tertentu dari file 'how'. ketika saya melakukan hal yang sama dengan from ..how import fooitu saya mendapatkan "percobaan impor relatif di luar paket tingkat atas"
James Hulse
3

Utas lama. Saya menemukan bahwa menambahkan __all__= ['submodule', ...]ke __init__.py berkas dan kemudian menggunakan from <CURRENT_MODULE> import *target bekerja dengan baik.

Laurent
sumber
3

Anda dapat menggunakan from pkg.components.core import GameLoopEvents, misalnya saya menggunakan pycharm, di bawah ini adalah gambar struktur proyek saya, saya hanya mengimpor dari paket root, lalu berfungsi:

masukkan deskripsi gambar di sini

Jayhello
sumber
3
Ini tidak berhasil untuk saya. Apakah Anda harus mengatur jalur di konfigurasi Anda?
Mohammad Mahjoub
3

Seperti yang dikatakan Paolo , kami memiliki 2 metode doa:

1) python -m tests.core_test
2) python tests/core_test.py

Satu perbedaan di antara mereka adalah string sys.path [0]. Karena penafsiran akan mencari sys.path ketika melakukan impor , kita dapat melakukannya dengan tests/core_test.py:

if __name__ == '__main__':
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from components import core
    <other stuff>

Dan lebih banyak lagi setelah ini, kita dapat menjalankan core_test.py dengan metode lain:

cd tests
python core_test.py
python -m core_test
...

Catatan, py36 hanya diuji.

zhengcao
sumber
3

Pendekatan ini bekerja untuk saya dan kurang berantakan daripada beberapa solusi:

try:
  from ..components.core import GameLoopEvents
except ValueError:
  from components.core import GameLoopEvents

Direktori induk ada di PYTHONPATH saya, dan ada __init__.pyfile di direktori induk dan direktori ini.

Hal di atas selalu bekerja di python 2, tetapi python 3 kadang-kadang menekan ImportError atau ModuleNotFoundError (yang terakhir adalah baru dalam python 3.6 dan subkelas ImportError), sehingga tweak berikut bekerja untuk saya di kedua python 2 dan 3:

try:
  from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
  from components.core import GameLoopEvents
Rick Graves
sumber
2

Coba ini

import components
from components import *
Vaishnavi Bala
sumber
1

Jika seseorang mencari solusi, saya menemukan satu. Inilah sedikit konteksnya. Saya ingin menguji salah satu metode yang saya miliki di file. Ketika saya menjalankannya dari dalam

if __name__ == "__main__":

selalu mengeluhkan impor relatif. Saya mencoba menerapkan solusi di atas, tetapi gagal berfungsi, karena ada banyak file bersarang, masing-masing dengan beberapa impor.

Inilah yang saya lakukan. Saya baru saja membuat peluncur, program eksternal yang akan mengimpor metode yang diperlukan dan memanggil mereka. Padahal, bukan solusi yang bagus, itu berhasil.

HappyWaters
sumber
0

Inilah satu cara yang akan membuat marah semua orang tetapi bekerja dengan cukup baik. Dalam tes dijalankan:

ln -s ../components components

Kemudian hanya mengimpor komponen seperti biasa.

SteveCalifornia
sumber
0

Ini sangat membingungkan, dan jika Anda menggunakan IDE seperti pycharm, ini sedikit membingungkan. Apa yang berhasil untuk saya: 1. Buat pengaturan proyek pycharm (jika Anda menjalankan python dari VE atau dari direktori python) 2. Tidak ada yang salah dengan cara Anda mendefinisikan. kadang-kadang berfungsi dengan dari folder1.file1 kelas impor

jika tidak bekerja, gunakan import folder1.file1 3. Variabel lingkungan Anda harus disebutkan dengan benar dalam sistem atau berikan dalam argumen baris perintah Anda.

SANTI SANTOSH MAHAPATRA
sumber
-2

Karena kode Anda berisi if __name__ == "__main__", yang tidak diimpor sebagai paket, Anda sebaiknya menggunakan sys.path.append()untuk menyelesaikan masalah.

rosefun
sumber
Saya rasa tidak ada if __name__ == "__main__"dalam file Anda membuat perbedaan untuk apa pun yang terkait dengan mengimpor.
user48956