Python Relative Import

概述

Python中的相對導(dǎo)入(Relative Import)有時是很神秘和晦澀的。有時會遇到ValueError: Attempted relative import beyond top-level package這樣的錯誤琐鲁。讓我們看下如何修正這個問題卫旱。

重現(xiàn)

假設(shè)有如下項目結(jié)構(gòu)

test_py
│  main.py
│  __init__.py
│
├─tests
│      mytest.py
│      __init__.py
│
└─tools
        doo.py
        __init__.py

tools/__init__.py內(nèi)容如下所示

def tool_init():
    return 'this is tool_init'

現(xiàn)在需要在tests/mytest.py中調(diào)用tools/__init__.py中定義的函數(shù)人灼,很可能會這么寫:

from ..tools import tool_init

print(tool_init())

執(zhí)行結(jié)果如下:

D:\Documents\JavaSpace\test_py>python tests/mytest.py
Traceback (most recent call last):
  File "tests/mytest.py", line 1, in <module>
    from ..tools import tool_init
ValueError: attempted relative import beyond top-level package

這個錯誤字面意思是:相對導(dǎo)入超過了top-level围段。導(dǎo)致這個問題原因是錯誤地將相對導(dǎo)入理解成相對路徑導(dǎo)入。

官方解釋

PEP 328 -- Imports: Multi-Line and Absolute/Relative中可以找到python解釋器是怎樣解釋相對導(dǎo)入的:

相對導(dǎo)入使用模塊的name屬性來確定模塊在包中的層次位置投放。如果模塊的名稱不包含任何包的信息(name被設(shè)置為'main')奈泪。那么相對導(dǎo)入將把模塊解析成top-level模塊,而不關(guān)心模塊在實際文件系統(tǒng)中的位置灸芳。

Relative imports use a module's name attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to 'main') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.

因此涝桅,以python tests/mytest.py這種直接啟動的方式執(zhí)行代碼時,mytest.py被視為top-level模塊烙样。而mytest.py中的from ..tools import tool_init就超過了top-level(beyond top-level package)冯遂,所以出現(xiàn)ValueError: attempted relative import beyond top-level package的錯誤。

探索

修改python tests/mytest.py代碼谒获,如下所示:

from tools import tool_init

print(tool_init())

# 執(zhí)行結(jié)果如下:
D:\Documents\JavaSpace\test_py>python tests/mytest.py
Traceback (most recent call last):
  File "tests/mytest.py", line 1, in <module>
    from tools import tool_init
ModuleNotFoundError: No module named 'tools'

上面的錯誤表示蛤肌,在當(dāng)前的路徑中壁却,找不到tools模塊。修改tests/mytest.py代碼裸准,查看當(dāng)前路徑:

import sys

for p in sys.path:
    print(p)

# 執(zhí)行結(jié)果如下:
D:\Documents\JavaSpace\test_py>python tests/mytest.py
D:\Documents\JavaSpace\test_py\tests
D:\Anaconda3\python37.zip
D:\Anaconda3\DLLs
D:\Anaconda3\lib
D:\Anaconda3
D:\Anaconda3\lib\site-packages
D:\Anaconda3\lib\site-packages\win32
D:\Anaconda3\lib\site-packages\win32\lib
D:\Anaconda3\lib\site-packages\Pythonwin

在上面結(jié)果所示的路徑中展东,是找不到tools模塊的。tools模塊在D:\Documents\JavaSpace\test_py中炒俱。解決這個問題有兩種方式盐肃。

方式一

在代碼中增加sys.path.append('..')代碼,增加父級路徑权悟。修改 tests/mytest.py代碼如下所示:

import sys
sys.path.append('..')
from tools import tool_init

for p in sys.path:
    print(p)

print('*'*20)
print(tool_init())

執(zhí)行時砸王,進(jìn)入tests目錄,再執(zhí)行mytest.py文件峦阁,結(jié)果如下:

D:\Documents\JavaSpace\test_py\tests>python mytest.py
D:\Documents\JavaSpace\test_py\tests
D:\Anaconda3\python37.zip
D:\Anaconda3\DLLs
D:\Anaconda3\lib
D:\Anaconda3
D:\Anaconda3\lib\site-packages
D:\Anaconda3\lib\site-packages\win32
D:\Anaconda3\lib\site-packages\win32\lib
D:\Anaconda3\lib\site-packages\Pythonwin
..
********************
this is tool_init

方式二

使用-m參數(shù)处硬,該選項使用python模塊命名空間定位模塊,以作為i腳本的方式執(zhí)行(The python -m option allows modules to be located using the Python module namespace for execution as scripts)拇派。修改tests/mytest.py代碼如下所示:

import sys
from tools import tool_init

for p in sys.path:
    print(p)

print('*'*20)
print(tool_init())

執(zhí)行時荷辕,再頂層目錄下執(zhí)行,且執(zhí)行的文件寫成tests.mytest的形式件豌。執(zhí)行結(jié)果如下:

D:\Documents\JavaSpace\test_py>python -m tests.mytest
D:\Documents\JavaSpace\test_py
D:\Anaconda3\python37.zip
D:\Anaconda3\DLLs
D:\Anaconda3\lib
D:\Anaconda3
D:\Anaconda3\lib\site-packages
D:\Anaconda3\lib\site-packages\win32
D:\Anaconda3\lib\site-packages\win32\lib
D:\Anaconda3\lib\site-packages\Pythonwin
********************
this is tool_init

總結(jié)

方式一和方式二疮方,對應(yīng)的是兩種啟動方式:

  • 方式一,直接啟動茧彤,python xxx.py骡显。將xxx.py所在的路徑放入sys.path
  • 方式二,模塊啟動曾掂,python -m xxx惫谤。將啟動python命令的路徑放入sys.path

兩中啟動方式的主要區(qū)別就在于,將不同的路徑放入sys.path中珠洗。因此溜歪,當(dāng)啟動一個py文件時,需要考慮许蓖,該py文件所引用的包在sys.path中的路徑中是否能找到蝴猪。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市膊爪,隨后出現(xiàn)的幾起案子自阱,更是在濱河造成了極大的恐慌,老刑警劉巖米酬,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沛豌,死亡現(xiàn)場離奇詭異,居然都是意外死亡赃额,警方通過查閱死者的電腦和手機(jī)加派,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門阁簸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人哼丈,你說我怎么就攤上這事启妹。” “怎么了醉旦?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵饶米,是天一觀的道長。 經(jīng)常有香客問我车胡,道長檬输,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任匈棘,我火速辦了婚禮丧慈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘主卫。我一直安慰自己逃默,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布簇搅。 她就那樣靜靜地躺著完域,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瘩将。 梳的紋絲不亂的頭發(fā)上吟税,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音姿现,去河邊找鬼肠仪。 笑死,一個胖子當(dāng)著我的面吹牛备典,可吹牛的內(nèi)容都是我干的异旧。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼熊经,長吁一口氣:“原來是場噩夢啊……” “哼泽艘!你這毒婦竟也來了欲险?” 一聲冷哼從身側(cè)響起镐依,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎天试,沒想到半個月后槐壳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡喜每,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年务唐,在試婚紗的時候發(fā)現(xiàn)自己被綠了雳攘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡枫笛,死狀恐怖吨灭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情刑巧,我是刑警寧澤喧兄,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站啊楚,受9級特大地震影響吠冤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恭理,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一拯辙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颜价,春花似錦涯保、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至横辆,卻和暖如春撇他,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狈蚤。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工困肩, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脆侮。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓锌畸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親靖避。 傳聞我的和親對象是個殘疾皇子潭枣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344