açıklama
Kaynaktan PEP 328
Göreli içe aktarmalar, modülün paket hiyerarşisindeki konumunu belirlemek için bir modülün __name__ özelliğini kullanır. Modülün adı herhangi bir paket bilgisi içermiyorsa (örn. '__Main__' olarak ayarlanmışsa), göreli ithalatlar , modülün gerçekte dosya sisteminde nerede bulunduğuna bakılmaksızın modülün en üst düzey bir modülmiş
gibi çözümlenir .
Bir noktada PEP 338 , PEP 328 ile çatıştı :
... göreli ithalatlar , geçerli modülün paket hiyerarşisindeki konumunu belirlemek için __name__ işlevini kullanır . Bir ana modülde __name__ değeri her zaman '__main__' olur , bu nedenle açık göreli içe aktarmalar her zaman başarısız olur (yalnızca bir paket içindeki bir modül için çalıştıklarından)
ve sorunu çözmek için PEP 366 en üst seviye değişkenini tanıttı __package__
:
Yeni bir modül seviyesi özniteliği ekleyerek, bu PEP, modül -m
anahtarı kullanılarak yürütülürse göreli içe aktarmaların otomatik olarak çalışmasına izin verir . Modülün kendisinde az miktarda kaynatma plakası, dosya adıyla yürütüldüğünde göreli içe aktarmaların çalışmasına izin verecektir. [...] [öznitelik] mevcut olduğunda, göreli içe aktarmalar __name__ özniteliği yerine bu özniteliğe dayalı olacaktır . [...] Ana modül dosya adıyla belirtildiğinde, __package__ özniteliği Yok olarak ayarlanır . [...] İçe aktarma sistemi __package__ set (veya None olarak ayarlanmamış) olmadan bir modülde açık bir göreceli içe aktarma ile karşılaştığında, doğru değeri hesaplar ve saklar (__name __. rpartition ('.') [0] normal modüller için ve __name__ paket başlatma modülleri için)
(benimkini vurgula)
Eğer __name__
ise '__main__'
, __name__.rpartition('.')[0]
boş dize döndürür. Hata açıklamasında boş dize hazır bilgisi olmasının nedeni budur:
SystemError: Parent module '' not loaded, cannot perform relative import
CPython PyImport_ImportModuleLevelObject
işlevinin ilgili kısmı :
if (PyDict_GetItem(interp->modules, package) == NULL) {
PyErr_Format(PyExc_SystemError,
"Parent module %R not loaded, cannot perform relative "
"import", package);
goto error;
}
CPython, package
(paketin adı) içinde interp->modules
(olarak erişilebilir ) bulamazsa bu istisnayı yükseltir sys.modules
. Yana sys.modules
olan "zaten yüklenmiş olan modülleri modül isimleri eşleştiren bir sözlük" , artık o temizlemek oluyor ebeveyn modülü açıkça nispi ithalat yapmadan önce-mutlak ithal olmalıdır .
Not: Sayı 18018'deki düzeltme eki,yukarıdaki koddan önce yürütülecek başka bir if
blok ekledi:
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) {
...
*/
Eğer package
(yukarıdaki ile aynı) boş bir dize, hata mesajı olacak
ImportError: attempted relative import with no known parent package
Ancak, bunu yalnızca Python 3.6 veya daha yenisinde görürsünüz.
1. Çözüm: Komut dosyanızı -m kullanarak çalıştırın
Bir dizin düşünün (Python paketi ):
.
├── package
│ ├── __init__.py
│ ├── module.py
│ └── standalone.py
Paketteki tüm dosyalar aynı 2 kod satırıyla başlar:
from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())
Bu iki satırı yalnızca işlemlerin sırasını açıklığa kavuşturmak için dahil ediyorum . Uygulamayı etkilemedikleri için onları tamamen görmezden gelebiliriz.
__init__.py ve module.py yalnızca bu iki satırı içerir (etkin bir şekilde boştur).
standalone.py ek olarak module.py dosyasını göreli içe aktarma yoluyla içe aktarmaya çalışır :
from . import module # explicit relative import
/path/to/python/interpreter package/standalone.py
Başarısız olacağının farkındayız . Ancak, biz birlikte modülü çalıştırabilirsiniz -m
komut satırı seçeneği olacaktır "arama sys.path
isimli modül için ve aynı içeriğini yürütmek __main__
modülü" :
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
tüm içe aktarma işlemlerini sizin için yapar ve otomatik olarak ayarlanır __package__
, ancak bunu kendiniz de yapabilirsiniz
2. Çözüm: __package__'yi manuel olarak ayarlayın
Lütfen bunu gerçek bir çözümden ziyade bir kavram kanıtı olarak kabul edin. Gerçek dünya kodlarında kullanım için uygun değildir.
PEP 366'nın bu soruna bir çözümü vardır, ancak bu tamamlanmamıştır, çünkü __package__
tek başına ayar yapmak yeterli değildir. Modül hiyerarşisinde en az N önceki paketi içe aktarmanız gerekir ; burada N , içe aktarılan modül için aranacak üst dizinlerin (komut dosyasının dizinine göre) sayısıdır.
Böylece,
Geçerli modülün N'inci öncülünün üst dizininisys.path
Geçerli dosyanın dizinini sys.path
Geçerli modülün ana modülünü tam adını kullanarak alma
2'den__package__
tam nitelikli isme ayarlayın
Göreli içe aktarmayı gerçekleştirme
1. Çözümden dosya ödünç alacağım ve daha fazla alt paket ekleyeceğim:
package
├── __init__.py
├── module.py
└── subpackage
├── __init__.py
└── subsubpackage
├── __init__.py
└── standalone.py
Bu sefer standalone.py aşağıdaki göreli içe aktarmayı kullanarak module.py dosyasını paket paketinden içe aktarır
from ... import module # N = 3
İşe yaraması için bu satırdan önce kazan plakası kodunu kullanmamız gerekecek.
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
Dosya adıyla standalone.py dosyasını çalıştırmamızı sağlar :
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
Bir fonksiyona sarılmış daha genel bir çözüm burada bulunabilir . Örnek kullanım:
if __name__ == '__main__' and __package__ is None:
import_parents(level=3) # N = 3
from ... import module
from ...module.submodule import thing
Adımlar -
Açık göreli ithalatları, eşdeğer mutlak ithalatlarla değiştir
package
İçe aktarılabilir hale getirmek için yükleyin
Örneğin, dizin yapısı aşağıdaki gibi olabilir
.
├── project
│ ├── package
│ │ ├── __init__.py
│ │ ├── module.py
│ │ └── standalone.py
│ └── setup.py
nerede setup.py olduğu
from setuptools import setup, find_packages
setup(
name = 'your_package_name',
packages = find_packages(),
)
Geri kalan dosyalar Çözüm # 1'den ödünç alındı .
Kurulum, çalışma dizininizden bağımsız olarak paketi içe aktarmanıza izin verecektir (adlandırma sorunu olmayacağı varsayılarak).
Bu avantajı kullanmak için standalone.py dosyasını değiştirebiliriz (1. adım):
from package import module # absolute import
Çalışma dizininizi değiştirin project
ve çalıştırın /path/to/python/interpreter setup.py install --user
( --user
paketi site paketleri dizininize yükler ) (2. adım):
vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user
Artık standalone.py dosyasını komut dosyası olarak çalıştırmanın mümkün olduğunu doğrulayalım :
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'>
Not : Bu rotaya gitmeye karar verirseniz,paketleri tek başına yüklemekiçin sanal ortamları kullanmaktan daha iyi olur.
4.Çözüm: Mutlak ithalatlar ve bazı kaynak plakası kodları kullanın
Açıkçası, kurulum gerekli değildir - mutlak ithalatın çalışması için betiğinize bazı kaynak kodu ekleyebilirsiniz.
Çözüm # 1'den dosyalar ödünç alacağım ve standalone.py dosyasını değiştireceğim :
Ana dizin ekleyin pakete kadar sys.path
önce ithal şey denemeden paketin mutlak ithalatı kullanılarak:
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
Göreli içe aktarmayı mutlak içe aktarma ile değiştirin:
from package import module # absolute import
standalone.py sorunsuz çalışır:
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'>
Sizi uyarmam gerektiğini hissediyorum: özellikle projeniz karmaşık bir yapıya sahipse bunu yapmamaya çalışın .
Bir yan not olarak, PEP 8 mutlak ithalatın kullanılmasını tavsiye eder, ancak bazı senaryolarda açık göreli ithalatın kabul edilebilir olduğunu belirtir:
Genellikle daha okunabilir olduklarından ve daha iyi davranma eğiliminde olduklarından (veya en azından daha iyi hata mesajları verdiklerinden) mutlak ithalatlar önerilir. [...] Bununla birlikte, açık göreli ithalatlar, özellikle mutlak ithalatların gereksiz yere ayrıntılı olacağı karmaşık paket düzenleri ile uğraşırken, mutlak ithalat için kabul edilebilir bir alternatiftir.