python中import與包管理
概念:模塊與包
- 模塊
module
:一般是以.py
爲後綴的文件,也包括.pyo
、.pyc
、.pyd
、.so
和.dll
後綴的文件,模塊內定義了函數、類以及變量 - 包
package
:包是含有若干個模塊的文件夾,在工程項目用包管理模塊可以避免模塊名衝突
__init__.py
在Python工程項目中,如果一個文件夾下有__init__.py
文件就會認爲該文件夾是一個包package
,這樣可以方便組織工程文件,避免模塊名衝突。
__init__.py
爲空時僅用於標識當前這個文件夾是一個包package
__all__
變量指明當該包被import *
時,哪些模塊module
會被導入可以利用
__init__.py
對外提供類型、變量及接口,對用戶隱藏各個子模塊的實現細節當我們
import
一個包時,會自動加載該包對應的__init__.py
,因此如果在其中做太複雜的運算會造成不必要的開銷
sys.modules
sys.modules
維護了一個已加載module
的字典,第二次加載該module
時可以直接從字典中查找,加快執行速度。
import sys
print(sys.modules)
// 輸出:
{'random': <module 'random' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/random.pyc'>, 'subprocess': <module 'subprocess' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.pyc'>, 'sysconfig': <module 'sysconfig' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sysconfig.pyc'>, 'gc': <module 'gc' (built-in)>}
namespace
-
local namespace
:函數的命名空間,記錄函數的變量 -
global namespace
:模塊的命名空間,記錄模塊的變量(函數、類、導入的模塊、模塊級別的變量和常量) -
build-in namespace
:包含build-in function
和exceptions
,可被任意模塊訪問
import
方式影響我們使用包的方式正是namespace
作用的體現:
from foo import bar # 將模塊foo中的函數/變量bar導入到當前模塊的命名空間, 可以直接訪問bar
import foo # 導入模塊foo同時保留它自己的命名空間, 需要通過foo.bar的方式來訪問bar
模塊內部屬性
-
__doc__
:文件註釋 -
__file__
:當前文件路徑 -
__package__
:導入文件的路徑 -
__cached__
:導入文件的緩存路徑 -
__name__
:導入文件的路徑加文件名稱 -
__builtins__
:包含內置函數
python內置模塊
-
os
:提供文件和目錄等的系統級操作 -
sys
:提供對解釋器相關的操作 -
hashlib
:提供加密相關的操作,替代了md5
和sha
模塊 -
shutil
:提供文件、文件夾和壓縮包等處理模塊 -
configparser
:提供對特定配置的操作 -
logging
:提供日誌功能 -
time
和datetime
:提供時間相關操作 -
random
:提供隨機數操作 -
json
和pickle
:提供序列化操作 -
shelve
:提供簡單kv
將內存數據通過文件持久化的功能
import方式
1. 簡介
在Python中import
的常用操作爲:
import somemodule # 導入整個模塊
from somemodule import somefunction # 從模塊中導入單個函數
from somemodule import firstfunc, secondfunc, thirdfunc # 從模塊中導入多個函數
from somemodule import * # 從模塊中導入所有函數
2. 執行import的步驟
- 創建一個新的
module
對象 - 將該
module
對象插入sys.modules
- 裝載
module
的代碼 - 執行新的
module
中對應的代碼
3. import的搜索包順序
注意第三步裝載module
代碼時python解釋器需要先搜索到對應的.py
文件,搜索順序爲:
-
sys.path
:包含了當前腳本的路徑和其他查找包(系統庫、第三方庫等)的路徑,你也可以在代碼中通過sys.path.append()
動態添加搜索路徑 PYTHONPATH
- 查看默認路徑,比如Linux下爲
/usr/local/lib/python/
4. 絕對導入與相對導入
絕對導入和相對導入的概念只針對於包內模塊導入包內模塊,注意如果foo.py
和bar.py
在同一個非包(沒有__init__.py
文件)的目錄下,那麼它們之間可以互相import
,不存在絕對導入和相對導入的問題。
在Python3中建議使用絕對導入。
舉個例子:
$ tree
mypackage
├── __init__.py
├── module_bar.py
└── module_foo.py
在包mypackage
內,如果module_bar
要導入module_foo
,那麼有三種方式:
# 方法一:
import module_foo
# 方法二:
# 如果是上層文件夾寫.., 上上層文件夾寫..., 以此類推
from . import module_foo
# 方法三:
from mypackage import module_foo
import mypackage.module_foo
對於python2而言,方法一和方法二都是相對導入,效果一樣,但是前者被稱爲隱式相對導入,後者被稱爲顯式相對導入,方法三是絕對導入(會在
sys.path
中的路徑搜索)對於python3而言,方法二是相對導入,方法一和方法三都是絕對導入,官方更推薦方法三
5. 包導入
包的導入和模塊導入基本一致,只不過導入包時會執行__init__.py
。如果只是導入一個包import package
而不指名任何模塊,且包中的__init__.py
沒有其他的初始化操作,那麼包下面的模塊是無法被自動導入的。
6. 直接運行與模塊運行
以下面的項目爲例:
$ tree
.
└── mypackage
├── __init__.py
└── module_foo.py
# module_foo.py內容如下:
import sys
print(sys.path)
我們有兩種方式運行module_foo.py
:
-m
參數表示run library module as a script,即以腳本的方式執行模塊。
# 直接運行: 第一個目錄是模塊module_foo所在的
$ python3 -B mypackage/module_foo.py
['/Users/didi/Desktop/MyProject/mypackage', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/Users/didi/Library/Python/3.7/lib/python/site-packages', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages']
# 模塊運行: 第一個目錄是當前路徑
$ python3 -B -m mypackage.module_foo
['/Users/didi/Desktop/MyProject', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/Users/didi/Library/Python/3.7/lib/python/site-packages', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages']
實例:包之間模塊引用的疑難雜症
1. 項目demo
假設當前你的工程文件目錄如下(僅針對python3):
注意這裏我的文件夾下並沒有
__init__.py
,嚴格來講它們並不是包,只是將聯繫緊密的模塊放在同一個文件夾下方便工程項目管理。
.
└── src
├── bar_package
│ └── module_1.py
├── foo_package
│ ├── module_2.py
│ └── module_3.py
└── main.py
# 注意
# 1) 所有模塊都以src爲根目錄, 包括main.py(當然這只是我個人習慣)
# 2) 引入方式都是絕對引入(python3推薦使用)
"""
module_1.py: 空文件
"""
"""
module_2.py: import同個包內的module_3
"""
from foo_package import module_3 # 引用同個包的模塊
"""
module_3.py: import另一個包內的module_1
"""
from bar_package import module_1 # 跨包引用模塊
if __name__ == "__main__":
print("module_3 exec successfully!")
"""
main.py: import所有模塊
"""
from foo_package import module_3, module_2
from bar_package import module_1
上面就是通常項目文件包管理的方式,執行整個程序:
python3 -B src/main.py
2. 問題:單獨執行某個模塊
如果要單獨執行module_3.py
,這時候會報錯:
$ python3 -B src/foo_package/module_3.py
Traceback (most recent call last):
File "src/foo_package/module_3.py", line 1, in <module>
from bar_package import module_1 # 跨包引用模塊
ModuleNotFoundError: No module named 'bar_package'
回顧一下之前提到的import
查找包的路徑,我們有兩種方法可以解決這個問題。
3. 方法一:通過模塊運行的方式解決(推薦)
本質上我們是希望將module_3.py
這個模塊作爲腳本運行,所以我們可以帶上-m
參數:
$ cd src # 代碼中是以src爲根目錄的, 所以需要進入到src下
$ python3 -B -m foo_package.module_3
module_3 exec successfully!
4. 方法二:在sys.path中添加查找路徑
前面的報錯是找不到bar_package
的模塊名,因爲直接運行的話sys.path
第一個路徑就是module_3.py
的路徑,自然找不到它上層的bar_package
,我們可以通過sys.path.append(..)
將它的上層目錄也加入sys.path
,修改後的module_3.py
文件內容爲:
"""module_3.py
本質上就是將module_3.py的上級目錄加入到sys.path中, 這樣就可以找到bar_package了
"""
import os
import sys
parent_path = os.path.dirname(sys.path[0])
if parent_path not in sys.path:
sys.path.append(parent_path)
from bar_package import module_1 # 跨包引用模塊
if __name__ == "__main__":
print("module_3 exec successfully!")
需要注意的是,如果你使用的是如下這種寫法還是可能出現問題:
"""module_3.py
"""
import sys
sys.path.append("../")
from bar_package import module_1 # 跨包引用模塊
if __name__ == "__main__":
print("module_3 exec successfully!")
# 進入到module_3.py所在的目錄, 輸出正常:
$ src/foo_package
$ python3 -B module_3.py
module_3 exec successfully!
# 直接在根目錄下執行會報錯:
$ python3 -B src/foo_package/module_3.py
Traceback (most recent call last):
File "src/foo_package/module_3.py", line 3, in <module>
from bar_package import module_1 # 跨包引用模塊
ModuleNotFoundError: No module named 'bar_package'
另一種簡潔的寫法是:
import sys
import oss
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
5. 儘量不要使用相對引用
Python3不建議使用相對引用,最好遵循一定的開發規範,不要在代碼中混用絕對引用與相對引用。
Reference
[1] https://blog.csdn.net/weixin_38256474/article/details/81228492
[2] https://zhuanlan.zhihu.com/p/115350758