melampaui kesalahan paket tingkat atas dalam impor relatif


317

Tampaknya sudah ada beberapa pertanyaan di sini tentang impor relatif dalam python 3, tetapi setelah melalui banyak dari mereka saya masih belum menemukan jawaban untuk masalah saya. jadi inilah pertanyaannya.

Saya memiliki paket yang ditunjukkan di bawah ini

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

dan saya memiliki satu baris di test.py:

from ..A import foo

sekarang, saya ada di folder package, dan saya jalankan

python -m test_A.test

Saya mendapat pesan

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

tetapi jika saya berada di folder induk package, misalnya, saya menjalankan:

cd ..
python -m package.test_A.test

semuanya baik-baik saja.

Sekarang pertanyaan saya adalah: ketika saya berada di folder package, dan saya menjalankan modul di dalam sub-paket test_A karena test_A.test, berdasarkan pemahaman saya, ..Anaik hanya satu tingkat, yang masih di dalam packagefolder, mengapa memberikan pesan yang mengatakan beyond top-level package. Apa sebenarnya alasan yang menyebabkan pesan kesalahan ini?


49
posting itu tidak menjelaskan kesalahan saya "di luar paket tingkat atas"
shelper

4
Saya punya pemikiran di sini, jadi ketika menjalankan test_A.test sebagai modul, '..' berjalan di atas test_A, yang sudah merupakan level tertinggi dari impor test_A.test, saya pikir level paket bukan level direktori, tetapi berapa banyak tingkat Anda mengimpor paket.
shelper

2
Saya berjanji Anda akan memahami segala sesuatu tentang impor relatif setelah menonton jawaban ini stackoverflow.com/a/14132912/8682868 .
pzjzeason

lihat ValueError: upaya impor relatif di luar paket tingkat atas untuk penjelasan terperinci tentang masalah ini.
napuzba

Apakah ada cara untuk menghindari impor relatif? Seperti cara PyDev di Eclipse melihat semua paket di dalam <PydevProject> / src?
Mushu909

Jawaban:


173

EDIT: Ada jawaban yang lebih baik / lebih koheren untuk pertanyaan ini dalam pertanyaan lain:


Mengapa itu tidak berhasil? Itu karena python tidak merekam dari mana sebuah paket diambil. Jadi ketika Anda melakukannya python -m test_A.test, itu pada dasarnya hanya membuang pengetahuan yang test_A.testsebenarnya disimpan package(yaitu packagetidak dianggap sebagai paket). Mencoba from ..A import foomencoba mengakses informasi yang tidak ada lagi (mis. Direktori saudara dari lokasi yang dimuat). Ini konseptual mirip dengan memungkinkan from ..os import pathdalam file di math. Ini akan menjadi buruk karena Anda ingin paketnya berbeda. Jika mereka perlu menggunakan sesuatu dari paket lain, maka mereka harus merujuknya secara global dengan from os import pathdan membiarkan python mencari tahu di mana itu dengan $PATHdan $PYTHONPATH.

Ketika Anda menggunakan python -m package.test_A.test, maka gunakan from ..A import fooresolves dengan baik karena itu melacak apa yang ada di dalam packagedan Anda hanya mengakses direktori anak dari lokasi yang dimuat.

Mengapa python tidak menganggap direktori kerja saat ini sebagai sebuah paket? TANPA PETUNJUK , tapi ampun itu akan berguna.


2
Saya telah mengedit jawaban saya untuk merujuk ke jawaban yang lebih baik untuk pertanyaan yang sama jumlahnya. Hanya ada solusi. Satu-satunya hal yang benar-benar saya lihat bekerja adalah apa yang telah dilakukan OP, yaitu menggunakan -mflag dan menjalankan dari direktori di atas.
Multihunter

1
Perlu dicatat bahwa jawaban ini , dari tautan yang diberikan oleh Multihunter, tidak melibatkan sys.pathperetasan, tetapi penggunaan setuptools , yang jauh lebih menarik menurut saya.
Angelo Cardellicchio

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

Coba ini. Bekerja untukku.


10
Umm ... bagaimana ini bekerja? Setiap file uji tunggal akan memiliki ini?
George Mauer

Masalahnya di sini adalah jika, misalnya, A/bar.pyada dan dalam diri foo.pyAnda from .bar import X.
user1834164

9
Saya harus menghapus .. dari "from ..A import ..." setelah menambahkan sys.path.append ("..")
Jake OPJ

2
Jika skrip dijalankan dari luar direktori yang ada, ini tidak akan berfungsi. Sebagai gantinya, Anda harus mengubah jawaban ini untuk menentukan jalur absolut dari skrip tersebut .
Manavalan Gajapathy

ini adalah pilihan terbaik, paling tidak rumit
Alex R

43

Asumsi:
Jika Anda berada di packagedirektori, Adan test_Amerupakan paket terpisah.

Kesimpulan:
..Aimpor hanya diperbolehkan dalam satu paket.

Catatan lebih lanjut:
Membuat impor relatif hanya tersedia dalam paket berguna jika Anda ingin memaksa agar paket dapat ditempatkan di jalur apa pun yang berada di sys.path.

EDIT:

Apa aku satu-satunya yang berpikir kalau ini gila !? Mengapa di dunia ini direktori kerja saat ini tidak dianggap sebagai paket? - Multihunter

Direktori kerja saat ini biasanya terletak di sys.path. Jadi, semua file yang ada dapat diimpor. Ini adalah perilaku sejak Python 2 ketika paket belum ada. Membuat direktori yang sedang berjalan paket akan memungkinkan impor modul sebagai "impor .A" dan sebagai "impor A" yang kemudian akan menjadi dua modul yang berbeda. Mungkin ini adalah ketidakkonsistenan untuk dipertimbangkan.


86
Apa aku satu-satunya yang berpikir kalau ini gila !? Mengapa di dunia ini direktori yang sedang berjalan tidak dianggap sebagai paket?
Multihunter

13
Tidak hanya itu gila, ini tidak membantu ... jadi bagaimana Anda menjalankan tes itu? Jelas hal yang ditanyakan OP dan mengapa saya yakin banyak orang di sini juga.
George Mauer

Direktori yang sedang berjalan biasanya terletak di sys.path. Jadi, semua file yang ada dapat diimpor. Ini adalah perilaku sejak Python 2 ketika paket belum ada. - jawaban yang diedit.
Pengguna

Saya tidak mengikuti inkonsistensi. Perilaku python -m package.test_A.testkelihatannya melakukan apa yang diinginkan, dan argumen saya adalah bahwa itu harus menjadi default. Jadi, dapatkah Anda memberi saya contoh ketidakkonsistenan ini?
Multihunter

Saya sebenarnya berpikir, apakah ada permintaan fitur untuk ini? Ini memang gila. Gaya C / C ++ #includeakan sangat berguna!
Nicholas Humphrey

29

Tidak ada solusi ini yang berfungsi untuk saya di 3.6, dengan struktur folder seperti:

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

Tujuan saya adalah mengimpor dari module1 ke module2. Yang akhirnya berhasil bagi saya adalah, anehnya:

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

Perhatikan titik tunggal yang bertentangan dengan solusi dua titik yang disebutkan sejauh ini.


Sunting: Berikut ini membantu mengklarifikasi bagi saya:

import os
print (os.getcwd())

Dalam kasus saya, direktori kerja (secara tidak terduga) adalah akar dari proyek.


2
itu bekerja secara lokal tetapi tidak bekerja pada contoh aws ec2, apakah itu masuk akal?
thebeancounter

Ini bekerja untuk saya juga - dalam kasus saya direktori kerja juga merupakan root proyek. Saya menggunakan jalan pintas lari dari editor pemrograman (TextMate)
JeremyDouglass

@thebeancounter Sama! Bekerja secara lokal di mac saya tetapi tidak bekerja pada EC2, maka saya menyadari bahwa saya menjalankan perintah di subdir pada EC2 dan menjalankannya di root secara lokal. Setelah saya jalankan dari root di EC2 itu berhasil.
Logan Yang

Ini juga bermanfaat bagi saya. Dari metode sys saya sekarang dapat memanggil paket tanpa perlu ".."
RamWill

sys.path.append(".")berfungsi karena Anda memanggilnya di direktori induk, perhatikan bahwa .selalu merupakan direktori tempat Anda menjalankan perintah python.
KevinZhou

13

from package.A import foo

Saya pikir itu lebih jelas daripada

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

4
itu lebih mudah dibaca tetapi masih perlu sys.path.append(".."). diuji pada python 3.6
MFA

Sama seperti jawaban yang lebih lama
nrofis

12

Seperti yang disarankan oleh jawaban paling populer, pada dasarnya itu karena Anda PYTHONPATHatau sys.pathtermasuk .tetapi bukan jalur Anda ke paket Anda. Dan impor relatif relatif terhadap direktori kerja Anda saat ini, bukan file di mana impor terjadi; anehnya

Anda bisa memperbaikinya dengan terlebih dahulu mengubah impor relatif Anda menjadi absolut dan kemudian memulainya dengan:

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

ATAU memaksa jalur python ketika dipanggil dengan cara ini, karena:

Dengan python -m test_A.testAnda mengeksekusi test_A/test.pydengan __name__ == '__main__'dan__file__ == '/absolute/path/to/test_A/test.py'

Itu berarti bahwa di dalam test.pyAnda dapat menggunakan importsemi-dilindungi absolut dalam kondisi kasus utama dan juga melakukan manipulasi jalur Python satu kali:

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

Sunting: 2020-05-08: Sepertinya situs web yang saya kutip tidak lagi dikendalikan oleh orang yang menulis saran, jadi saya menghapus tautan ke situs tersebut. Terima kasih telah memberi tahu saya baxx.


Jika seseorang masih berjuang sedikit setelah jawaban-jawaban hebat telah diberikan, saya menemukan saran di situs web yang tidak lagi tersedia.

Kutipan penting dari situs yang saya sebutkan:

"Hal yang sama dapat ditentukan secara pemrograman dengan cara ini:

impor sys

sys.path.append ('..')

Tentu saja kode di atas harus ditulis sebelum pernyataan impor lainnya .

Sudah cukup jelas bahwa harus seperti ini, memikirkannya setelah fakta. Saya mencoba menggunakan sys.path.append ('..') dalam pengujian saya, tetapi mengalami masalah yang diposting oleh OP. Dengan menambahkan impor dan definisi sys.path sebelum impor saya yang lain, saya bisa menyelesaikan masalah.


tautan yang Anda posting sudah mati.
baxx

Terima kasih telah memberi tahu saya. Tampaknya nama domain tidak lagi dikendalikan oleh orang yang sama. Saya telah menghapus tautannya.
Mierpo

5

jika Anda memiliki __init__.pydi folder atas, Anda dapat menginisialisasi impor seperti import file/path as aliaspada file init itu. Kemudian Anda dapat menggunakannya pada skrip yang lebih rendah sebagai:

import alias

0

Menurut pendapat saya yang sederhana, saya memahami pertanyaan ini dengan cara ini:

[KASUS 1] Saat Anda memulai impor seperti absolut

python -m test_A.test

atau

import test_A.test

atau

from test_A import test

Anda sebenarnya mengatur anchor impor menjadi test_A, dengan kata lain, paket tingkat atas adalah test_A. Jadi, ketika kami memiliki test.py lakukan from ..A import xxx, Anda melarikan diri dari jangkar, dan Python tidak mengizinkan ini.

[KASUS 2] Saat Anda melakukannya

python -m package.test_A.test

atau

from package.test_A import test

jangkar Anda menjadi package, jadi package/test_A/test.pymelakukan from ..A import xxxtidak lepas jangkar (masih di dalam packagefolder), dan Python dengan senang hati menerima ini.

Pendeknya:

  • Impor absolut mengubah jangkar saat ini (= mendefinisikan kembali apa itu paket tingkat atas);
  • Impor relatif tidak mengubah jangkar tetapi terbatas padanya.

Selain itu, kami dapat menggunakan nama modul yang memenuhi syarat (FQMN) untuk memeriksa masalah ini.

Periksa FQMN dalam setiap kasus:

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

Jadi, untuk CASE2, sebuah from .. import xxxakan menghasilkan modul baru dengan FQMN = package.xxx, yang dapat diterima.

Sedangkan untuk CASE1, ..dari dalam from .. import xxxakan melompat keluar dari simpul awal (jangkar) test_A, dan ini TIDAK diizinkan oleh Python.


2
Ini jauh lebih rumit dari yang seharusnya. Begitu banyak untuk Zen of Python.
AtilioA

0

Tidak yakin dengan python 2.x tetapi dalam python 3.6, dengan asumsi Anda mencoba menjalankan seluruh rangkaian, Anda hanya perlu menggunakan -t

-t, --top-level-direktori direktori Direktori tingkat atas proyek (default untuk memulai direktori)

Jadi, pada struktur suka

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

Misalnya seseorang dapat menggunakan:

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

Dan masih mengimpor my_module.my_classtanpa drama utama.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.