Penjelasan
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.
Pada titik tertentu, PEP 338 bertentangan dengan PEP 328 :
... impor relatif mengandalkan __name__ untuk menentukan posisi modul saat ini dalam hirarki paket. Dalam modul utama, nilai __name__ selalu '__main__' , jadi impor relatif eksplisit akan selalu gagal (karena mereka hanya bekerja untuk modul di dalam paket)
dan untuk mengatasi masalah ini, PEP 366 memperkenalkan variabel tingkat atas __package__
:
Dengan menambahkan atribut level modul baru, PEP ini memungkinkan impor relatif untuk bekerja secara otomatis jika modul dijalankan menggunakan
sakelar -m . Sejumlah kecil boilerplate dalam modul itu sendiri akan memungkinkan impor relatif bekerja ketika file dijalankan berdasarkan nama. [...] Ketika [atribut] hadir, impor relatif akan didasarkan pada atribut ini daripada atribut modul __name__ . [...] Ketika modul utama ditentukan oleh nama filenya, maka atribut __package__ akan diatur ke None . [...] __name __. rpartition ('.') [0] untuk modul normal dan __name__ untuk modul inisialisasi paket) Ketika sistem impor bertemu impor relatif eksplisit dalam modul tanpa __package__ set (atau dengan itu diatur ke None), itu akan menghitung dan menyimpan nilai yang benar (
(penekanan milikku)
Jika __name__
adalah '__main__'
, __name__.rpartition('.')[0]
mengembalikan string kosong. Inilah sebabnya mengapa ada string kosong literal dalam deskripsi kesalahan:
SystemError: Parent module '' not loaded, cannot perform relative import
Bagian yang relevan dari CPython ini PyImport_ImportModuleLevelObject
fungsi :
if (PyDict_GetItem(interp->modules, package) == NULL) {
PyErr_Format(PyExc_SystemError,
"Parent module %R not loaded, cannot perform relative "
"import", package);
goto error;
}
CPython memunculkan pengecualian ini jika tidak dapat menemukan package
(nama paket) di interp->modules
(dapat diakses sebagai sys.modules
). Karena sys.modules
merupakan "kamus yang memetakan nama-nama modul untuk modul yang telah dimuat" , sekarang jelas bahwa modul orang tua harus secara eksplisit mutlak-impor sebelum melakukan impor relatif .
Catatan: Patch dari masalah 18018 telah menambahkan blok lainif
, yang akan dieksekusi sebelum kode di atas:
if (PyUnicode_CompareWithASCIIString(package, "") == 0) {
PyErr_SetString(PyExc_ImportError,
"attempted relative import with no known parent package");
goto error;
} /* else if (PyDict_GetItem(interp->modules, package) == NULL) {
...
*/
Jika package
(sama seperti di atas) adalah string kosong, pesan kesalahannya adalah
ImportError: attempted relative import with no known parent package
Namun, Anda hanya akan melihat ini dalam Python 3.6 atau lebih baru.
Solusi # 1: Jalankan skrip Anda menggunakan -m
Pertimbangkan direktori (yang merupakan paket Python ):
.
├── package
│ ├── __init__.py
│ ├── module.py
│ └── standalone.py
Semua file dalam paket dimulai dengan 2 baris kode yang sama:
from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())
Saya menyertakan dua baris ini hanya untuk membuat urutan operasi jelas. Kami dapat mengabaikan mereka sepenuhnya, karena mereka tidak mempengaruhi eksekusi.
__init__.py dan module.py hanya berisi dua baris tersebut (yaitu, keduanya kosong secara efektif).
standalone.py juga mencoba mengimpor module.py melalui impor relatif:
from . import module # explicit relative import
Kami sadar bahwa itu /path/to/python/interpreter package/standalone.py
akan gagal. Namun, kita dapat menjalankan modul dengan -m
opsi baris perintah yang akan "mencari sys.path
modul yang bernama dan mengeksekusi isinya sebagai __main__
modul" :
vaultah@base:~$ python3 -i -m package.standalone
Importing /home/vaultah/package/__init__.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/module.py
>>> __file__
'/home/vaultah/package/standalone.py'
>>> __package__
'package'
>>> # The __package__ has been correctly set and module.py has been imported.
... # What's inside sys.modules?
... import sys
>>> sys.modules['__main__']
<module 'package.standalone' from '/home/vaultah/package/standalone.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
-m
melakukan semua barang impor untuk Anda dan secara otomatis ditetapkan __package__
, tetapi Anda dapat melakukannya sendiri di menu
Solusi # 2: Set __package__ secara manual
Harap perlakukan itu sebagai bukti konsep daripada solusi yang sebenarnya. Itu tidak cocok untuk digunakan dalam kode dunia nyata.
PEP 366 memiliki solusi untuk masalah ini, namun, itu tidak lengkap, karena pengaturan __package__
saja tidak cukup. Anda akan perlu mengimpor setidaknya N paket sebelumnya dalam hierarki modul, tempat N adalah jumlah direktori induk (relatif terhadap direktori skrip) yang akan dicari untuk modul yang sedang diimpor.
Jadi,
Tambahkan direktori induk dari Nth pendahulu modul saat inisys.path
Hapus direktori file saat ini dari sys.path
Impor modul induk modul saat ini menggunakan nama yang sepenuhnya memenuhi syarat
Setel __package__
ke nama yang sepenuhnya memenuhi syarat dari 2
Lakukan impor relatif
Saya akan meminjam file dari Solusi # 1 dan menambahkan beberapa sub paket lagi:
package
├── __init__.py
├── module.py
└── subpackage
├── __init__.py
└── subsubpackage
├── __init__.py
└── standalone.py
Kali ini standalone.py akan mengimpor module.py dari paket paket menggunakan impor relatif berikut
from ... import module # N = 3
Kita harus mendahului baris itu dengan kode boilerplate, untuk membuatnya bekerja.
import sys
from pathlib import Path
if __name__ == '__main__' and __package__ is None:
file = Path(__file__).resolve()
parent, top = file.parent, file.parents[3]
sys.path.append(str(top))
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
import package.subpackage.subsubpackage
__package__ = 'package.subpackage.subsubpackage'
from ... import module # N = 3
Hal ini memungkinkan kita untuk mengeksekusi standalone.py dengan nama file:
vaultah@base:~$ python3 package/subpackage/subsubpackage/standalone.py
Running /home/vaultah/package/subpackage/subsubpackage/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/subpackage/__init__.py
Importing /home/vaultah/package/subpackage/subsubpackage/__init__.py
Importing /home/vaultah/package/module.py
Solusi yang lebih umum yang dibungkus dalam suatu fungsi dapat ditemukan di sini . Contoh penggunaan:
if __name__ == '__main__' and __package__ is None:
import_parents(level=3) # N = 3
from ... import module
from ...module.submodule import thing
Solusi # 3: Gunakan impor absolut dan alat setup
Langkah-langkahnya adalah -
Ganti impor relatif eksplisit dengan impor absolut setara
Instal package
untuk membuatnya dapat diimpor
Sebagai contoh, struktur direktori mungkin sebagai berikut
.
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module.py
│ │ └── standalone.py
│ └── setup.py
di mana setup.py berada
from setuptools import setup, find_packages
setup(
name = 'your_package_name',
packages = find_packages(),
)
Sisa file dipinjam dari Solusi # 1 .
Instalasi akan memungkinkan Anda untuk mengimpor paket terlepas dari direktori kerja Anda (dengan asumsi tidak akan ada masalah penamaan).
Kami dapat memodifikasi standalone.py untuk menggunakan keunggulan ini (langkah 1):
from package import module # absolute import
Ubah direktori kerja Anda project
dan jalankan /path/to/python/interpreter setup.py install --user
( --user
instal paket di direktori paket situs Anda ) (langkah 2):
vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user
Mari kita verifikasi bahwa sekarang mungkin untuk menjalankan standalone.py sebagai skrip:
vaultah@base:~/project$ python3 -i package/standalone.py
Running /home/vaultah/project/package/standalone.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
Catatan : Jika Anda memutuskan untuk turun rute ini, Anda akan lebih baik menggunakan lingkungan virtual untuk menginstal paket secara terpisah.
Solusi # 4: Gunakan impor absolut dan beberapa kode boilerplate
Terus terang, instalasi tidak diperlukan - Anda dapat menambahkan beberapa kode boilerplate ke skrip Anda untuk membuat impor absolut berfungsi.
Saya akan meminjam file dari Solution # 1 dan mengubah standalone.py :
Menambahkan direktori induk dari paket untuk sys.path
sebelum mencoba apa pun impor dari paket menggunakan impor absolut:
import sys
from pathlib import Path # if you haven't already done so
file = Path(__file__).resolve()
parent, root = file.parent, file.parents[1]
sys.path.append(str(root))
# Additionally remove the current file's directory from sys.path
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
Ganti impor relatif dengan impor absolut:
from package import module # absolute import
standalone.py berjalan tanpa masalah:
vaultah@base:~$ python3 -i package/standalone.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
Saya merasa bahwa saya harus memperingatkan Anda: cobalah untuk tidak melakukan ini, terutama jika proyek Anda memiliki struktur yang kompleks.
Sebagai catatan tambahan, PEP 8 merekomendasikan penggunaan impor absolut, tetapi menyatakan bahwa dalam beberapa skenario, impor relatif eksplisit dapat diterima:
Impor absolut direkomendasikan, karena biasanya lebih mudah dibaca dan cenderung berperilaku lebih baik (atau setidaknya memberikan pesan kesalahan yang lebih baik). [...] Namun, impor relatif eksplisit adalah alternatif yang dapat diterima untuk impor absolut, terutama ketika berhadapan dengan tata letak paket yang rumit di mana menggunakan impor absolut akan menjadi kata yang tidak perlu.