1. 程式人生 > >python基礎--自定義模組、import、from......import......

python基礎--自定義模組、import、from......import......

## 自定義模組、import、from......import...... ### 1)模組的定義和分類 #### 1.模組是什麼? 我們知道一個函式封裝了一個功能,軟體可能是有多個函式組成的。我們說一個函式就是一個功能,那麼把一些常用的函式放在一個py檔案中,那麼這個檔案就稱之為模組。模組就是一些列常用功能的集合體。 什麼是模組:本質就是.py檔案,封裝語句的最小單位。 #### 2.為什麼要使用模組 1. 從檔案級別組織程式,更方便管理 隨著程式的發展,功能越來越多,為了方便管理,我們通常將程式分成一個個的檔案,這樣做程式的結構更清晰,方便管理。這時我們不僅僅可以把這些檔案當做指令碼去執行,還可以把他們當做模組來匯入到其他的模組中,實現了功能的重複利用 2. 拿來主義,提升開發效率 同樣的原理,我們也可以下載別人寫好的模組然後匯入到自己的專案中使用,這種拿來主義,可以極大地提升我們的開發效率,避免重複造輪子。 **ps:人們常說的指令碼是什麼?** 如果你退出python直譯器然後重新進入,那麼你之前定義的函式或者變數都將丟失,因此我們通常將程式寫到檔案中以便永久儲存下來,需要時就通過python test.py方式去執行,此時test.py被稱為指令碼script。 所以,指令碼就是一個python檔案,比如你之前寫的購物車,模擬部落格園登入系統的檔案等等。 #### 3.模組的分類 Python語言中,模組分為三類。 第一類:內建模組,也叫做標準庫。此類模組就是python直譯器給你提供的,比如我們之前見過的time模組,os模組。標準庫的模組非常多(200多個,每個模組又有很多功能),我們這幾天就講常用的十幾種,後面課程中還會陸續的講到。 第二類:第三方模組,第三方庫。一些python大神寫的非常好用的模組,必須通過pip install 指令安裝的模組,比如BeautfulSoup, Django,等等。大概有6000多個。 第三類:自定義模組。我們自己在專案中定義的一些模組。 我們先定義一個模組,定義一個模組其實很簡單就是寫一個檔案,裡面寫一些程式碼(變數,函式)即可。此檔案的名字為tbjx.py,檔案內容如下: ```python print('from the tbjx.py') name = '太白金星' def read1(): print('tbjx模組:',name) def read2(): print('tbjx模組') read1() def change(): global name name = 'barry' ``` ### 2)自定義模組 自定義模組:定義一個模組其實很簡單就是寫一個檔案,裡面寫一些程式碼(變數,函式)即可。此檔案的名字為tbjx.py,檔案內容如下: 模組中出現的變數,for迴圈,if結構,函式定義。。。。稱為模組的成員。 自定義模組:實際上就是定義.py檔案,其中可以包含:變數定義,可執行語句,for迴圈,函式定義等等,他們 統稱模組的成員 模組的執行方式: 1.指令碼方式:python xxx.py(直接用直譯器執行) 或者在pycharm軟體run執行(右鍵執行) 2.模組方式:被其它的模組匯入。為匯入它的模組提供資源(變數,函式定義,類定義等)。 ```python # b.py檔案 # 可執行語句 a = 1 print(a) for x in range(10): print(x) # 函式的定義 def f(): print('hello world') f() ``` 自定義模組被其他模組匯入時,其中的可執行語句會立即執行。 但是函式的定義和類的定義等是不會立即執行的。 但是我們在實際的開發的過程中,不是我們import模組就執行了。 而是在實際開發的時候用到什麼就執行什麼。 所以在模組中,我們一般不會寫可執行語句,而是寫變數的定義,函式定義和類定義等不會立即執行的語句。 我們在實際的時候,不能直接可執行語句,只有變數、函式、類定義等等。被其它的模組匯入。為匯入它的模組提供資源(變數,函式定義,類定義等)。 ```python # test_import.py檔案 import b # 輸出的結果為: ''' 1 0 1 2 3 4 5 6 7 8 9 hello world ''' # import bb # ModuleNotFoundError: No module named 'bb' ``` 我們再看一下下面這個例子,我們沒有寫可執行語句,而是變數、函式、類等的定義,不會再import時,就直接執行。 ```python # b.py檔案 # 可執行語句 a = 1 # 函式的定義 def f(): print('hello world') ``` ```python import b print(b.a) b.f() print(b.f()) ''' 輸出的結果為: 1 hello world hello world None ''' ``` python中提供一種可以判斷自定義模組是屬於開發階段還是使用階段。'__name__' 系統匯入模組的路徑 1.記憶體中:如果之前成功匯入過某個模組,直接使用已經存在的模組 2.內建路徑中:安全路徑下:Lib PYTHONPATH:import是尋找模組的路徑 3.sys.path:是一個路徑的列表 如果三個都找不到,就報錯。 動態修改sys.path os.path.dirname():獲取到某一個檔案的父路徑。 通常獲取當前指令碼(模組)的相對位置,可以獲取到每一個檔案。 ```python # 檢視sys.path內容 # import sys # print(sys.path) # 新增b.py所在的路徑到sys.path中 # import sys # sys.path.append(r'D:\Program Files (x86)\DjangoProjects\basic\day15\bbb') # import bb # print(bb.a) # 輸出的結果為:get it # # 使用相對位置找到bbb資料夾中的bb # print(__file__) # 獲取當前檔案的絕對路徑;D:/Program Files (x86)/DjangoProjects/basic/day15/test_imoirt.py # # 使用os模組獲取一個路徑的父路徑 # import os # print(os.path.dirname(__file__)) # 獲取當前檔案的父路徑 D:/Program Files (x86)/DjangoProjects/basic/day15 # print(os.path.dirname(__file__)+r'/bbb') # D:/Program Files (x86)/DjangoProjects/basic/day15/bbb import sys import os sys.path.append(os.path.dirname(__file__)+'/bbb') ``` ```python a = 1 def main(): print(a) for x in range(3): print(x) f() # __name__屬性的使用。 if __name__ == '__main__': main() ''' 輸出的結果為: 1 0 1 2 hello world ''' ''' __name__屬性的使用: 在指令碼方式執行的時候:__name__是固定的字串:__main__ 在模組匯入執行的時候,__name__就是被匯入模組的名字,沒有檔案的字尾名.py。 在模組方式匯入時,__name__就是本模組的名字。 通過__name__屬性,我們就可以決定可執行檔案中的語句該不該被執行。 ''' ``` ```python ''' 自定義模組 ''' # age = 10 # # # def f1(): # print('hello') # # # # 測試函式,在開發階段,對本模組中的功能進行測試。 # # 這個測試函式一般我們是寫成main函式的形式。 # def main(): # print(age) # f1() # # # # 可以快速生成。 # if __name__ == '__main__': # main() ''' 對於一個新的py檔案或者是一個新的模組時,我們一上來要寫下面兩個東西. 然後根據自己的需求,去寫這個模組對應的別的東西。例如變數的定義,函式的定義等等 ''' def main(): pass if __name__ == '__main__': main() ``` Python中引用模組是按照一定的規則以及順序去尋找的,這個查詢順序為:先從記憶體中已經載入的模組進行尋找找不到再從內建模組中尋找,內建模組如果也沒有,最後去sys.path中路徑包含的模組中尋找。它只會按照這個順序從這些指定的地方去尋找,如果最終都沒有找到,那麼就會報錯。 **記憶體中已經載入的模組->內建模組->sys.path路徑中包含的模組** **模組的查詢順序** 1. 在第一次匯入某個模組時(比如tbjx),會先檢查該模組是否已經被載入到記憶體中(當前執行檔案的名稱空間對應的記憶體),如果有則直接引用(ps:python直譯器在啟動時會自動載入一些模組到記憶體中,可以使用sys.modules檢視) 2. 如果沒有,直譯器則會查詢同名的內建模組 3. 如果還沒有找到就從sys.path給出的目錄列表中依次尋找tbjx.py檔案。 **需要特別注意的是:我們自定義的模組名不應該與系統內建模組重名。雖然每次都說,但是仍然會有人不停的犯錯** ```python #在初始化後,python程式可以修改sys.path,路徑放到前面的優先於標準庫被載入。 > > > import sys > > > sys.path.append('/a/b/c/d') > > > sys.path.insert(0,'/x/y/z') #排在前的目錄,優先被搜尋 > > > 注意:搜尋時按照sys.path中從左到右的順序查詢,位於前的優先被查詢,sys.path中還可能包含.zip歸檔檔案和.egg檔案,python會把.zip歸檔檔案當成一個目錄去處理, #首先製作歸檔檔案:zip module.zip foo.py bar.py import sys sys.path.append('module.zip') import foo,bar #也可以使用zip中目錄結構的具體位置 sys.path.append('module.zip/lib/python') #windows下的路徑不加r開頭,會語法錯誤 sys.path.insert(0,r'C:\Users\Administrator\PycharmProjects\a') #至於.egg檔案是由setuptools建立的包,這是按照第三方python庫和擴充套件時使用的一種常見格式,.egg檔案實際上只是添加了額外元資料(如版本號,依賴項等)的.zip檔案。 #需要強調的一點是:只能從.zip檔案中匯入.py,.pyc等檔案。使用C編寫的共享庫和擴充套件塊無法直接從.zip檔案中載入(此時setuptools等打包系統有時能提供一種規避方法),且從.zip中載入檔案不會建立.pyc或者.pyo檔案,因此一定要事先建立他們,來避免載入模組是效能下降。 接下來我們就開始講解python常用的內建模組,由於Python常用的模組非常多,我們不可能將所有的模組都講完, 所以只針對於工作中經常用到模組進行講解。剩下的模組可以在課餘時間自學。 ``` ### 3)import使用 #### 1.import使用 import 翻譯過來是一個匯入的意思。 這裡一定要給同學強調那個檔案執行檔案,和哪個檔案是被執行模組。 模組可以包含可執行的語句和函式的定義,這些語句的目的是初始化模組,它們只在模組名第一次遇到匯入import語句時才執行(import語句是可以在程式中的任意位置使用的,且針對同一個模組很import多次,為了防止你重複匯入,python的優化手段是:第一次匯入後就將模組名載入到記憶體了,後續的import語句僅是對已經載入到記憶體中的模組物件增加了一次引用,不會重新執行模組內的語句),如下 import tbjx #只在第一次匯入時才執行tbjx.py內程式碼,此處的顯式效果是隻列印一次'from the tbjx.py',當然其他的頂級程式碼也都被執行了,只不過沒有顯示效果. ![](https://img2020.cnblogs.com/blog/2062851/202007/2062851-20200702185251891-1039740772.png) ```python 程式碼示例: import tbjx import tbjx import tbjx import tbjx import tbjx 執行結果:只是列印一次: from the tbjx.py ``` #### 2.第一次匯入模組執行三件事 - 建立一個以模組名命名的名稱空間。 - 執行這個名稱空間(即匯入的模組)裡面的程式碼。 - 通過此模組名. 的方式引用該模組裡面的內容(變數,函式名,類名等)。 這個名字和變數名沒什麼區別,都是‘第一類的’,且使用tbjx.名字的方式可以訪問tbjx.py檔案中定義的名字,tbjx.名字與test.py中的名字來自兩個完全不同的地方。 ps:**重複匯入會直接引用記憶體中已經載入好的結果** #### 3. 被匯入模組有獨立的名稱空間 每個模組都是一個獨立的名稱空間,定義在這個模組中的函式,把這個模組的名稱空間當做全域性名稱空間,這樣我們在編寫自己的模組時,就不用擔心我們定義在自己模組中全域性變數會在被匯入時,與使用者的全域性變數衝突。 示例: ![](https://img2020.cnblogs.com/blog/2062851/202007/2062851-20200702185251891-1039740772.png) ```python 當前是meet.py import tbjx.py name = 'alex' print(name) print(tbjx.name) ''' from the tbjx.py alex 太白金星 ''' def read1(): print(666) tbjx.read1() ''' from the tbjx.py tbjx模組: 太白金星 ''' name = '日天' tbjx.change() print(name) print(tbjx.name) ''' from the tbjx.py 日天 barry ''' ``` #### 4.為模組起別名 **1. 好處可以將很長的模組名改成很短,方便使用.** ```python import tbjx as t t.read1() from xxx import xxx as xxx ``` ​ **2. 有利於程式碼的擴充套件和優化。** ```python #mysql.py def sqlparse(): print('from mysql sqlparse') #oracle.py def sqlparse(): print('from oracle sqlparse') #test.py db_type=input('>>: ') if db_type == 'mysql': import mysql as db elif db_type == 'oracle': import oracle as db db.sqlparse() ``` #### 5.匯入多個模組 我們以後再開發過程中,免不了會在一個檔案中,匯入多個模組,推薦寫法是一個一個匯入。 ```python import os,sys,json # 這樣寫可以但是不推薦 推薦寫法 import os import sys import json ```   **多行匯入:易於閱讀 易於編輯 易於搜尋 易於維護。** ### 4)from......import...... #### 1.from......import......使用 ```python from ... import ... 的使用示例。 from tbjx import name, read1 print(name) read1() ''' 執行結果: from the tbjx.py 太白金星 tbjx模組: 太白金星 ''' ``` #### 2.from...import... 與import對比 唯一的區別就是:使用from...import...則是將spam中的名字直接匯入到當前的名稱空間中,所以在當前名稱空間中,直接使用名字就可以了、無需加字首:tbjx. from...import...的方式有好處也有壞處 好處:使用起來方便了 壞處:容易與當前執行檔案中的名字衝突 示例演示: 1. **執行檔案有與模組同名的變數或者函式名,會有覆蓋效果。** ```python name = 'oldboy' from tbjx import name, read1, read2 print(name) ''' 執行結果: 太白金星 ''' ---------------------------------------- from tbjx import name, read1, read2 name = 'oldboy' print(name) ''' 執行結果: oldboy ''' ---------------------------------------- def read1(): print(666) from tbjx import name, read1, read2 read1() ''' 執行結果: tbjx模組: 太白金星 ''' ---------------------------------------- from tbjx import name, read1, read2 def read1(): print(666) read1() ''' 執行結果: tbjx模組: 666 ''' ```  **2. 當前位置直接使用read1和read2就好了,執行時,仍然以tbjx.py檔案全域性名稱空間** ```python #測試一:匯入的函式read1,執行時仍然回到tbjx.py中尋找全域性變數 'alex' #test.py from tbjx import read1 name = 'alex' read1() ''' 執行結果: from the spam.py spam->read1->name = '太白金星' ''' #測試二:匯入的函式read2,執行時需要呼叫read1(),仍然回到tbjx.py中找read1() #test.py from tbjx import read2 def read1(): print('==========') read2() ''' 執行結果: from the tbjx.py tbjx->read2 calling read tbjx->read1->tbjx 'barry' ''' 通過這種方式引用模組也可以對模組進行改名。 from tbjx import read1 as read read() ``` #### 3.一行匯入多個 ```python from tbjx import read1,read2,name ``` #### 4.from......import* from spam import * 把tbjx中所有的不是以下劃線(_)開頭的名字都匯入到當前位置 大部分情況下我們的python程式不應該使用這種匯入方式,因為*你不知道你匯入什麼名字,很有可能會覆蓋掉你之前已經定義的名字。而且可讀性極其的差,在互動式環境中匯入時沒有問題。 可以使用**all**來控制*(用來發布新版本),在tbjx.py中新增一行 ```python __all__=['money','read1'] #這樣在另外一個檔案中用from spam import *就這能匯入列表中規定的兩個名字 ``` #### 5.模組迴圈匯入問題 模組迴圈/巢狀匯入丟擲異常的根本原因是由於在python中模組被匯入一次之後,就不會重新匯入,只會在第一次匯入時執行模組內程式碼 在我們的專案中應該儘量避免出現迴圈/巢狀匯入,如果出現多個模組都需要共享的資料,可以將共享的資料集中存放到某一個地方在程式出現了迴圈/巢狀匯入後的異常分析、解決方法如下(**瞭解,以後儘量避免**) 示範檔案內容如下 ```python #建立一個m1.py print('正在匯入m1') from m2 import y x='m1' #建立一個m2.py print('正在匯入m2') from m1 import x y='m2' #建立一個run.py import m1 #測試一 執行run.py會丟擲異常 正在匯入m1 正在匯入m2 Traceback (most recent call last): File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/aa.py", line 1, in import m1 File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in from m2 import y File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in from m1 import x ImportError: cannot import name 'x' #測試一結果分析 先執行run.py--->執行import m1,開始匯入m1並執行其內部程式碼--->列印內容"正在匯入m1" --->執行from m2 import y 開始匯入m2並執行其內部程式碼--->列印內容“正在匯入m2”--->執行from m1 import x,由於m1已經被匯入過了,所以不會重新匯入,所以直接去m1中拿x,然而x此時並沒有存在於m1中,所以報錯 #測試二:執行檔案不等於匯入檔案,比如執行m1.py不等於匯入了m1 直接執行m1.py丟擲異常 正在匯入m1 正在匯入m2 正在匯入m1 Traceback (most recent call last): File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in from m2 import y File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in from m1 import x File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in from m2 import y ImportError: cannot import name 'y' #測試二分析 執行m1.py,列印“正在匯入m1”,執行from m2 import y ,匯入m2進而執行m2.py內部程式碼--->列印"正在匯入m2",執行from m1 import x,此時m1是第一次被匯入,執行m1.py並不等於匯入了m1,於是開始匯入m1並執行其內部程式碼--->列印"正在匯入m1",執行from m1 import y,由於m1已經被匯入過了,所以無需繼續匯入而直接問m2要y,然而y此時並沒有存在於m2中所以報錯 # 解決方法: 方法一:匯入語句放到最後 #m1.py print('正在匯入m1') x='m1' from m2 import y #m2.py print('正在匯入m2') y='m2' from m1 import x 方法二:匯入語句放到函式中 #m1.py print('正在匯入m1') def f1(): from m2 import y print(x,y) x = 'm1' # f1() #m2.py print('正在匯入m2') def f2(): from m1 import x print(x,y) y = 'm2' #run.py import m1 m1.f1() ``` ### 5)import和from......import......總結 ```python # 匯入模組的多種方式: # import xxx匯入一個模組的所有成員 # import aaa,bbb,....一次性匯入多個模組的成員,不推薦這種寫法,分開寫比較好。因人而異。import os,sys等 # from xxx import aaa.. 從某個模組中匯入指定的成員。最大化利用。有用就匯入,沒有使用我們就不用去匯入。 # from xxx import a,b,c 從某個模組中匯入多個成員。 # from xxx import * 從某個模組彙總匯入所有成員。 # import xxx 和 from xxx import * # 第一種方式在使用其中成員時,必須使用模組名作為前提。不容易產生命名衝突 # 第二種方式在使用其中成員時,不用使用模組名作為前提,直接使用成員名即可。容易產生命名衝突,在後面定義的成員生效,把前面的覆蓋了。 # 怎麼解決名稱衝突的問題 # 改用import xxx 這種方式匯入 # 自己避免使用同名(alias的縮寫) # 使用別名解決衝突 from xxx import xxx as xxx # # 也可以給模組起別名 import my_module as m import xxx as xxx,為了方便簡化書寫。 # from xxx import * 控制成員被匯入(__all__只是適合控制這一種匯入成員的方式,其餘方式都是不可以用的) # 預設情況下,所有的成員都會被匯入 # __all__是一個列表,用於表示本模組可被外界使用的成員。元素是成員名組成的字串。 # __all__ = [] # __all__ = [ # 'age', # 'age2' # ] # 相對匯入:相對匯入時匯入的是同項目下的模組。 # 只有一種的匯入的方式 # from xxx import xxx # import os # import sys # # 把專案所在的父路徑加到sys.path中,python的直譯器中。os是作業系統相關的路徑。 # sys.path.append(os.path.dirname(__file__)) # from xx.y import yy # print(yy.age2) # # 使用相對位置找到bbb資料夾中的bb # print(__file__) # 當前檔案的絕對路徑;D:/Program Files (x86)/DjangoProjects/basic/day15/test_imoirt.py # # 使用os模組獲取一個路徑的父路徑 # import os # print(os.path.dirname(__file__)) # 獲取當前檔案的父路徑 D:/Program Files (x86)/DjangoProjects/basic/day15 # print(os.path.dirname(__file__)+r'/bbb') # D:/Program Files (x86)/DjangoProjects/basic/day15