1. 直接執行被導入模塊的代碼
在 Python 中,import 語句會被執行,也就是在導入某個模塊的的類、函數等時候,會執行該模塊,此時如果該模塊中有實例化的對象或者可以執行的函數,那麼就會執行。用一個工作中遇到的問題來解釋:
(關於業務的描述可以忽略)在執行工裝測試套時,正確填寫好需要測試的工裝IPC的信息後,發現實際執行的是默認的IP值是device_info.json文件中默認值,而不是當時需要測試的203.1.2.35。
在 pycharm 本地調試 main_tooling.py,發現是一個導包問題導致的:引入了一個類的實例化(業務:破解設備二層的模塊)。
解決過程:
首先是複製console打印日誌中的“查看update -v異常”,到Utest工程中全局查找,發現只有在破二層的地方出現,於是在這邊添加斷點:
通過debug可以回溯前面調用的地方:
通過 git 歷史提交記錄發現是同事處理某個問題時引入:
通過回溯回顧,發現:
在最初執行的 main_tooling.py
中調用 liveoperator
模塊中的類 :
from src.components.liveoperator import LiveOperator
在 liveoperator.py
中調用 videoaction
模塊中的類 :
from src.components.videoaction import VideoActionLapi
在 videoaction.py
中調用 video_parameters
模塊中的類:
from src.parameters.video_parameters import videoparameters
在 video_parameters.py
中實例化了 VideoParameters
類:
videoparameters = VideoParameters()
在實例化過程中,就出現了問題的原因所在,拋開業務來說就是在不合適的時機,實例化了我不想實例化的類,我還沒做 xxx 呢,你現在就給我實例化,我不能讓你這麼做。
解決辦法:
可以在將 main_tooling.py
中導入liveoperator 的代碼放入需要執行的地方,而不是放在文件的首部,這樣可以等到我做了某一業務操作後再執行導入就沒有問題了,也就是一個先後問題。
2. import 循環
Python 是可以循環引用的,只要循環引用中的模塊並不是在定義階段就馬上使用:
# module1.py
import module2
class ModuleDemo():
def module2_func(self):
print(module2.module2_func)
# module2.py
import module1
def module2_func():
print(module1.ModuleDemo)
由於示例中只有在函數內部使用,只要 import 階段沒有執行到相應的用到 import 位置的代碼就沒有問題。正常使用時要避免三種使用方法:
- from … import … (如果有循環導入的,考慮把這種形式的去掉)
- 直接執行的代碼 (避免導入直接執行的代碼)
- 類的繼承(避免基類的模塊去 import 派生類的模塊)
還有其他方法:用到時再導入,而不是放在模塊頂部。比如將 import 放到函數裏面,可以解決問題,但治標不治本,治本的還是要重新劃分模塊,邏輯理順了就不會出現循環 import 。
錯誤示範,出現 ImportError:
# module1.py
from module2 import module2_func
class ModuleDemo():
def module2_func(self):
print(module2_func)
# module2.py
from module1 import ModuleDemo
def module2_func():
print(ModuleDemo)
3. 如何擁有導入的模塊
假設有以下模塊:
foo.py:
from bar import bar_var
foo_var = 1
bar.py:
from foo import foo_var
bar_var = 2
問題在於解釋器將執行以下步驟:
- python 的 main 主函數 import 導入 foo(執行 foo 模塊)
- 爲 foo 創建空的全局變量
- foo 被編譯並開始執行
- foo 導入 bar (即 from bar import bar_var)
- 創建 bar 的空全局變量
- bar 被編譯並開始執行
- 在 bar 模塊中,導入foo(因爲前面已經有一個名爲foo的模塊,所以它是空操作)
- bar.foo_var = foo.foo_var
最後一步失敗了,因爲 Python 尚未完成解釋foo
,並且的全局符號字典foo
仍然爲空。
同樣的事情會發生:使用 import foo
,然後在全局代碼中訪問 foo.foo_var
。
foo.py:
import bar
foo_var = 1
bar.py:
import foo
bar_var = 2
print(foo.foo_var)
執行 bar.py 會出現 AttributeError: module 'foo' has no attribute 'foo_var'
。
Guido van Rossum 建議避免使用 from <module> import ...
的所有用法,並將所有代碼放在函數中。全局變量和類變量的初始化應僅使用常量或內置函數。這意味着來自導入模塊的所有內容都被引用爲<module>.<name>
。
Jim Roskind建議在每個模塊中按以下順序執行步驟:
- 導出(不需要導入基類的全局變量,函數和類)
import
語句- 激活代碼(包括從導入值初始化的全局變量)。
van Rossum 不太喜歡這種方法,因爲這種導入語句會出現在一個奇怪的地方,但是也確實可行。
Matthias Urlichs建議重組代碼,這樣一開始就不需要遞歸導入(循環導入)。
以上這些解決方案不是互斥的。