簡體   English   中英

Python 中的循環模塊依賴和相對導入

[英]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.apkg.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 barimport 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM