(轉)Python中的模塊循環導入問題


 

本文轉自: https://wiki.woodpecker.org.cn/moin/MiscItems/2008-11-25

問題

 cleven <shenglipang@gmail.com>

回覆至 python-cn@googlegroups.com 收件人 python-cn@googlegroups.com 日期 2008年11月25日 下午 12:01 主旨 [CPyUG:72341] import嵌套的問題

看了《Python源碼剖析》,里面提到的嵌套import的問題還是沒有弄明白,各位給看一下吧。

[A.py]
from B import D class C:pass  [B.py] from A import C class D:pass

為什么執行A的時候不能加載D呢?

如果將A.py改為:import B就可以了。

這是怎么回事呢?

 

Robert Chen:詳解

 

Robert Chen <search.pythoner@gmail.com>
回覆至 python-cn@googlegroups.com 收件人 python-cn@googlegroups.com 日期 2008年11月25日 下午 1:41 主旨 [CPyUG:72362] Re: import嵌套的問題

恩,這跟Python內部import的機制是有關的,具體到from B import D,Python內部會分成幾個步驟:

  1. 在sys.modules中查找符號"B"
  2. 如果符號B存在,則獲得符號B對應的module對象<module B>

    • 從<module B>的__dict__中獲得符號"D"對應的對象,如果"D"不存在,則拋出異常

  3. 如果符號B不存在,則創建一個新的module對象<module B>,注意,這時,module對象的__dict__為空

    • 執行B.py中的表達式,填充<module B>的__dict__

    • 從<module B>的__dict__中獲得"D"對應的對象,如果"D"不存在,則拋出異常

所以,這個例子的執行順序如下:

1、執行A.py中的from B import D
 由於是執行的python A.py,所以在sys.modules中並沒有<module B>存在,  首先為B.py創建一個module對象(<module B>),  注意,這時創建的這個module對象是空的,里邊啥也沒有,  在Python內部創建了這個module對象之后,就會解析執行B.py,其目的是填充<module B>這個dict。  2、執行B.py中的from A import C  在執行B.py的過程中,會碰到這一句,  首先檢查sys.modules這個module緩存中是否已經存在<module A>了,  由於這時緩存還沒有緩存<module A>,  所以類似的,Python內部會為A.py創建一個module對象(<module A>),  然后,同樣地,執行A.py中的語句  3、再次執行A.py中的from B import D  這時,由於在第1步時,創建的<module B>對象已經緩存在了sys.modules中,  所以直接就得到了<module B>,  但是,注意,從整個過程來看,我們知道,這時<module B>還是一個空的對象,里面啥也沒有,  所以從這個module中獲得符號"D"的操作就會拋出異常。  如果這里只是import B,由於"B"這個符號在sys.modules中已經存在,所以是不會拋出異常的。

 

ZQ:圖解

PyImportFlow.png

 

編譯追蹤

hiter的日記:

問題代碼如下:

A.py
from A import B class B(object):pass >>> import A Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "/home/john/pythonstudy/mypython/bin/A.py", line 9, in <module>  from A import B ImportError: cannot import name B >>>

閱讀代碼后發現: 字節碼大概

9           0 LOAD_CONST               0 (-1)
 3 LOAD_CONST 1 (('B',))  6 IMPORT_NAME 0 (A)  9 IMPORT_FROM 1 (B)  12 STORE_NAME 1 (B)  15 POP_TOP   10 16 LOAD_CONST 2 ('B')  19 LOAD_NAME 2 (object)  22 BUILD_TUPLE 1  25 LOAD_CONST 3 (<code object B at 0xb7a1fa88, file "A.py", line 10>)  28 MAKE_FUNCTION 0  31 CALL_FUNCTION 0  34 BUILD_CLASS  35 STORE_NAME 1 (B)   12 38 LOAD_CONST 4 ('hi')  41 PRINT_ITEM  42 PRINT_NEWLINE  43 LOAD_CONST 5 (None)  46 RETURN_VALUE
  • 可以看出整個import的過程是:先import A,然后再import A然后報錯。
  • 經過分析發現原因是:在import A時,虛擬機發現sys.modules(在import_submodule中會做檢查)中沒有加載過A,然后新建了一個A的module,新建的module是空的,需要向里面加入__builtin__,__file__等屬性(在執行下一個import的時候,新module的dict將作為globals(locals)傳給執行(A)字節碼時使用),然后虛擬機會將這個新的module加入sys.modules中,至此虛擬機的調用堆棧如下:(代碼行號可能不對,因為源碼中加入了很多調試輸出代碼)

 

#0  PyImport_AddModule (name=0xbfd91673 "A") at Python/import.c:617                                                            <-------PyImport_AddModule 在這里
#1 0x08106271 in PyImport_ExecCodeModuleEx (name=0xbfd91673 "A", co=0xb7da6748, pathname=0xbfd8f533 "A.pyc") at Python/import.c:653 #2 0x08106c67 in load_source_module (name=0xbfd91673 "A", pathname=0xbfd8f533 "A.pyc", fp=0x821bd60) at Python/import.c:963 #3 0x081085cf in load_module (name=0xbfd91673 "A", fp=0x821bd60, buf=0xbfd905d3 "A.py", type=1, loader=0x0) at Python/import.c:1753 #4 0x0810a39b in import_submodule (mod=0x818c888, subname=0xbfd91673 "A", fullname=0xbfd91673 "A") at Python/import.c:2433 <--------import_submodule 在這里 #5 0x081098bb in load_next (mod=0x818c888, altmod=0x818c888, p_name=0xbfd91654, buf=0xbfd91673 "A", p_buflen=0xbfd9166c)  at Python/import.c:2234 #6 0x08108e1c in import_module_level (name=0x0, globals=0xb7de82b4, locals=0xb7de82b4, fromlist=0x818c888, level=-1) at Python/import.c:2005 #7 0x081093a1 in PyImport_ImportModuleLevel (name=0xb7de115c "A", globals=0xb7de82b4, locals=0xb7de82b4, fromlist=0x818c888, level=-1)  at Python/import.c:2076 #8 0x080d8809 in builtin___import__ (self=0x0, args=0xb7d9de34, kwds=0x0) at Python/bltinmodule.c:47 #9 0x0814d04b in PyCFunction_Call (func=0xb7dcf5ac, arg=0xb7d9de34, kw=0x0) at Objects/methodobject.c:77 #10 0x08062974 in PyObject_Call (func=0xb7dcf5ac, arg=0xb7d9de34, kw=0x0) at Objects/abstract.c:1861 #11 0x080ecad2 in PyEval_CallObjectWithKeywords (func=0xb7dcf5ac, arg=0xb7d9de34, kw=0x0) at Python/ceval.c:3446 #12 0x080e7b33 in PyEval_EvalFrameEx (f=0x821bc04, throwflag=0) at Python/ceval.c:2068 #13 0x080eaf9e in PyEval_EvalCodeEx (co=0xb7d9ab08, globals=0xb7de82b4, locals=0xb7de82b4, args=0x0, argcount=0, kws=0x0, kwcount=0,  defs=0x0, defcount=0, closure=0x0) at Python/ceval.c:2840 #14 0x080e013e in PyEval_EvalCode (co=0xb7d9ab08, globals=0xb7de82b4, locals=0xb7de82b4) at Python/ceval.c:494 #15 0x08116ab0 in run_mod (mod=0x8220378, filename=0x81653bb "<stdin>", globals=0xb7de82b4, locals=0xb7de82b4, flags=0xbfd92f70,  arena=0x81c5cd8) at Python/pythonrun.c:1273 #16 0x081151e1 in PyRun_InteractiveOneFlags (fp=0xb7f4d440, filename=0x81653bb "<stdin>", flags=0xbfd92f70) at Python/pythonrun.c:792 #17 0x08114e54 in PyRun_InteractiveLoopFlags (fp=0xb7f4d440, filename=0x81653bb "<stdin>", flags=0xbfd92f70) at Python/pythonrun.c:723 #18 0x08114cac in PyRun_AnyFileExFlags (fp=0xb7f4d440, filename=0x81653bb "<stdin>", closeit=0, flags=0xbfd92f70) at Python/pythonrun.c:692 #19 0x08059d60 in Py_Main (argc=1, argv=0xbfd93074) at Modules/main.c:523 #20 0x08058e26 in main (argc=136033156, argv=0xb7dc37b4) at ./Modules/python.c:23
  • 可以新建module並將其加入sys.modules是在函數PyImport_ExecCodeModuleEx中完成的,此后,就會將新module的dict作為locals(globals)傳給執行A字節碼的函數,在執行A字節碼時,發現需要IMPORT_NAME A,這時虛擬機會發現在sys.modules中已經存在A,所以會直接返回這個A的module,而在接下來的IMPORT_FROM時,會從這個module中試圖找到B,而此時這個module里虛擬機只加載了__builtin__,__file__等屬性,加載B的字節碼還沒執行到(也不可能執行到),所以虛擬機就會拋出無法加載B的異常。

  • 在<python源碼剖析>前言中提到這樣一個問題:

 

 [A.py]
from B import D class C:  pass [B.py] from A import C class D:  pass

這里無法加載D,這個問題是和本文一開始提出的問題相似的。

 

總結:

IMPORT_NAME字節碼命令的執行流程如
  1. 假設需要import A,那么虛擬機首先在sys.modules中查找是否已經load過A,
  2. 找到則返回該對象,命令結束;
  3. 如果沒有找到,那么虛擬機會新建一個module對象,
  4. 然后向module對象中添加必要的屬性(builtin等),

  5. 然后用這個module中的dict作為globals(locals)執行A,
  6. 然后返回


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM