[英]Cyclic module dependencies and relative imports in Python
假設我們有兩個具有循環依賴的模塊:
# a.py
import b
def f(): return b.y
x = 42
# b.py
import a
def g(): return a.x
y = 43
這兩個模塊位於pkg
目錄中,其中__init__.py
為空。 導入pkg.a
或pkg.b
工作正常,如this answer中所述。 如果我將進口更改為相對進口
from . import b
嘗試導入其中一個模塊時出現ImportError
:
>>> import pkg.a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pkg/a.py", line 1, in <module>
from . import b
File "pkg/b.py", line 1, in <module>
from . import a
ImportError: cannot import name a
為什么我會收到此錯誤? 情況不是和上面差不多嗎? (這與這個問題有關嗎?)
編輯:這個問題與軟件設計無關。 我知道避免循環依賴的方法,但無論如何我對錯誤的原因很感興趣。
首先讓我們from import
工作開始:
那么首先讓我們看一下字節碼:
>>> def foo():
... from foo import bar
>>> dis.dis(foo)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 2 (('bar',))
6 IMPORT_NAME 0 (foo)
9 IMPORT_FROM 1 (bar)
12 STORE_FAST 0 (bar)
15 POP_TOP
16 LOAD_CONST 0 (None)
19 RETURN_VALUE
嗯很有趣:),所以from foo import bar
被翻譯成第一個IMPORT_NAME foo
,相當於import foo
然后IMPORT_FROM bar
。
現在IMPORT_FROM
做什么?
讓我們看看 python 在找到IMPORT_FROM
時做了什么:
TARGET(IMPORT_FROM)
w = GETITEM(names, oparg);
v = TOP();
READ_TIMESTAMP(intr0);
x = import_from(v, w);
READ_TIMESTAMP(intr1);
PUSH(x);
if (x != NULL) DISPATCH();
break;
好吧,基本上他得到了要導入的名稱,在我們的foo()
function 將是bar
,然后他從幀堆棧中彈出值v
,這是最后執行的操作碼的返回,即IMPORT_NAME
,然后調用function import_from()
與這兩個 arguments:
static PyObject *
import_from(PyObject *v, PyObject *name)
{
PyObject *x;
x = PyObject_GetAttr(v, name);
if (x == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Format(PyExc_ImportError, "cannot import name %S", name);
}
return x;
}
如您所見, import_from()
function 很簡單,它首先嘗試從模塊v
獲取屬性name
,如果它不存在,則引發ImportError
否則返回此屬性。
現在這與相對導入有什么關系?
以及相對導入,例如from. import b
from. import b
是等價的,例如在 OP 問題中與from pkg import b
的情況相同。
但這是怎么發生的? 要理解這一點,我們應該看看 python 的import.c
模塊,特別是 function get_parent() 。 如您所見, function 在這里列出的時間很長,但一般來說,當它看到相對導入時,它會嘗試替換 dot .
父 package 取決於__main__
模塊,這再次來自 OP 問題是 package pkg
。
現在讓我們把所有這些放在一起,並試圖弄清楚為什么我們最終會出現 OP 問題中的行為。
為此,如果我們可以看到 python 在進行導入時做了什么,這將對我們有所幫助,這是我們的幸運日 python 已經具有此功能,可以通過在額外詳細模式下運行它來啟用-vv
。
所以使用命令行: python -vv -c 'import pkg.b'
:
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
import pkg # directory pkg
# trying pkg/__init__.so
# trying pkg/__init__module.so
# trying pkg/__init__.py
# pkg/__init__.pyc matches pkg/__init__.py
import pkg # precompiled from pkg/__init__.pyc
# trying pkg/b.so
# trying pkg/bmodule.so
# trying pkg/b.py
# pkg/b.pyc matches pkg/b.py
import pkg.b # precompiled from pkg/b.pyc
# trying pkg/a.so
# trying pkg/amodule.so
# trying pkg/a.py
# pkg/a.pyc matches pkg/a.py
import pkg.a # precompiled from pkg/a.pyc
# clear[2] __name__
# clear[2] __file__
# clear[2] __package__
# clear[2] __name__
# clear[2] __file__
# clear[2] __package__
...
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "pkg/b.py", line 1, in <module>
from . import a
File "pkg/a.py", line 2, in <module>
from . import a
ImportError: cannot import name a
# clear __builtin__._
嗯ImportError
之前發生了什么?
一) from. import a
調用pkg/b.py
中的from. import a
,按照上面的解釋翻譯為from pkg import a
,在字節碼中又相當於import pkg; getattr(pkg, 'a')
import pkg; getattr(pkg, 'a')
。 但是等一下, a
也是一個模塊?! 那么有趣的部分來了,如果我們有類似from module|package import module
在這種情況下會發生第二次導入,即在 import 子句中導入模塊。 因此,在 OP 示例中,我們現在需要導入pkg/a.py
,首先我們在sys.modules
中設置新模塊的密鑰pkg.a
,然后我們繼續解釋模塊pkg/a.py
,但在模塊pkg/a.py
完成導入之前調用它from. import b
from. import b
。
現在是第二部分, pkg/b.py
將被導入,然后它會首先嘗試import pkg
,因為pkg
已經導入,所以在我們的sys.modules
中有一個鍵pkg
它只會返回值那把鑰匙。 然后它將import b
設置sys.modules
中的pkg.b
鍵並開始解釋。 我們從這條線from. import a
from. import a
!
但是請記住pkg/a.py
已經導入,這意味着('pkg.a' in sys.modules) == True
所以導入將被跳過,並且只會調用getattr(pkg, 'a')
,但是什么會發生嗎? python 沒有完成導入pkg/a.py
?? 所以只會調用getattr(pkg, 'a')
,這將在import_from()
function 中引發AttributeError
,這將被轉換為ImportError(cannot import name a)
。
免責聲明:這是我自己努力了解口譯員內部發生的事情,我離成為專家還很遙遠。
編輯:這個答案被改寫了,因為當我試圖再次閱讀它時,我注意到我的答案是如何糟糕的,希望現在它會更有用:)
(順便說一句,相對導入無關緊要。使用from pkg import
... 顯示相同的異常。)
我認為這里發生的事情是from foo import bar
和import foo.bar
之間的區別在於,在第一個中,值bar
可以是 pkg foo
中的模塊,也可以是模塊foo
中的變量。 在第二種情況下, bar
除了模塊/包之外的任何東西都是無效的。
這很重要,因為如果已知 bar 是一個模塊,那么sys.modules
的內容就足以填充它。 如果它可能是foo
模塊中的變量,那么解釋器實際上必須查看foo
的內容,但是在導入foo
時,那將是無效的; 實際模塊尚未填充。
在相對導入的情況下,我們理解from. import bar
from. import bar
表示從包含當前模塊的 package 導入 bar 模塊,但這實際上只是語法糖, .
name 被翻譯成一個完全限定的名稱並傳遞給__import__()
,因此它會像 ambigious from foo import bar
一樣查找整個世界
作為附加說明:
我有以下模塊結構:
base
+guiStuff
-gui
+databaseStuff
-db
-basescript
我希望能夠通過import base.basescript
運行我的腳本,但是由於gui
文件有一個import base.databaseStuff.db
導致導入base
,因此失敗並出現錯誤。 由於base
僅注冊為__main__
它導致整個導入的第二次執行和上述錯誤,除非我在base
上方使用外部腳本,因此僅導入一次base
/ basescript
。 為了防止這種情況,我在我的基本腳本中加入了以下內容:
if __name__ == '__main__' or \
not '__main__' in sys.modules or \
sys.modules['__main__'].__file__ != __file__:
#imports here
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.