Saya sudah mencoba membaca pertanyaan tentang impor saudara kandung dan bahkan dokumentasi paket , tetapi saya belum menemukan jawabannya.
Dengan struktur sebagai berikut:
├── LICENSE.md
├── README.md
├── api
│ ├── __init__.py
│ ├── api.py
│ └── api_key.py
├── examples
│ ├── __init__.py
│ ├── example_one.py
│ └── example_two.py
└── tests
│ ├── __init__.py
│ └── test_one.py
Bagaimana skrip dalam direktori examples
dan tests
impor dari
api
modul dan dijalankan dari commandline?
Juga, saya ingin menghindari sys.path.insert
peretasan yang buruk untuk setiap file. Tentunya ini bisa dilakukan dengan Python, kan?
python
packages
python-import
siblings
zachwill
sumber
sumber
sys.path
peretasan dan membaca satu-satunya solusi aktual yang telah diposting sejauh ini (setelah 7 tahun!).Jawaban:
Tujuh tahun kemudian
Karena saya menulis jawaban di bawah ini, memodifikasi
sys.path
masih merupakan trik cepat dan kotor yang berfungsi dengan baik untuk skrip pribadi, tetapi ada beberapa peningkatansetup.cfg
untuk menyimpan metadata)-m
flag dan menjalankannya sebagai sebuah paket juga berfungsi (tetapi akan menjadi sedikit canggung jika Anda ingin mengubah direktori kerja Anda menjadi sebuah paket yang dapat diinstal).sys.path
peretasan untuk AndaJadi itu sangat tergantung pada apa yang ingin Anda lakukan. Namun, dalam kasus Anda, karena tampaknya tujuan Anda adalah membuat paket yang tepat, menginstal melalui
pip -e
mungkin merupakan taruhan terbaik Anda, bahkan jika itu belum sempurna.Jawaban lama
Seperti yang sudah dinyatakan di tempat lain, kebenarannya adalah Anda harus melakukan peretasan jelek untuk mengizinkan impor dari modul saudara kandung atau paket orang tua dari sebuah
__main__
modul. Masalahnya dirinci dalam PEP 366 . PEP 3122 berusaha menangani impor dengan cara yang lebih rasional tetapi Guido telah menolaknya(di sini )
Padahal, saya menggunakan pola ini secara teratur dengan
Ini
path[0]
adalah folder induk skrip Anda yang sedang berjalan dandir(path[0])
folder tingkat atas Anda.Saya masih belum dapat menggunakan impor relatif dengan ini, tetapi itu memang memungkinkan impor absolut dari tingkat atas (dalam
api
folder induk contoh Anda ).sumber
-m
formulir atau jika Anda menginstal paket (pip dan virtualenv membuatnya mudah)__package__ = "examples"
untuk saya. Mengapa Anda menggunakan itu? 2. Dalam situasi apa__name__ == "__main__"
tetapi__package__
tidakNone
?__packages__
membantu jika Anda ingin jalur absolut sepertiexamples.api
untuk bekerja iirc (tapi sudah lama sejak saya terakhir melakukan itu) dan memeriksa bahwa paket tersebut tidak ada.Bosan dengan hack sys.path?
Ada banyak
sys.path.append
-hack yang tersedia, tetapi saya menemukan cara alternatif untuk memecahkan masalah di tangan.Ringkasan
packaged_stuff
)setup.py
skrip tempat Anda menggunakan setuptools.setup () .pip install -e <myproject_folder>
from packaged_stuff.modulename import function_name
Mempersiapkan
Titik awal adalah struktur file yang Anda berikan, dibungkus dengan folder yang disebut
myproject
.Saya akan memanggil
.
folder root, dan dalam contoh saya ini terletak diC:\tmp\test_imports\
.api.py
Sebagai uji coba, mari gunakan yang berikut ini ./api/api.py
test_one.py
Coba jalankan test_one:
Juga mencoba impor relatif tidak akan berfungsi:
Menggunakan
from ..api.api import function_from_api
akan menghasilkanLangkah
Isi untuk
setup.py
akan *Jika Anda terbiasa dengan lingkungan virtual, aktifkan satu, dan lewati ke langkah berikutnya. Penggunaan lingkungan virtual tidak mutlak diperlukan, tetapi mereka benar - benar akan membantu Anda dalam jangka panjang (ketika Anda memiliki lebih dari 1 proyek yang sedang berlangsung ..). Langkah paling dasar adalah (dijalankan di folder root)
python -m venv venv
source ./venv/bin/activate
(Linux, macOS) atau./venv/Scripts/activate
(Win)Untuk mempelajari lebih lanjut tentang ini, cukup Google keluar "python virtual env tutorial" atau serupa. Anda mungkin tidak pernah membutuhkan perintah selain membuat, mengaktifkan, dan menonaktifkan.
Setelah Anda membuat dan mengaktifkan lingkungan virtual, konsol Anda harus memberi nama lingkungan virtual dalam tanda kurung
dan pohon folder Anda akan terlihat seperti ini **
Instal paket tingkat atas Anda
myproject
menggunakanpip
. Caranya adalah dengan menggunakan-e
bendera saat melakukan instalasi. Dengan cara ini diinstal dalam keadaan yang dapat diedit, dan semua pengeditan yang dilakukan pada file .py akan secara otomatis disertakan dalam paket yang diinstal.Di direktori root, jalankan
pip install -e .
(perhatikan titik, singkatan dari "direktori saat ini")Anda juga dapat melihat bahwa itu diinstal dengan menggunakan
pip freeze
myproject.
ke impor AndaPerhatikan bahwa Anda harus menambahkan
myproject.
hanya ke impor yang tidak akan berfungsi sebaliknya. Impor yang berfungsi tanpasetup.py
&pip install
akan berfungsi dengan baik. Lihat contoh di bawah ini.Uji solusinya
Sekarang, mari kita coba solusinya menggunakan yang
api.py
didefinisikan di atas, dan yangtest_one.py
didefinisikan di bawah ini.test_one.py
menjalankan tes
* Lihat dokumen setuptools untuk lebih banyak contoh setup.py verbose.
** Pada kenyataannya, Anda dapat menempatkan lingkungan virtual Anda di mana saja di hard disk Anda.
sumber
-e git+https://[email protected]/folder/myproject.git@f65466656XXXXX#egg=myproject
Ada ide tentang bagaimana menyelesaikan?ModuleNotFoundError
? Saya telah menginstal 'myproject' ke dalam virtualenv mengikuti langkah-langkah ini, dan ketika saya memasukkan sesi yang ditafsirkan dan menjalankanimport myproject
saya dapatkanModuleNotFoundError: No module named 'myproject'
?pip list installed | grep myproject
menunjukkan bahwa itu ada di sana, direktori sudah benar, dan keduanya verisonpip
danpython
diverifikasi benar.pip list
menunjukkan paket, sambilpip freeze
menunjukkan nama-nama aneh jika diinstal dengan flag -eBerikut adalah alternatif lain yang saya masukkan di atas file Python di
tests
folder:sumber
..
sini relatif terhadap direktori Anda mengeksekusi --- bukan direktori yang berisi file tes / contoh. Saya mengeksekusi dari direktori proyek, dan saya membutuhkannya./
. Semoga ini bisa membantu orang lain.sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
@JoshuaDetwilerAnda tidak perlu dan tidak boleh meretas
sys.path
kecuali jika perlu dan dalam hal ini tidak. Menggunakan:Jalankan dari direktori proyek:
python -m tests.test_one
.Anda mungkin harus pindah
tests
(jika mereka belum pernah api) di dalamapi
dan menjalankanpython -m api.test
untuk menjalankan semua tes (dengan asumsi ada__main__.py
) ataupython -m api.test.test_one
untuk menjalankantest_one
sebagai gantinya.Anda juga dapat menghapus
__init__.py
dariexamples
(ini bukan paket Python) dan menjalankan contoh-contoh dalam virtualenv di manaapi
diinstal misalnya,pip install -e .
dalam virtualenv akan menginstalapi
paket inplace jika Anda memiliki yang tepatsetup.py
.sumber
python -m api.test.test_one
dari mana saja ketika virtualenv diaktifkan. Jika Anda tidak dapat mengonfigurasi PyCharm untuk menjalankan tes Anda, cobalah untuk mengajukan pertanyaan Stack Overflow baru (jika Anda tidak dapat menemukan pertanyaan yang ada tentang topik ini).Saya belum memiliki pemahaman tentang Pythonology yang diperlukan untuk melihat cara berbagi kode yang dimaksud di antara proyek-proyek yang tidak terkait tanpa saudara / impor relatif hack. Sampai hari itu, ini solusi saya. Untuk
examples
atautests
mengimpor barang dari..\api
, akan terlihat seperti:sumber
Untuk impor paket saudara, Anda dapat menggunakan sisipan atau metode append dari modul [sys.path] [2] :
Ini akan berfungsi jika Anda meluncurkan skrip Anda sebagai berikut:
Di sisi lain, Anda juga dapat menggunakan impor relatif:
Dalam hal ini Anda harus meluncurkan skrip Anda dengan argumen '-m' (perhatikan bahwa, dalam hal ini, Anda tidak boleh memberikan ekstensi '.py' ):
Tentu saja, Anda dapat mencampur kedua pendekatan tersebut, sehingga skrip Anda akan berfungsi tidak peduli bagaimana namanya:
sumber
__file__
global jadi saya harus menggunakan yang berikut:sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))
Tapi itu berfungsi di direktori mana pun sekarangTLDR
Metode ini tidak memerlukan setuptools, hack path, argumen baris perintah tambahan, atau menentukan tingkat atas paket di setiap file proyek Anda.
Buat saja skrip di direktori induk apa pun yang Anda panggil untuk menjadi milik Anda
__main__
dan jalankan semuanya dari sana. Untuk penjelasan lebih lanjut lanjutkan membaca.Penjelasan
Ini dapat dilakukan tanpa meretas jalur baru bersama, argumen baris perintah tambahan, atau menambahkan kode ke setiap program Anda untuk mengenali saudara kandungnya.
Alasan ini gagal karena saya percaya disebutkan sebelumnya adalah program yang dipanggil telah
__name__
ditetapkan sebagai__main__
. Ketika ini terjadi, skrip yang dipanggil menerima dirinya berada di tingkat atas paket dan menolak untuk mengenali skrip dalam direktori saudara.Namun, segala sesuatu di bawah tingkat atas direktori masih akan mengenali APA SAJA LAIN di bawah tingkat atas. Ini berarti satu- satunya hal yang harus Anda lakukan untuk mendapatkan file dalam direktori saudara untuk mengenali / memanfaatkan satu sama lain adalah memanggil mereka dari skrip di direktori induknya.
Bukti Konsep Dalam sebuah dir dengan struktur berikut:
Main.py
berisi kode berikut:sib1 / call.py berisi:
dan sib2 / callsib.py berisi:
Jika Anda mereproduksi contoh ini, Anda akan melihat bahwa panggilan
Main.py
akan menghasilkan "Got Called" dicetak sebagaimana didefinisikansib2/callsib.py
meskipunsib2/callsib.py
dipanggil melaluisib1/call.py
. Namun jika seseorang langsung meneleponsib1/call.py
(setelah melakukan perubahan yang sesuai dengan impor) itu melempar pengecualian. Meskipun itu berfungsi ketika dipanggil oleh skrip di direktori induknya, itu tidak akan berfungsi jika ia percaya dirinya berada di tingkat atas paket.sumber
Saya membuat contoh proyek untuk menunjukkan bagaimana saya menangani ini, yang memang merupakan hack sys.path lain seperti yang ditunjukkan di atas. Contoh Impor Sirip Python , yang mengandalkan:
if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())
Ini tampaknya cukup efektif selama direktori kerja Anda tetap pada akar proyek Python. Jika ada yang menyebarkan ini dalam lingkungan produksi nyata, akan lebih baik untuk mendengar jika itu bekerja di sana juga.
sumber
Anda perlu melihat untuk melihat bagaimana pernyataan impor ditulis dalam kode terkait. Jika
examples/example_one.py
menggunakan pernyataan impor berikut:... maka ia mengharapkan direktori root proyek berada di jalur sistem.
Cara termudah untuk mendukung ini tanpa ada peretasan (seperti yang Anda katakan) adalah dengan menjalankan contoh dari direktori tingkat atas, seperti ini:
sumber
$ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.api
. Saya juga mendapatkan yang samaimport api.api
.Untuk berjaga-jaga jika seseorang menggunakan Pydev di Eclipse berakhir di sini: Anda dapat menambahkan jalur induk saudara (dan dengan demikian orangtua modul panggilan) sebagai folder pustaka eksternal menggunakan Project-> Properties dan mengatur Pustaka Eksternal di bawah menu kiri Pydev-PYTHONPATH . Kemudian Anda dapat mengimpor dari saudara Anda, mis
from sibling import some_class
.sumber
Pertama, Anda harus menghindari memiliki file dengan nama yang sama dengan modul itu sendiri. Ini dapat merusak impor lainnya.
Ketika Anda mengimpor file, pertama penerjemah memeriksa direktori saat ini dan kemudian mencari direktori global.
Di dalam
examples
atautests
Anda dapat menelepon:sumber
Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package
__init__.py
file ke direktori tingkat atas. Kalau tidak, Python tidak bisa memperlakukannya sebagai modul__name__
adalah__main__
bukanpackage.module
, Python tidak bisa melihat paket induknya, sehingga.
poin apa-apa.