Bagaimana cara melakukan impor relatif dengan Python?

527

Bayangkan struktur direktori ini:

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

Saya sedang coding mod1, dan saya perlu mengimpor sesuatu mod2. Bagaimana saya harus melakukannya?

Saya mencoba from ..sub2 import mod2tetapi saya mendapatkan "Impor relatif non-paket".

Saya mencari-cari di sekitar tetapi hanya menemukan sys.pathhacks "manipulasi". Apakah tidak ada cara yang bersih?


Sunting: semua milik saya __init__.pysaat ini kosong

Edit2: Saya mencoba untuk melakukan hal ini karena Sub2 berisi kelas yang dibagi di sub paket ( sub1, subX, dll).

Sunting3: Perilaku yang saya cari adalah sama seperti yang dijelaskan dalam PEP 366 (terima kasih John B)

Joril
sumber
8
Saya sarankan memperbarui pertanyaan Anda untuk membuatnya lebih jelas bahwa Anda menggambarkan masalah yang dibahas dalam PEP 366.
John B
2
Ini penjelasan panjang lebar tetapi periksa di sini: stackoverflow.com/a/10713254/1267156 Saya menjawab pertanyaan yang sangat mirip. Saya punya masalah yang sama sampai tadi malam.
Sevvy325
3
Bagi mereka yang ingin memuat modul yang terletak di jalur sewenang-wenang, lihat ini: stackoverflow.com/questions/67631/…
Evgeni Sergeev
2
Pada catatan terkait, Python 3 akan mengubah penanganan default impor menjadi absolut secara default; impor relatif harus ditentukan secara eksplisit.
Ross

Jawaban:

337

Semua orang tampaknya ingin memberi tahu Anda apa yang harus Anda lakukan daripada hanya menjawab pertanyaan.

Masalahnya adalah Anda menjalankan modul sebagai '__main__' dengan melewatkan mod1.py sebagai argumen ke penerjemah.

Dari PEP 328 :

Impor relatif menggunakan atribut __name__ modul untuk menentukan posisi modul dalam hierarki paket. Jika nama modul tidak mengandung informasi paket apa pun (mis. Diatur ke '__main__') maka impor relatif diselesaikan seolah-olah modul tersebut adalah modul tingkat atas, terlepas dari di mana letak sebenarnya modul pada sistem file.

Dalam Python 2.6, mereka menambahkan kemampuan untuk referensi modul relatif ke modul utama. PEP 366 menjelaskan perubahan tersebut.

Pembaruan : Menurut Nick Coghlan, alternatif yang disarankan adalah menjalankan modul di dalam paket menggunakan saklar -m.

John B
sumber
2
Jawabannya di sini melibatkan bermain-main dengan sys.path di setiap titik masuk ke program Anda. Saya kira itu satu-satunya cara untuk melakukannya.
Nick Retallack
76
Alternatif yang disarankan adalah menjalankan modul di dalam paket menggunakan -msakelar, alih-alih dengan menentukan nama file secara langsung.
ncoghlan
127
Saya tidak mengerti: di mana jawabannya di sini? Bagaimana seseorang dapat mengimpor modul dalam struktur direktori seperti itu?
Tom
27
@ Tom: Dalam hal ini, mod1 akan from sub2 import mod2. Kemudian, untuk menjalankan mod1, dari dalam aplikasi, lakukan python -m sub1.mod1.
Xiong Chiamiov
11
@XiongChiamiov: apakah ini berarti Anda tidak dapat melakukannya jika python Anda disematkan dalam aplikasi, sehingga Anda tidak memiliki akses ke sakelar baris perintah python?
LarsH
130

Inilah solusi yang berfungsi untuk saya:

Saya melakukan impor relatif sebagai from ..sub2 import mod2 dan kemudian, jika saya ingin menjalankan mod1.pymaka saya pergi ke direktori induk appdan menjalankan modul menggunakan saklar python -m sebagai python -m app.sub1.mod1.

Alasan sebenarnya mengapa masalah ini terjadi pada impor relatif, adalah impor relatif bekerja dengan mengambil __name__properti modul. Jika modul sedang dijalankan secara langsung, maka __name__diatur ke __main__dan tidak mengandung informasi tentang struktur paket. Dan, itu sebabnya python mengeluh tentang relative import in non-packagekesalahan tersebut.

Jadi, dengan menggunakan sakelar -m Anda memberikan informasi struktur paket ke python, yang dengannya ia dapat menyelesaikan impor relatif dengan sukses.

Saya telah mengalami masalah ini berkali-kali saat melakukan impor relatif. Dan, setelah membaca semua jawaban sebelumnya, saya masih tidak dapat menemukan cara untuk menyelesaikannya, dengan cara yang bersih, tanpa perlu memasukkan kode boilerplate di semua file. (Meskipun beberapa komentar sangat membantu, terima kasih kepada @ncoghlan dan @XiongChiamiov)

Semoga ini membantu seseorang yang berjuang dengan masalah impor relatif, karena melalui PEP benar-benar tidak menyenangkan.

Pankaj
sumber
9
Jawaban terbaik IMHO: tidak hanya menjelaskan mengapa OP memiliki masalah, tetapi juga menemukan cara untuk menyelesaikannya tanpa mengubah cara modulnya melakukan impor . Lagipula, impor relatif OP baik-baik saja. Pelakunya adalah kurangnya akses ke paket luar ketika langsung dijalankan sebagai skrip, sesuatu -mdirancang untuk dipecahkan.
MestreLion
26
Perhatikan juga: jawaban ini 5 tahun setelah pertanyaan. Fitur-fitur ini tidak tersedia pada saat itu.
JeremyKun
1
Jika Anda ingin mengimpor modul dari direktori yang sama, Anda dapat melakukannya 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. Anda lari python main.py.
  2. main.py tidak: import app.package_a.module_a
  3. module_a.py tidak import app.package_b.module_b

Atau 2 atau 3 dapat menggunakan: from app.package_a import module_a

Itu akan bekerja selama Anda miliki appdi PYTHONPATH Anda. main.pybisa di mana saja kalau begitu.

Jadi, Anda menulis setup.pyuntuk menyalin (menginstal) seluruh paket aplikasi dan subpackages ke folder python sistem target, dan main.pyuntuk menargetkan folder skrip sistem.

nosklo
sumber
3
Jawaban yang sangat bagus. Apakah ada cara untuk mengimpor cara itu tanpa menginstal paket di PYTHONPATH?
auraham
4
Bacaan tambahan yang disarankan: blog.habnab.it/blog/2013/07/21/python-packages-and-you
nosklo
6
maka, suatu hari, perlu mengubah nama aplikasi menjadi test_app. apa yang akan terjadi? Anda perlu mengubah semua kode sumber, impor app.package_b.module_b -> test_app.package_b.module_b. ini benar-benar praktik buruk ... Dan kita harus mencoba menggunakan impor relatif dalam paket.
Spybdai
49

"Guido memandang menjalankan skrip dalam sebuah paket sebagai anti-pola" (ditolak PEP-3122 )

Saya telah menghabiskan banyak waktu untuk mencari solusi, membaca posting terkait di sini di Stack Overflow dan berkata pada diri sendiri "pasti ada cara yang lebih baik!". Sepertinya tidak ada.

lesnik
sumber
10
Catatan: Sudah disebutkan pep-366 (dibuat sekitar waktu yang sama dengan pep-3122 ) memberikan kemampuan yang sama tetapi menggunakan implementasi kompatibel-mundur yang berbeda yaitu, jika Anda ingin menjalankan modul di dalam paket sebagai skrip dan menggunakan impor relatif eksplisit di dalamnya maka Anda dapat menjalankannya menggunakan -msakelar: python -m app.sub1.mod1atau memanggil app.sub1.mod1.main()dari skrip tingkat atas (mis., dihasilkan dari entry_points setuptools yang ditentukan dalam setup.py).
jfs
+1 untuk menggunakan setuptools dan titik masuk - ini adalah cara yang tepat untuk mengatur skrip yang akan dijalankan dari luar, di lokasi yang terdefinisi dengan baik, sebagai lawan dari peretasan tanpa henti PYTHONPATH
RecencyEffect
38

Ini diselesaikan 100%:

  • aplikasi/
    • main.py
  • pengaturan /
    • local_setings.py

Impor pengaturan / local_setting.py di app / main.py:

main.py:

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


try:
    from local_settings import *
except ImportError:
    print('No Import')
Роман Арсеньев
sumber
2
Terima kasih! semua ppl memaksa saya untuk menjalankan skrip saya berbeda daripada memberitahu saya bagaimana menyelesaikannya dalam skrip. Tetapi saya harus mengubah kode untuk digunakan sys.path.insert(0, "../settings")dan kemudianfrom 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

Saya menggunakan cuplikan ini untuk mengimpor modul dari jalur, semoga bisa membantu

iElectric
sumber
2
Saya menggunakan cuplikan ini, dikombinasikan dengan modul imp (seperti yang dijelaskan di sini [1]) sangat efektif. [1]: stackoverflow.com/questions/1096216/…
Xiong Chiamiov
7
Mungkin, sys.path.append (path) harus diganti dengan sys.path.insert (0, path), dan sys.path [-1] harus diganti dengan sys.path [0]. Kalau tidak, fungsi akan mengimpor modul yang salah, jika sudah ada modul dengan nama yang sama di jalur pencarian. Misalnya, jika ada "some.py" dalam direktori saat ini, import_path ("/ import / some.py") akan mengimpor file yang salah.
Alex Che
Saya setuju! Terkadang impor relatif lain akan didahulukan. Gunakan sys.path.insert
iElectric
Bagaimana Anda mereplikasi perilaku dari x import y (atau *)?
levesque
Tidak jelas, harap tentukan penggunaan penuh skrip ini untuk menyelesaikan masalah OP.
mrgloom
21

penjelasan nosklo'sjawaban dengan contoh

catatan: semua __init__.pyfile kosong.

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

app / package_a / fun_a.py

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

app / 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()

jika Anda menjalankannya $ python main.pykembali:

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 tidak: from app.package_b import fun_b
  • fun_b.py tidak from app.package_a.fun_a import print_a

jadi file dalam folder package_bmenggunakan file dalam folder package_a, yang adalah apa yang Anda inginkan. Baik??

suhailvs
sumber
12

Sayangnya ini adalah hack sys.path, tetapi ia bekerja dengan cukup baik.

Saya mengalami masalah ini dengan lapisan lain: Saya sudah memiliki modul dengan nama yang ditentukan, tetapi itu adalah modul yang salah.

apa yang ingin saya lakukan adalah sebagai berikut (modul tempat saya bekerja adalah module3):

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


import mymodule.mymodule1.mymodule1_1  

Perhatikan bahwa saya telah menginstal mymodule, tetapi dalam instalasi saya, saya tidak memiliki "mymodule1"

dan saya akan mendapatkan ImportError karena mencoba mengimpor dari modul yang diinstal.

Saya mencoba melakukan sys.path.append, dan itu tidak berhasil. Apa yang berhasil adalah sys.path.insert

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

Semacam hack, tapi semuanya berhasil! Jadi perlu diingat, jika Anda ingin keputusan Anda untuk menimpa jalur lain maka Anda perlu menggunakan sys.path.insert (0, pathname) untuk membuatnya berfungsi! Ini adalah titik yang sangat menyebalkan bagi saya, banyak orang mengatakan untuk menggunakan fungsi "append" untuk sys.path, tetapi itu tidak berfungsi jika Anda sudah memiliki modul yang ditentukan (saya menemukan perilaku yang sangat aneh)

Garrett Berg
sumber
sys.path.append('../')berfungsi dengan baik untuk saya (Python 3.5.2)
Nister
Saya pikir ini baik-baik saja karena ini meretas hack ke executable dan tidak mempengaruhi modul lain yang mungkin tergantung pada paket Anda.
Tom Russell
10

Biarkan saya letakkan ini di sini untuk referensi saya sendiri. Saya tahu bahwa itu bukan kode Python yang baik, tetapi saya membutuhkan skrip untuk proyek yang sedang saya kerjakan dan saya ingin meletakkan skrip dalam scriptsdirektori.

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

Seperti yang dikatakan @EvgeniSergeev dalam komentar di OP, Anda dapat mengimpor kode dari .pyfile di lokasi acak dengan:

import imp

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

Ini diambil dari jawaban SO ini .

LondonRob
sumber
2

Dari Python doc ,

Di Python 2.5, Anda bisa mengubah perilaku impor menjadi impor absolut menggunakan from __future__ import absolute_importarahan. Perilaku impor absolut ini akan menjadi default di versi yang akan datang (mungkin Python 2.7). Setelah impor absolut adalah default, import stringakan selalu menemukan versi perpustakaan standar. Disarankan agar pengguna mulai menggunakan impor absolut sebanyak mungkin, jadi sebaiknya mulai menulis from pkg import stringdalam kode Anda

jung rhew
sumber
1

Saya menemukan lebih mudah untuk mengatur variabel lingkungan "PYTHONPATH" ke folder teratas:

bash$ export PYTHONPATH=/PATH/TO/APP

kemudian:

import sub1.func1
#...more import

tentu saja, PYTHONPATH adalah "global", tetapi itu belum menimbulkan masalah bagi saya.

Andrew_1510
sumber
Ini pada dasarnya bagaimana virtualenvAnda mengelola laporan impor Anda.
byxor
1

Di atas apa yang dikatakan John B, sepertinya mengatur __package__variabel harus membantu, alih-alih mengubah __main__yang dapat mengacaukan hal-hal lain. Tetapi sejauh yang saya bisa uji, itu tidak sepenuhnya berfungsi sebagaimana mestinya.

Saya memiliki masalah yang sama dan PEP 328 atau 366 tidak menyelesaikan masalah sepenuhnya, karena keduanya, pada akhir hari, membutuhkan kepala paket untuk dimasukkan sys.path, sejauh yang saya bisa mengerti.

Saya juga harus menyebutkan bahwa saya tidak menemukan cara memformat string yang harus dimasukkan ke dalam variabel tersebut. Apakah itu "package_head.subfolder.module_name"atau apa?

Gabriel
sumber
0

Anda harus menambahkan jalur modul ke PYTHONPATH:

export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"
Giorgos Myrianthous
sumber
1
Ini kira-kira sama dengan memanipulasi sys.path, karena sys.pathakan diinisialisasi dariPYTHONPATH
Joril
@ Joril Itu benar tetapi sys.pathperlu di-hardcode dalam kode sumber berbeda dengan PYTHONPATHyang merupakan variabel lingkungan dan dapat diekspor.
Giorgos Myrianthous