第17天模組詳解
什麼是模組
模組就是一組功能的集合。(每次建立的一個python檔案就是一個模組),那麼函式是什麼呢?函式是一個功能的實現,也就是說模組更像是一堆函式的封裝。
為什麼要用模組
1. 從檔案級別組織程式,更方便管理
之前我們寫的一個程式大都是存在一個檔案中的,包含著各式各樣的函式功能。為了更好的管理,我們可以把不同的功能集合統一的寫到一個檔案中,當我們要使用的時候,載入一下模組進行使用就可以了。例如我們之前寫的購物城程式,無論是atm操作過程還是管理員的操作過程我們都是寫在了一個檔案中,檢視以及維護起來相當的麻煩,因此我們可以建立兩個檔案,一個用來專門的放atm的操作,一個用來專門的放管理員的操作,這樣就可以很方便的管理和維護了。
2. 提高開發效率
我們都知道對於一些常用的功能,我們一般都會把它寫成一個函式以便後續不斷的呼叫,其實模組也是這個原理,只是模組是常用的一類函式的集合,也是方便我們之後呼叫的。例如,我們之後遇到一個專案,發現專案中的很多功能之前已經有大神把它寫成了一個模組了,這樣我們就可以不用再去自己造輪子,可以直接使用這個模組,這樣可以大大的提高我們的開發效率。
模組的分類
# 模組的分類 ''' 1. 自定義模組 2. 用c語言寫成的連線到python直譯器的內建模組 3. 包(把一系列模組組織到一起的資料夾) 4. 已被編譯為共享庫或DLL的C或C++擴充套件 '''
模組之import的使用
#import的使用 ''' 1. import 模組名 2. import 很長的一個模組名 as 小名 3. import 模組名1, 模組名2, 模組名3 '''
例一:import 模組名
當我們執行了import m1之後發生的三個步驟:
# 這是一個m2檔案 import m1 # 當執行到這個import的時候會做以下幾件事 ''' 1. 執行m2.py檔案的時候會建立一個屬於m2.py的名稱空間 2. 然後去執行模組也就是m1.py裡面的內容,首先開啟m1.py,再建立一個m1.py的名稱空間 用來存放m1.py中的變數名和記憶體地址的空間。 3. 將m1作為一個變數名加入到m2.py的名稱空間內,指向m1.py的名稱空間'''
從這三個步驟我們就可以看出來在匯入模組的時候並不是說把匯入模組的名稱空間照搬過來,而是在執行檔案內建立了一個變數指向了模組的名稱空間。
例子:建立兩個檔案一個spam.py 一個是test檔案
#spam.py print('from the spam.py') money=1000 def read1(): print('spam模組:',money) def read2(): print('spam模組') read1() def change(): global money money=0spam檔案的內容
測試: 只要是通過import匯入的模組,名稱空間是很明瞭的,通過【spam.money】這種方式呼叫的用的都是模組的名稱空間,而沒有用模組呼叫的用的名稱空間都是當前執行檔案的。
# 定義一個變數 money = 0 # 匯入spam模組,此時會執行spam,因此結果會首先列印 # from the spam.py import spam # 列印money,會去找test.py的名稱空間 print(money) # 可以理解為列印spam內的money print(spam.money) # 結果: # from the spam.py # 0 # 1000變數的測試
# 函式的測試 def read1(): print('這是test檔案') # 匯入spam模組,此時會執行spam,因此結果會首先列印 # from the spam.py import spam # 會去找test.py的名稱空間並且執行函式 # 列印這是test檔案 read1() # 可以理解為執行spam內的read1函式 spam.read1() # 結果: # from the spam.py # 這是test檔案 # spam模組! 1000函式的測試 read1
# 匯入spam模組,此時會執行spam,因此結果會首先列印 # from the spam.py import spam # 當執行read2的時候會去呼叫read1,此時的read1仍然是spam的名稱空間的 spam.read2()函式的測試 read2
money = 100 # 匯入spam模組,此時會執行spam,因此結果會首先列印 # from the spam.py import spam # 執行change的時候有一個修改全域性變數的操作, # 它的名稱空間用的也是spam的 spam.change() # 整個過程不會改變test名稱空間的值所以列印100 print(money) # money已經在change裡面得到了改變,因此列印的為0 print(spam.money) # 結果: # from the spam.py # 100 # 0函式的測試 change
例二:import 很長的一個模組名 as 小名
# 這種方式的特點 1. 簡潔 2. 可以通過別名的形式進行選擇不同的操作
測試: 建立兩個檔案,mysql.py , oracle.py
# oracle.py檔案的內容 def sqlparse(): print('from oracle sqlparse') # mysql.py檔案的內容 def sqlparse(): print('from mysql sqlparse') # 使用者選擇是用mysql資料庫還是oracle資料庫 choice = input('>>:').strip() if choice == 'mysql': import mysql as db elif choice == 'oracle': import oracle as db else: print('Wrong!') # 根據使用者的資料選擇使用不同的資料庫 db.sqlparse()
例三: import 模組名1, 模組名2, 模組名3
# 使程式更加簡潔 import m1, m2, mysql
模組之from xxx import xx 的使用
使用方法:
from spam import money
from和import的比較
# 有了import為什麼還要用from ''' from和import的區別在於: 1. import不會把匯入模組內變數新增到執行檔案的名稱空間內, 2. 但是from是把匯入的模組直接在自己的名稱空間內添加了一份和模組內一摸一樣的名稱空間 from: 優點: 方便了,我們可以直接使用匯入的變數而不用想import一樣還要用spam.money 缺點: 容易和我們現在名稱空間內的變數重複了 '''
測試:
#測試一:匯入read1函式 # 首先會執行spam from spam import read1 read1() # 結果: # from the spam.py # spam模組! 1000匯入read1函式
# 測試二:匯入read2函式 # 首先會執行spam from spam import read2 def read1(): print('+++++++++') # read2內執行read1的時候和import一樣名稱空間還是在spam內 read2() # 結果: # from the spam.py # spam模組 # spam模組! 1000匯入read2函式
# 測試三: 匯入change函式 money = 100 from spam import change from spam import money # 因為匯入的money把上面定義的money給覆蓋掉了 # 因此現在money指向的是spam裡面的記憶體地址,列印1000 print(money) # change內修改的全域性變數依然是spam名稱空間內的 change() # 因為change修改了money的值,但是修改的是spam裡面的記憶體空間地址 # 但是在test裡面的記憶體地址指向的還是原來的1000的地址,並沒有發生改變, # 所以打印出來的為1000 print(money)匯入change函式
from的別名和多個匯入:
from spam import money as m from spam import money, read1, read2
模組的迴圈巢狀:
模組的迴圈巢狀是很容易出現問題的,但是歸根結底只有一點就是執行並不代表匯入。
print('這個檔案執行了!') # 匯入自己 import test # 此處是列印了兩次,因為我執行檔案雖然是加入了記憶體,但是作為模組還是沒有 # 匯入到記憶體中的,因此當執行了import之後又執行了一遍
測試: 三個檔案內容如下:
# m1.py檔案 print('正在匯入m1') from m2 import y x = 'm1' # m2.py檔案 print('匯入模組m2') from m2 import x y = 1 # test檔案 import m1當執行此檔案的時候會報錯,這個原因就是
錯誤分析:
# test檔案 # 會去執行m1.py import m1 # m1.py檔案 # 列印此內容 print('正在匯入m1') # 去執行m2檔案 from m2 import y x = 'm1' # m2.py檔案 # 列印此內容 print('匯入模組m2') # 因為在test中已經匯入了m1因此記憶體中已經有了m1模組,就不會再匯入了, # 但是m1中的x = 'm1'還沒有執行呢,所以報錯了 from m1 import x # y = 1
模組的瑣碎知識點:
''' 模組的過載: 考慮到效能的原因,每個模組只被匯入一次,放入字典sys.module中, 如果你改變了模組的內容,你必須重啟程式,python不支援重新載入或解除安裝之前匯入的模組。 模組的內建的全域性變數__name__ 如果執行的是當前的檔案__name__ = __main__ 如果只是被當做模組的形式被執行,__name__ == 模組名 注意不是別名 指令碼和模組 模組就是一系列功能的集合,等待被匯入之後使用 指令碼就是一個程式,用來執行 模組的迴圈匯入 檔案的執行雖然也會載入到記憶體中,但是並不是模組載入到記憶體,也就是執行並不等於匯入 '''
模組的搜尋路徑
當執行這個語句的時候,我們要去執行spam檔案,但是系統是怎麼知道spam檔案在哪裡的呢?這個涉及到了模組的搜尋路徑了,模組的尋找路徑是這樣的:
# 模組的搜尋路徑 ''' 1. 查詢記憶體中是否存在此模組 2. 是否是內建函式 3. 去系統路徑中找是否存在此檔案,查詢順序如下 H:\python_study\day17 當前檔案所在目錄 H:\python_study\venv\Scripts\python36.zip E:\software\python3\DLLs E:\software\python3\lib E:\software\python3 H:\python_study\venv H:\python_study\venv\lib\site-packages H:\python_study\venv\lib\site-packages\setuptools-39.1.0-py3.6.egg H:\python_study\venv\lib\site-packages\pip-10.0.1-py3.6.egg E:\software\pycharm\PyCharm 2018.1.4\helpers\pycharm_matplotlib_backend '''
從路徑可以看出來如果我們在當前目錄下面重新建立一個目錄寫入一個模組的話,預設情況下匯入的時候是找不到路徑的,我們需要通過sys.path方法把它匯入進去。
sys.path(r'H:\python_study\day17')
作業:
1. 定義一個cuboid模組,模組中有三個變數長(long)寬(wide)高(high),數值自定義,有一個返回值為周長的perimeter方法,一個返回值為表面積的area方法
#2.定義一個cuboid模組,模組中有三個變數長(long)寬(wide)高(high),數值自定義, # 有一個返回值為周長的perimeter方法,一個返回值為表面積的area方法 long = 2.5 wide = 3.5 high = 1 def perimeter(): return (long + wide + high) * 4 def area(): return (long * wide + long * high + wide * high) * 2第一題答案
2. 定義一個使用者檔案stu1.py,在該檔案中列印cuboid的長寬高,並獲得周長和表面積,打印出來
# 方法一:import import cuboid print('長: {long} 寬: {width} 高: {high} 周長: {perimeter} 面積: {area}'.format( long=cuboid.long, width=cuboid.width, high=cuboid.high, perimeter=cuboid.perimeter(), area=cuboid.area() )) # 方法二:from from cuboid import long, width, high, perimeter, area print('長: {long} 寬: {width} 高: {high} 周長: {perimeter} 面積: {area}'.format( long=long, width=width, high=high, perimeter=perimeter(), area=area() ))第二題答案
3. 在stu2.py檔案中匯入cuboid模組時為模組起簡單別名,利用別名完成第3題中完成的操作
# 方法一:import import cuboid as cb print('長: {long} 寬: {width} 高: {high} 周長: {perimeter} 面積: {area}'.format( long=cb.long, width=cb.width, high=cb.high, perimeter=cb.perimeter(), area=cb.area() )) # 方法二:from from cuboid import long as l, width as w, high as h, perimeter as p, area as a print('長: {long} 寬: {width} 高: {high} 周長: {perimeter} 面積: {area}'.format( long=l, width=w, high=h, perimeter=p(), area=a() ))第三題答案
4.比較總結import與from...import...各自的優缺點
import: 模組和原始檔各自都有自己的名稱空間,不會與當前執行檔案中的名字產生衝突,但是使用變數的時候會麻煩一點 from: 模組的名稱空間會複製某些部分到當前執行檔案中,所以會與當前執行檔案中的名字產生衝突,使用起來會方便一些