天天看點

python之from 和import執行過程分析

原文連結:http://blog.csdn.net/lis_12/article/details/52883729

問題1

同一個目錄下,有兩個Python檔案,A.py,B.py

#A.py
from B import D
class C(object):
    pass

#B.py
from A import C
class D(object):
    pass
'''
執行A.py
結果:
Traceback (most recent call last):
  File "A.py", line 4, in <module>
    from B import D
  File "B.py", line 4, in <module>
    from A import C
  File "A.py", line 4, in <module>
    from B import D
ImportError: cannot import name D
'''      

why?

from B import D的執行機制

可以利用sys.modules檢視是否包含B子產品,如檢視os子產品,輸入

>>> sys.modules[‘os’].

  1. 存在子產品B

    如果sys.modules有B這個鍵,就會擷取相對應的值,也就是modules對象,然後從子產品B的__dict__清單中查找并擷取名稱為D的對象,如果不存在,抛出異常.

    子產品B的__dict__清單可利用dir(B)來檢視.

  2. 不存在子產品B

    如果不存在B這個鍵的話,則會為B建立一個子產品對象,此時子產品對象的__dict__清單為空,然後在搜尋路徑下查找并執行B.py,以填充子產品B對象的__dict__清單,然後從__dict__清單中查找名稱為D的對象,如果找不到,則抛出異常.

    注:module ,子產品的意思

根據上面修改代碼,檢視sys.modules變化情況

#A.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
try:
    print 'module B: %s'%sys.modules['B']
except Exception,e:
    print u'沒有module B  %s'%e

try:
    print 'module A: %s'%sys.modules['A']
except Exception,e:
    print u'沒有module A  %s'%e

from B import D
class C(object):
    pass

#B.py
from A import C
class D(object):
    pass
'''
執行A.py
結果:
沒有module B  'B'
沒有module A  'A'
module B: <module 'B' from 'B.pyc'>
module A: <module 'A' from 'A.py'>
Traceback (most recent call last):
  File "A.py", line 4, in <module>
    from B import D
  File "B.py", line 4, in <module>
    from A import C
  File "A.py", line 4, in <module>
    from B import D
ImportError: cannot import name D      

問題1答案

  1. 運作A.py,當執行到from B import D語句時,因為還沒有運作過B.py,是以sys.modules中沒有B這個鍵.會建立一個鍵B并指派為子產品B對象,隻不過此時子產品B對象是空的,裡面什麼都沒有;
  2. 然後,暫停執行A.py的其他語句,Python 在搜尋路徑下查找B.py,找到同目錄下的B.py并運作,為了填充子產品B對象中的__dict__清單.當執行到from A import C時,也會檢查sys.modules中是否有名為A的子產品,但因為A.py還沒有讀取完,是以并沒有在sys.modules中緩存對應的資訊.同樣,Python 會建立一個鍵A并指派為空的子產品A對象.暫停執行B.py并尋找、從頭執行A.py.
  3. 這時,會再次執行到from B import D語句,由于在第一步時已經在sys.modules建立了鍵B的子產品B對象,是以直接擷取到,但此時子產品B對象中的__dict__清單還是空的,裡面什麼都沒有啊,是以找不到名為D的對象,抛出異常.

對照運作結果,是不是很符合- -!

(表述能力有限,多看代碼吧- -)

問題2 将上述from import 換成 import

#A1.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
try:
    print 'module B1: %s'%sys.modules['B1']
except Exception,e:
    print u'沒有module B1 %s'%e

try:
    print 'module A1: %s'%sys.modules['A1']
except Exception,e:
    print u'沒有module A1 %s'%e

import B1

class C(object):
    pass
try:
    B1.D()
except Exception,e:
    print u'B中沒有屬性D',e

#B1.py
import A1
class D(object):
    pass

'''
執行A.py
結果:
沒有module B1 'B1'
沒有module A1 'A1'
module B1: <module 'B1' from 'B1.pyc'>
module A1: <module 'A1' from 'A1.py'>
B中沒有屬性D 'module' object has no attribute 'D'
'''      

由程式結果可知,将from…import…改成import後,程式不會出錯了,但是調用B1.D()時為什麼會抛出異常呢?

問題2答案

  1. 運作A1.py,當執行到import B1語句時,因為還沒有運作過B1.py,是以sys.modules中沒有B1這個鍵.會建立一個鍵B1并指派為子產品B1對象,隻不過此時這個子產品對象是空的,裡面什麼都沒有;
  2. 然後,暫停執行A1.py的其他語句,Python 在搜尋路徑下查找B1.py,找到同目錄下的B1.py并運作,為了填充子產品B1對象中的__dict__清單.當執行到import A1時,也會檢查sys.modules中是否有名為A1的子產品,但因為A1.py還沒有讀取完,是以并沒有在sys.modules中緩存對應的資訊.然後,同樣的,Python 會建立一個鍵A1并指派為空的子產品A1對象.暫停執行B1.py并尋找、從頭執行A1.py.
  3. 這時,會再次執行到import B1語句,由于在第一步時已經在sys.modules建立了子產品B1對象,子產品B1對象已經存在了,不需要執行B1.py,是以繼續執行A1.py中的其他内容.

    注意:此時子產品B1仍為空的,裡面什麼都沒有.

  4. 當執行到B1.D()時,由于在子產品B1對象的__dict__清單找不到名為D的對象,抛出異常.

問題3 修改問題1中的程式使的能正确運作

#A2.py
from B2 import D
class C(object):
    pass

#B2.py
class D(object):
    pass
from A2 import C      

簡單說明:

A2.py->B2.py->A2.py時,D屬性已經在B2的__dict__清單中了,是以不會報錯…雖然這樣寫不會報錯,但是強烈不推薦這麼寫….

總結

當Python程式導入其他子產品時,要避免循環導入,不然總會出意向不到的問題….

注:循環導入,即A檔案導入了B,B檔案又導入了A.

重新導入子產品

如果更新了一個已經用import語句導入的子產品,内建函數reload()可以重新導入并運作更新後的子產品代碼.它需要一個子產品對象做為參數.例如: 

import foo

... some code ...

reload(foo)          # 重新導入 foo

在reload()運作之後的針對子產品的操作都會使用新導入代碼,不過reload()并不會更新使用舊子產品建立的對象,是以有可能出現新舊版本對象共存的情況。*注意* 使用C或C++編譯的子產品不能通過 reload() 函數來重新導入。記住一個原則,除非是在調試和開發過程中,否則不要使用reload()函數