一 模塊介紹
#一個python文件有兩種用途#1、被當成程序運行#2、被當做模塊導入
在Python中,一個py文件就是一個模塊,文件名為xxx.py模塊名則是xxx,導入模塊可以引用模塊中已經寫好的功能。如果把開發程序比喻成制造一臺電腦,編寫模塊就像是在制造電腦的零部件,準備好零部件后,剩下的工作就是按照邏輯把它們組裝到一起。
將程序模塊化會使得程序的組織結構清晰,維護起來更加方便。比起直接開發一個完整的程序,單獨開發一個小的模塊也會更加簡單,并且程序中的模塊與電腦中的零部件稍微不同的是:程序中的模塊可以被重復使用。所以總結下來,使用模塊既保證了代碼的重用性,又增強了程序的結構性和可維護性。另外除了自定義模塊外,我們還可以導入使用內置或第三方模塊提供的現成功能,這種“拿來主義”極大地提高了程序員的開發效率。
模塊就是一系列功能的集合體,分為三大類
I:內置的模塊
II:第三方的模塊
III:自定義的模塊
一個python文件本身就一個模塊,文件名m.py,模塊名叫m
ps:模塊有四種形式
1 使用python編寫的.py文件
2 已被編譯為共享庫或DLL的C或C++擴展
3 把一系列模塊組織到一起的文件夾(注:文件夾下有一個__init__.py文件,該文件夾稱之為包)
4 使用C編寫并鏈接到python解釋器的內置模塊
I:內置與第三的模塊拿來就用,無需定義,這種拿來主義,可以極大地提升自己的開發效率
II:自定義的模塊
可以將程序的各部分功能提取出來放到一模塊中為大家共享使用
好處是減少了代碼冗余,程序組織結構更加清晰
二 模塊的使用
2.1 import語句
y=333z=444
importfoo#1、首次導入模塊會發生3件事#1、執行foo.py#2、產生foo.py的名稱空間,將foo.py運行過程中產生的名字都丟到foo的名稱空間中#3、在當前文件中產生的有一個名字foo,該名字指向2中產生的名稱空間
#之后的導入,都是直接引用首次導入產生的foo.py名稱空間,不會重復執行代碼#import foo#import foo#import foo#import foo
有如下示范文件
#文件名:foo.py
x=1
defget():print(x)defchange():globalx
x=0classFoo:deffunc(self):print('from the func')
要想在另外一個py文件中引用foo.py中的功能,需要使用import foo,首次導入模塊會做三件事:
1、執行源文件代碼
2、產生一個新的名稱空間用于存放源文件執行過程中產生的名字
3、在當前執行文件所在的名稱空間中得到一個名字foo,該名字指向新創建的模塊名稱空間,若要引用模塊名稱空間中的名字,需要加上該前綴,如下
import foo #導入模塊foo
a=foo.x #引用模塊foo中變量x的值賦值給當前名稱空間中的名字a
foo.get() #調用模塊foo的get函數
foo.change() #調用模塊foo中的change函數
obj=foo.Foo() #使用模塊foo的類Foo來實例化,進一步可以執行obj.func()
加上foo.作為前綴就相當于指名道姓地說明要引用foo名稱空間中的名字,所以肯定不會與當前執行文件所在名稱空間中的名字相沖突,并且若當前執行文件的名稱空間中存在x,執行foo.get()或foo.change()操作的都是源文件中的全局變量x。
需要強調一點是,第一次導入模塊已經將其加載到內存空間了,之后的重復導入會直接引用內存中已存在的模塊,不會重復執行文件,通過import sys,打印sys.modules的值可以看到內存中已經加載的模塊名。
#強調1:模塊名.名字,是指名道姓地問某一個模塊要名字對應的值,不會與當前名稱空間中的名字發生沖突#x=1111111111111#print(x)#print(foo.x)
#強調2:無論是查看還是修改操作的都是模塊本身,與調用位置無關#import foo#
#x=3333333333## foo.get()#
#foo.change()#print(x)#
#print(foo.x)#foo.get()
提示:
#1、在Python中模塊也屬于第一類對象,可以進行賦值、以數據形式傳遞以及作為容器類型的元素等操作。
#2、模塊名應該遵循小寫形式,標準庫從python2過渡到python3做出了很多這類調整,比如ConfigParser、Queue、SocketServer全更新為純小寫形式。
用import語句導入多個模塊,可以寫多行import語句
importmodule1importmodule2
...import moduleN
還可以在一行導入,用逗號分隔開不同的模塊
import module1,module2,...,moduleN
但其實第一種形式更為規范,可讀性更強,推薦使用,而且我們導入的模塊中可能包含有python內置的模塊、第三方的模塊、自定義的模塊,為了便于明顯地區分它們,我們通常在文件的開頭導入模塊,并且分類導入,一類模塊的導入與另外一類的導入用空行隔開,不同類別的導入順序如下:
#1. python內置模塊#2. 第三方模塊#3. 程序員自定義模塊
? 當然,我們也可以在函數內導入模塊,對比在文件開頭導入模塊屬于全局作用域,在函數內導入的模塊則屬于局部的作用域。
#4、導入模塊的規范#I. python內置模塊#II. 第三方模塊#III. 程序員自定義模塊
#import time#import sys#
#import 第三方1#import 第三方2#
#import 自定義模塊1#import 自定義模塊2#import 自定義模塊3
#5、模塊是第一類對象
importfoo#6、自定義模塊的命名應該采用純小寫+下劃線的風格#7、可以在函數內導入模塊
deffunc():import foo
2.2 from-import 語句
#impot導入模塊在使用時必須加前綴"模塊."#優點:肯定不會與當前名稱空間中的名字沖突#缺點:加前綴顯得麻煩
#from ... import ...導入也發生了三件事#1、產一個模塊的名稱空間#2、運行foo.py將運行過程中產生的名字都丟到模塊的名稱空間去#3、在當前名稱空間拿到一個名字,該名字與模塊名稱空間中的某一個內存地址#from foo import x # x=模塊foo中值0的內存地址#from foo import get#from foo import change
from...import...與import語句基本一致,唯一不同的是:使用import foo導入模塊后,引用模塊中的名字都需要加上foo.作為前綴,而使用from foo import x,get,change,Foo則可以在當前執行文件中直接引用模塊foo中的名字,如下
from foo import x,get,change #將模塊foo中的x和get導入到當前名稱空間
a=x #直接使用模塊foo中的x賦值給a
get() #直接執行foo中的get函數
change() #即便是當前有重名的x,修改的仍然是源文件中的x
無需加前綴的好處是使得我們的代碼更加簡潔,壞處則是容易與當前名稱空間中的名字沖突,如果當前名稱空間存在相同的名字,則后定義的名字會覆蓋之前定義的名字。
另外from語句支持from foo import?語法,代表將foo中所有的名字都導入到當前位置
from foo import * #把foo中所有的名字都導入到當前執行文件的名稱空間中,在當前位置直接可以使用這些名字
a=x
get()
change()
obj=Foo()
如果我們需要引用模塊中的名字過多的話,可以采用上述的導入形式來達到節省代碼量的效果,但是需要強調的一點是:只能在模塊最頂層使用的方式導入,在函數內則非法,并且的方式會帶來一種副作用,即我們無法搞清楚究竟從源文件中導入了哪些名字到當前位置,這極有可能與當前位置的名字產生沖突。模塊的編寫者可以在自己的文件中定義__all__變量用來控制*代表的意思
#foo.py
__all__=['x','get'] #該列表中所有的元素必須是字符串類型,每個元素對應foo.py中的一個名字
x=1
defget():print(x)defchange():globalx
x=0classFoo:deffunc(self):print('from the func')
這樣我們在另外一個文件中使用*導入時,就只能導入__all__定義的名字了
from foo import * #此時的*只代表x和get
x#可用
get() #可用
change() #不可用
Foo() #不可用
#from...impot...導入模塊在使用時不用加前綴#優點:代碼更精簡#缺點:容易與當前名稱空間混淆#from foo import x # x=模塊foo中值1的內存地址#x=1111
#一行導入多個名字(不推薦)#from foo import x,get,change
2.3 其他導入語法(as)
我們還可以在當前位置為導入的模塊起一個別名
import foo as f #為導入的模塊foo在當前位置起別名f,以后再使用時就用這個別名f
f.x
f.get()
還可以為導入的一個名字起別名
from foo importget as get_x
get_x()
通常在被導入的名字過長時采用起別名的方式來精簡代碼,另外為被導入的名字起別名可以很好地避免與當前名字發生沖突,還有很重要的一點就是:可以保持調用方式的一致性,例如我們有兩個模塊json和pickle同時實現了load方法,作用是從一個打開的文件中解析出結構化的數據,但解析的格式不同,可以用下述代碼有選擇性地加載不同的模塊
if data_format == 'json':import json as serialize #如果數據格式是json,那么導入json模塊并命名為serialize
elif data_format == 'pickle':import pickle as serialize #如果數據格式是pickle,那么導入pickle模塊并命名為serialize
data=serialize.load(fn) #最終調用的方式是一致的
2.4 循環導入問題
循環導入問題指的是在一個模塊加載/導入的過程中導入另外一個模塊,而在另外一個模塊中又返回來導入第一個模塊中的名字,由于第一個模塊尚未加載完畢,所以引用失敗、拋出異常,究其根源就是在python中,同一個模塊只會在第一次導入時執行其內部代碼,再次導入該模塊時,即便是該模塊尚未完全加載完畢也不會去重復執行內部代碼
我們以下述文件為例,來詳細分析循環/嵌套導入出現異常的原因以及解決的方案
m1.py
print('正在導入m1')from m2 importy
x='m1'
m2.py
print('正在導入m2')from m1 importx
y='m2'
run.py
import m1
測試一
#1、執行run.py會拋出異常
正在導入m1
正在導入m2
Traceback (most recent call last):
File"/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/aa.py", line 1, in
importm1
File"/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in
from m2 importy
File"/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in
from m1 importx
ImportError: cannotimport name 'x'
#2、分析
先執行run.py--->執行import m1,開始導入m1并運行其內部代碼--->打印內容"正在導入m1"
--->執行from m2 import y 開始導入m2并運行其內部代碼--->打印內容“正在導入m2”--->執行from m1 import x,由于m1已經被導入過了,所以不會重新導入,所以直接去m1中拿x,然而x此時并沒有存在于m1中,所以報錯
測試二
#1、執行文件不等于導入文件,比如執行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 importy
File"/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in
from m1 importx
File"/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in
from m2 importy
ImportError: cannotimport name 'y'
#2、分析
執行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 importy#文件:m2.py
print('正在導入m2')
y='m2'
from m1 importx#文件:run.py內容如下,執行該文件,可以正常使用
importm1print(m1.x)print(m1.y)#方案二:導入語句放到函數中,只有在調用函數時才會執行其內部代碼#文件:m1.py
print('正在導入m1')deff1():from m2 importyprint(x,y)
x= 'm1'
#文件:m2.py
print('正在導入m2')deff2():from m1 importxprint(x,y)
y= 'm2'
#文件:run.py內容如下,執行該文件,可以正常使用
importm1
m1.f1()
注意:循環導入問題大多數情況是因為程序設計失誤導致,上述解決方案也只是在爛設計之上的無奈之舉,在我們的程序中應該盡量避免出現循環/嵌套導入,如果多個模塊確實都需要共享某些數據,可以將共享的數據集中存放到某一個地方,然后進行導入
2.5 搜索模塊的路徑與優先級
模塊其實分為四個通用類別,分別是:
1、使用純Python代碼編寫的py文件
2、包含一系列模塊的包
3、使用C編寫并鏈接到Python解釋器中的內置模塊
4、使用C或C++編譯的擴展模塊
在導入一個模塊時,如果該模塊已加載到內存中,則直接引用,否則會優先查找內置模塊,然后按照從左到右的順序依次檢索sys.path中定義的路徑,直到找模塊對應的文件為止,否則拋出異常。sys.path也被稱為模塊的搜索路徑,它是一個列表類型
>>>sys.path
['','/Library/Frameworks/Python.framework/Versions/3.5/lib/python35.zip','/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5',
...,'/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages'
列表中的每個元素其實都可以當作一個目錄來看:在列表中會發現有.zip或.egg結尾的文件,二者是不同形式的壓縮文件,事實上Python確實支持從一個壓縮文件中導入模塊,我們也只需要把它們都當成目錄去看即可。
sys.path中的第一個路徑通常為空,代表執行文件所在的路徑,所以在被導入模塊與執行文件在同一目錄下時肯定是可以正常導入的,而針對被導入的模塊與執行文件在不同路徑下的情況,為了確保模塊對應的源文件仍可以被找到,需要將源文件foo.py所在的路徑添加到sys.path中,假設foo.py所在的路徑為/pythoner/projects/
importsys
sys.path.append(r'/pythoner/projects/') #也可以使用sys.path.insert(……)
import foo #無論foo.py在何處,我們都可以導入它了
#無論是import還是from...import在導入模塊時都涉及到查找問題#優先級:#1、內存(內置模塊)#2、硬盤:按照sys.path中存放的文件的順序依次查找要導入的模塊
#import sys#值為一個列表,存放了一系列的對文件夾#其中第一個文件夾是當前執行文件所在的文件夾#print(sys.path)
#import foo # 內存中已經有foo了#foo.say()#
#import time#time.sleep(10)#
#import foo#foo.say()
#了解:sys.modules查看已經加載到內存中的模塊
importsys#import foo # foo=模塊的內存地址#del foo
#def func():#import foo # foo=模塊的內存地址#
#func()#
## print('foo' in sys.modules)#print(sys.modules)
2.6 區分py文件的兩種用途
一個Python文件有兩種用途,一種被當主程序/腳本執行,另一種被當模塊導入,為了區別同一個文件的不同用途,每個py文件都內置了__name__變量,該變量在py文件被當做腳本執行時賦值為“__main__”,在py文件被當做模塊導入時賦值為模塊名
作為模塊foo.py的開發者,可以在文件末尾基于__name__在不同應用場景下值的不同來控制文件執行不同的邏輯
#foo.py
...if __name__ == '__main__':
foo.py被當做腳本執行時運行的代碼else:
foo.py被當做模塊導入時運行的代碼
通常我們會在if的子代碼塊中編寫針對模塊功能的測試代碼,這樣foo.py在被當做腳本運行時,就會執行測試代碼,而被當做模塊導入時則不用執行測試代碼。
2.7 編寫一個規范的模塊
我們在編寫py文件時,需要時刻提醒自己,該文件既是給自己用的,也有可能會被其他人使用,因而代碼的可讀性與易維護性顯得十分重要,為此我們在編寫一個模塊時最好按照統一的規范去編寫,如下
#!/usr/bin/env python #通常只在類unix環境有效,作用是可以使用腳本名來執行,而無需直接調用解釋器。
"The module is used to..." #模塊的文檔描述
import sys #導入模塊
x=1 #定義全局變量,如果非必須,則最好使用局部變量,這樣可以提高代碼的易維護性,并且可以節省內存提高性能
class Foo: #定義類,并寫好類的注釋
'Class Foo is used to...'
pass
def test(): #定義函數,并寫好函數的注釋
'Function test is used to…'
pass
if __name__ == '__main__': #主程序
test() #在被當做腳本執行時,執行此處的代碼
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态