不恰當的 import 會導致的問題

1. 直接執行被導入模塊的代碼

在 Python 中,import 語句會被執行,也就是在導入某個模塊的的類、函數等時候,會執行該模塊,此時如果該模塊中有實例化的對象或者可以執行的函數,那麼就會執行。用一個工作中遇到的問題來解釋:

(關於業務的描述可以忽略)在執行工裝測試套時,正確填寫好需要測試的工裝IPC的信息後,發現實際執行的是默認的IP值是device_info.json文件中默認值,而不是當時需要測試的203.1.2.35。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WeCNl37A-1593916899751)(screenshot/import導致的問題0.png)]
在 pycharm 本地調試 main_tooling.py,發現是一個導包問題導致的:引入了一個類的實例化(業務:破解設備二層的模塊)。

解決過程:

首先是複製console打印日誌中的“查看update -v異常”,到Utest工程中全局查找,發現只有在破二層的地方出現,於是在這邊添加斷點:
在這裏插入圖片描述
通過debug可以回溯前面調用的地方:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LoJn7DRF-1593916899841)(screenshot/import導致的問題2.png)]
通過 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 位置的代碼就沒有問題。正常使用時要避免三種使用方法:

  1. from … import … (如果有循環導入的,考慮把這種形式的去掉)
  2. 直接執行的代碼 (避免導入直接執行的代碼)
  3. 類的繼承(避免基類的模塊去 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)

Reference


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建議重組代碼,這樣一開始就不需要遞歸導入(循環導入)。

以上這些解決方案不是互斥的。

Reference

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章