您好,登錄后才能下訂單哦!
在Python中,一個py文件就是一個模塊,文件名為xxx.py模塊名則是xxx,導入模塊可以引用模塊中已經寫好的功能。如果把開發程序比喻成制造一臺電腦,編寫模塊就像是在制造電腦的零部件,準備好零部件后,剩下的工作就是按照邏輯把它們組裝到一起。
將程序模塊化會使得程序的組織結構清晰,維護起來更加方便。比起直接開發一個完整的程序,單獨開發一個小的模塊也會更加簡單,并且程序中的模塊與電腦中的零部件稍微不同的是:程序中的模塊可以被重復使用。所以總結下來,使用模塊既保證了代碼的重用性,又增強了程序的結構性和可維護性。另外除了自定義模塊外,我們還可以導入使用內置或第三方模塊提供的現成功能,這種“拿來主義”極大地提高了程序員的開發效率。
插圖:惡搞圖01
有如下示范文件
#文件名:foo.py
x=1
def get():
print(x)
def change():
global x
x=0
class Foo:
def func(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。
插圖:惡搞圖02
需要強調一點是,第一次導入模塊已經將其加載到內存空間了,之后的重復導入會直接引用內存中已存在的模塊,不會重復執行文件,通過import sys,打印sys.modules的值可以看到內存中已經加載的模塊名。
提示:
#1、在Python中模塊也屬于第一類對象,可以進行賦值、以數據形式傳遞以及作為容器類型的元素等操作。
#2、模塊名應該遵循小寫形式,標準庫從python2過渡到python3做出了很多這類調整,比如ConfigParser、Queue、SocketServer全更新為純小寫形式。
插圖:惡搞圖03
用import語句導入多個模塊,可以寫多行import語句
import module1
import module2
...
import moduleN
還可以在一行導入,用逗號分隔開不同的模塊
import module1,module2,...,moduleN
但其實第一種形式更為規范,可讀性更強,推薦使用,而且我們導入的模塊中可能包含有python內置的模塊、第三方的模塊、自定義的模塊,為了便于明顯地區分它們,我們通常在文件的開頭導入模塊,并且分類導入,一類模塊的導入與另外一類的導入用空行隔開,不同類別的導入順序如下:
#1. python內置模塊
#2. 第三方模塊
#3. 程序員自定義模塊
? 當然,我們也可以在函數內導入模塊,對比在文件開頭導入模塊屬于全局作用域,在函數內導入的模塊則屬于局部的作用域。
插圖:惡搞圖04
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
無需加前綴的好處是使得我們的代碼更加簡潔,壞處則是容易與當前名稱空間中的名字沖突,如果當前名稱空間存在相同的名字,則后定義的名字會覆蓋之前定義的名字。
插圖:惡搞圖05
另外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
def get():
print(x)
def change():
global x
x=0
class Foo:
def func(self):
print('from the func')
這樣我們在另外一個文件中使用*導入時,就只能導入__all__定義的名字了
from foo import * #此時的*只代表x和get
x #可用
get() #可用
change() #不可用
Foo() #不可用
插圖:惡搞圖06
我們還可以在當前位置為導入的模塊起一個別名
import foo as f #為導入的模塊foo在當前位置起別名f,以后再使用時就用這個別名f
f.x
f.get()
還可以為導入的一個名字起別名
from foo import get 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) #最終調用的方式是一致的
插圖:惡搞圖08
循環導入問題指的是在一個模塊加載/導入的過程中導入另外一個模塊,而在另外一個模塊中又返回來導入第一個模塊中的名字,由于第一個模塊尚未加載完畢,所以引用失敗、拋出異常,究其根源就是在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
測試一
#1、執行run.py會拋出異常
正在導入m1
正在導入m2
Traceback (most recent call last):
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/aa.py", line 1, in <module>
import m1
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in <module>
from m2 import y
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in <module>
from m1 import x
ImportError: cannot import name 'x'
#2、分析
先執行run.py--->執行import m1,開始導入m1并運行其內部代碼--->打印內容"正在導入m1"
--->執行from m2 import y 開始導入m2并運行其內部代碼--->打印內容“正在導入m2”--->執行from m1 import x,由于m1已經被導入過了,所以不會重新導入,所以直接去m1中拿x,然而x此時并沒有存在于m1中,所以報錯
插圖:惡搞圖09
測試二
#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 <module>
from m2 import y
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m2.py", line 2, in <module>
from m1 import x
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa練習目錄/m1.py", line 2, in <module>
from m2 import y
ImportError: cannot import 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中所以報錯
插圖:惡搞圖10
解決方案
# 方案一:導入語句放到最后,保證在導入時,所有名字都已經加載過
# 文件:m1.py
print('正在導入m1')
x='m1'
from m2 import y
# 文件:m2.py
print('正在導入m2')
y='m2'
from m1 import x
# 文件:run.py內容如下,執行該文件,可以正常使用
import m1
print(m1.x)
print(m1.y)
# 方案二:導入語句放到函數中,只有在調用函數時才會執行其內部代碼
# 文件:m1.py
print('正在導入m1')
def f1():
from m2 import y
print(x,y)
x = 'm1'
# 文件:m2.py
print('正在導入m2')
def f2():
from m1 import x
print(x,y)
y = 'm2'
# 文件:run.py內容如下,執行該文件,可以正常使用
import m1
m1.f1()
注意:循環導入問題大多數情況是因為程序設計失誤導致,上述解決方案也只是在爛設計之上的無奈之舉,在我們的程序中應該盡量避免出現循環/嵌套導入,如果多個模塊確實都需要共享某些數據,可以將共享的數據集中存放到某一個地方,然后進行導入
插圖:惡搞圖11
模塊其實分為四個通用類別,分別是:
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確實支持從一個壓縮文件中導入模塊,我們也只需要把它們都當成目錄去看即可。
插圖:惡搞圖12
sys.path中的第一個路徑通常為空,代表執行文件所在的路徑,所以在被導入模塊與執行文件在同一目錄下時肯定是可以正常導入的,而針對被導入的模塊與執行文件在不同路徑下的情況,為了確保模塊對應的源文件仍可以被找到,需要將源文件foo.py所在的路徑添加到sys.path中,假設foo.py所在的路徑為/pythoner/projects/
import sys
sys.path.append(r'/pythoner/projects/') #也可以使用sys.path.insert(……)
import foo #無論foo.py在何處,我們都可以導入它了
一個Python文件有兩種用途,一種被當主程序/腳本執行,另一種被當模塊導入,為了區別同一個文件的不同用途,每個py文件都內置了__name__變量,該變量在py文件被當做腳本執行時賦值為“__main__”,在py文件被當做模塊導入時賦值為模塊名
插圖:惡搞圖14
作為模塊foo.py的開發者,可以在文件末尾基于__name__在不同應用場景下值的不同來控制文件執行不同的邏輯
#foo.py
...
if __name__ == '__main__':
foo.py被當做腳本執行時運行的代碼
else:
foo.py被當做模塊導入時運行的代碼
通常我們會在if的子代碼塊中編寫針對模塊功能的測試代碼,這樣foo.py在被當做腳本運行時,就會執行測試代碼,而被當做模塊導入時則不用執行測試代碼。
我們在編寫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() #在被當做腳本執行時,執行此處的代碼
插圖:惡搞圖13
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。