模塊(module)
什么是模塊
一個 .py文件 就是一個模塊(Module)。
在開發過程中我們不會把所有的代碼都寫在一個 .py文件 中。隨着代碼量的增大,可以按照功能將函數或者類分開存放到不同的 .py文件 中。
這樣代碼更方便管理,以及后期的維護,也便於其他程序來調用當前已經實現的功能~
在開發過程中,我們也經常引用其他模塊,例如:time,os,configparser,re 等等
在Python中模塊一般有如下3種:
1)Python內置模塊
2)第三方模塊
3)自定義模塊
模塊的導入
import 語句
導入模塊的語句如下:
import module1[, module2[,... moduleN] 或 import module1 import module2 ... import moduleN
具體使用哪一種方式根據個人習慣而定,導入模塊后,模塊中的方法或者類可以通過 模塊名.方法() 直接調用~
>>> import time >>> time.time() # time() 為 time模塊中的方法 1545832129.4365451 >>> import datetime >>> datetime.datetime.now() # datetime 為datetime模塊中的類 datetime.datetime(2018, 12, 26, 21, 49, 2, 805953)
當我們使用 import 語句導入模塊時,Python解釋器首先會去內置名稱空間中尋找,即判斷導入的模塊是不是內置模塊(例如time模塊就是Python內置模塊),然后再去 sys.path 列表中定義的路徑從前往后尋找 .py文件
如下是在個人筆記本上輸出的 sys.path列表:
# pycharm中進行輸出: ['/Users/baby/PycharmProjects/untitled/module', '/Users/baby/PycharmProjects/untitled', '/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/usr/local/lib/python3.7/site-packages', '/Applications/PyCharm.app/Contents/helpers/pycharm_matplotlib_backend'] # 在終端進行輸出: >>> sys.path ['', '/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/usr/local/lib/python3.7/site-packages']
- 可以看到 sys.path 在pycharm中的輸出和在終端的輸出略有區別,在pycharm中,pycharm會自動將當前項目的路徑添加在 sys.path 列表的最前面。所以若是在當前路徑下存在與要引入模塊同名的文件,就會把要引入的模塊屏蔽掉~
sys.path 列表中其中一個路徑下的文件 如下:➜ ~ ls /usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7 LICENSE.txt fileinput.py re.py __future__.py fnmatch.py reprlib.py __phello__.foo.py formatter.py rlcompleter.py ... ...
import 的過程
現在我自己編輯了一個模塊 sftp,內容如下:
server_ip = '192.168.0.30' def get_file(): print('ddownload file ...')
然后在 main.py文件(與sftp.py在同一個路徑下) 中進行導入:
import sftp
在 import sftp 時,Python解釋器會首先創建一個新的名稱空間,這個名稱空間用於存放 sftp 模塊中定義的名字,然后在該名稱空間中執行 sftp.py 文件。
例如現在在 sftp 模塊中添加 print 語句,然后執行 main.py文件:
# sftp.py server_ip = '192.168.0.30' def get_file(): print('ddownload file ...') print('hello ....') # main.py import sftp # 執行 main.py 后會有如下輸出: hello ....
import語句 可以理解為定義了一個變量,而該變量就指向對應的名稱空間,通過使用這個變量來引用該名稱空間中的方法及變量~
import sftp 之后,注意區分新創建的名稱空間和當前的名稱空間,示例如下:
# sftp.py server_ip = '192.168.0.30' def get_file(): print('ddownload file ...') # main.py import sftp server_ip = '1.2.3.4' print(server_ip) print(sftp.server_ip) # 執行 main.py 后會有如下輸出: 1.2.3.4 192.168.0.30
- 注意,sftp.py 中的 server_ip 和 main.py文件中的 server_ip 位於不同的名稱空間下,所以名稱相同不會沖突;當然調用的方式也不同,當前文件中的 server_ip 可以直接引用,sftp模塊中的 server_ip 需要使用 模塊名.變量名 的方式來引用(sftp.server_ip)
還有一點需要注意,若是一個模塊在當前文件中被導入多次,那么模塊中的代碼只會被執行一次,而不會多次執行~。那是因為第一次導入,模塊中的方法和變量已經加載到內存中,之后的導入不會重復加載~
重命名模塊
在導入模塊的時候還可以對模塊進行重命名,以方便使用;若是當前文件中存在同名的方法或變量,也可以通過這種方式避免沖突~
import datetime as date date.datetime.now()
from import
from import 語法如下:
from modname import name1[, name2[, ... nameN]]
import語句的導入會新建一個名稱空間,將模塊中的名稱存放在該名稱空間中,而 'from modname import name1, name2' 則會將name1 和 name2 單個導入到當前的名稱空間中。既然是導入到當前的名稱空間中,那就可以直接拿來使用,前面不需要再添加模塊名稱。
from datetime import datetime print(datetime.now()) # 不需要寫成 datetime.datetime.now()
若是 from … import 導致了名稱重讀,則哪一個后定義,就使用哪一個
def foo(): pass from demo import foo # 這里引用 foo 函數,會使用 demo 模塊中的 foo函數 ############# from demo import foo def foo(): pass # 這里引用 foo 函數,會使用 當前文件中的 foo函數
包(package)
簡單而言,Python中的包(Package)就是一個目錄,里面存放了 .py文件,外加一個 __init__.py。通過目錄的方式來組織眾多的模塊,包就是用來管理和分類模塊的。引入包之后,還有一個好處就是 同名的模塊可以放在不同的包下,以避免名稱沖突~
例如現在有如下3個包,ROOT,pk_1,pk_2:
模塊m1的全名是:ROOT.pk_1.m1;模塊m2的全名則是:ROOT.pk_2.m2 ~
init.py 文件的作用
在每一個包目錄下,都應該有一個 __init__.py 文件,若這個文件不存在,那么這個目錄只是一個目錄而不是一個包。__init__.py 文件可以是空文件,也可以有 Python 代碼,原則是盡量保持 __init__.py 文件的精簡~
導入包的語句如下:
import package # 或引入包下的某一個模塊 from package import module
import package 或者 from package import module 都會執行package 包下的 __init__ 文件
現在有如下目錄結構:
├─ROOT │ ├─pk_1 │ │ ├─__init__.py │ │ ├─m1.py │ ├─pk_2 │ │ ├─__init__.py │ │ └─m2.py │ ├─__init__.py │ ├─test.py
pk_1 和 pk_2 包中的 __init__.py 文件都為空,ROOT包下的 test.py 想要使用 pk_1 包下 m1模塊中的方法,可以使用如下語句:
from pk_1 import m1 m1.fun_1() # fun_1() 為m1模塊中的方法
但是使用如下語句,就會拋出異常:
from pk_1 import * m1.fun_1() # 異常信息: NameError: name 'm1' is not defined ############################## import pk_1 pk_1.m1.fun_1() # 異常信息: AttributeError: module 'pk_1' has no attribute 'm1'
這時候可以在 pk_1 包中的__init__.py 中 進行 包提升(在包中提升導入權限),pk_1 包的 __init__.py 文件內容如下:
from pk_1.m1 import fun_1
然后在 test.py 文件中可以直接通過包名引入方法:
# 1) from pk_1 import fun_1 # 或 from pk_1 import * fun_1() # 2) import pk_1 pk_1.fun_1()
這個就是 包中 __init__.py 文件存在的意義,可以將相關的導入語句 或 提升導入權限的語句 寫在 __init__.py文件中,這樣使用者就不需要了解包中的內部結構,可以直接通過包名 調用該包(package)中某個模塊的方法~
還可以在 包中 __init__.py 文件中使用 __all__ 列出需要導入的模塊,例如在 pk_1 包中的 __init__.py文件中添加 __all__ 變量:
__all__ = ['m2']
然后在 test.py 文件中就可以使用 from pk_2 import * 一次性導入 __all__變量中列出的模塊:
from pk_2 import * m2.fun_2()
若是 pk_2 包的 __init__.py 文件已經對 fun_2 方法做了提升:
# pk_2 包的 \_\_init\_\_.py 內容 from pk_2.m2 import fun_2
這樣在 test.py 中 import * 后可直接使用該方法:
from pk_2 import * fun_2()
注意:當 __init__.py 中定義了 __all__ 變量時,import * 只會導入 __all__中列出的模塊
包中的模塊調用
現在有如下目錄結構:
├─log │ ├─util │ │ ├─__init__.py │ │ ├─a.py │ │ ├─b.py │ ├─__init__.py │ ├─test.py
在test中引入 a模塊:
from util import a
在 a 模塊中又引入了 b 模塊:
import b
這樣的話在執行 test 文件時就會報錯,ModuleNotFoundError: No module named 'b',說無法找到b模塊。
這是因為 在執行 test 時,sys.path 中的路徑不包含 util 下的路徑,所以在 a.py 文件中 import b 模塊時就會報錯(若是直接執行的是 a.py 文件就不會有問題)。在 a模塊 中引入 b 模塊的正確的寫法是:
from util import b
當然這個時候 a.py 文件就不能再單獨運行了,運行時就會報錯
Tip:在 a.py 文件中 使用 "from util import b" 導入模塊 b,這個時候若是直接執行 a.py 文件就會報錯,因為 a.py 文件本身就位於util路徑下,sys.path(執行 a.py 時的sys.path)中有util路徑,但是 'from util' 是找不到util的,util 位於 sys.path 的某個路徑下時,'from util' 才能找到util ~
若是現在 主執行文件 本身就位於項目目錄下的某個包中,要引入其他包中的模塊,就需要通過在 os.path 中添加路徑來實現:
現在執行文件是 bin 目錄下的 bin.py,在 bin.py 中要導入 util包 中的 a模塊 和 b模塊,為了保證通用性,可以使用如下方式獲取 log 路徑,並且添加到os.path中:
import sys, os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from util import a
__name__變量
__name__ 與 __file__一樣,是一個內置變量,這個變量記錄了當前文件(使用 __name__ 變量的文件)是作為模塊運行還是主執行文件~
示例:
a.py 文件內容
print(__name__) # 直接執行 a.py 文件,輸出: # __main__
現在 在b.py文件中 import a。b.py 文件內容如下:
import a # 現在運行 b.py 文件(這個過程會運行 a.py 文件),輸出內容: # a ## 即 a 的模塊名稱
這個功能經常被用於代碼的調試:
if __name__=='__main__': pass
可以將調試的代碼寫在 if 語句中,用於調試當前py文件中的代碼,因為直接運行當前文件, __name__ 變量的值就是 __main__。當外部模塊調用的時候,就不會執行 if 語句中的內容,因為 外部模塊調用 __name__ 變量的值 為模塊名稱~