我們來考慮下如下幾種場景:
1、編寫一個python程序,如果程序比較簡單,則可以把代碼放到一個python文件中。但如果程序功能比較多,可能需要多個python文件來組織源代碼。而這些文件之間的代碼肯定有關聯,比如一個文件中的python代碼調用另一個python文件中定義的函數。
2、我們編寫程序,肯定不會所有的東西都自己寫,不會全部重造輪子,我們肯定會用到Python提供的一些標准庫。那怎么使用呢?其實前面的文章中已經看到了,用import語句。 如同java中用import,c#中用using語句。
3、我們自想編寫一個公共代碼,或從外部找到一個第三方的公共代碼,如何放入到整個python系統中,如何被自己編寫的代碼使用。
上面這些場景,都是在編寫程序時常見的事情。
這些問題,python是通過模塊和包的機制來解決的。
簡單的說,一個模塊就是一個python文件,一個包是包含一組模塊。下面我們通過實際的例子來說明。
一、案例1:一個最簡單例子
編寫 test1.py文件,代碼如下
#coding=utf-8 import test2 print "hello" test2.fun("world")
編寫test2.py文件,代碼如下
#coding=utf-8 def fun(para): print para
這兩個python文件位於同一目錄,但不一定要在python的相關系統目錄下,可以是任意的合法目錄。
這時我們執行 test1.py,可以成功運行。
可以看出,test1.py中的代碼 調用了 test2.py中的 fun方法,這能夠調用的關鍵是在test1.py中 import test2這個語句,表示將test2.py這個模塊引入進來,
同時調用時是通過 模塊名.函數名 調用的。
二、案例2: 如何放置模塊
上面的例子,是兩個python文件位於同一目錄下。如果test2.py想放在其它目錄下怎么辦呢?
這里關鍵是讓python解釋器能找到 test2.py。
這種情況很常見,比如 test2.py是個通用的模塊,可以被多個程序使用,那它就不能與使用它的程序放在一起,否則就要拷貝多份了。
將test2.py 放在其它地方,有多種方法,下面分別介紹。
方法一:放在python已有的系統目錄下
把模塊(python文件)放在python的系統目錄下,引入模塊時,python解釋器就能找到。
可以通過如下的代碼查看當前有哪些系統目錄:
>>> import sys,pprint >>> pprint.pprint(sys.path) ['', 'C:\\Python27\\lib\\site-packages\\pip-7.1.2-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\paramiko-1.15.2-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\robotframework_sshlibrary-2.1.1-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\ecdsa-0.13-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\selenium-2.47.1-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\decorator-4.0.2-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\easyprocess-0.1.9-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\webtest-2.0.20-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\beautifulsoup4-4.4.1-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\waitress-0.8.10-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\webob-1.5.1-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\six-1.10.0-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\jsonpointer-1.10-py2.7.egg', 'C:\\Python27\\lib\\site-packages\\jsonpatch-1.12-py2.7.egg', 'C:\\Windows\\system32\\python27.zip', 'C:\\Python27\\DLLs', 'C:\\Python27\\lib', 'C:\\Python27\\lib\\plat-win', 'C:\\Python27\\lib\\lib-tk', 'C:\\Python27', 'C:\\Python27\\lib\\site-packages', 'C:\\Python27\\lib\\site-packages\\win32', 'C:\\Python27\\lib\\site-packages\\win32\\lib', 'C:\\Python27\\lib\\site-packages\\Pythonwin']
python的 標准庫 sys模塊中的path對象包含了所有的系統路徑,利用 pprint模塊中的pprint方法可以格式化的顯示數據,如果用內置語句print則只能在一行顯示所有內容,查看不方便。
我們只要把python文件(如本文例子中的test2.py)放在上述任何目錄下,python解釋器就能找到。
注意:必須直接放在上述目錄下,不能建立子目錄,放在子目錄下。要想能放到子目錄下,就是包的概念,下面會介紹。
方法二:新增系統目錄
除了python自己默認的一些系統目錄外,應用程序也可以通過代碼添加系統目錄。
因為系統路徑是存在 sys.path對象下的,path對象是個列表,就可以自己通過代碼往其中插入目錄,如
sys.path.append("D:/demo/python/dir")
但很顯然,插入的這個目錄作為系統目錄只能對當前程序生效,因為這只是在內存中生效。
方法三:設置環境變量
如果我們不想把代碼放在python的系統目錄下,以免和python的目錄混在一起,增加管理的復雜性。
甚至有的時候,因為權限的原因,還不能在python的系統目錄下加文件。
而希望放在自己規划的目錄下。 這時就可以操作系統的 PYTHONPATH 環境變量,該環境變量包含一系列的目錄。
該環境變量下的所有目錄都能被python解釋器搜到。
這樣我們就可以將代碼放到PYTHONPATH 環境變量包含的目錄下(注意不能是子目錄,除非是包),就可以被別的程序import了。
設置PYTHONPATH 環境變量是相對比較好的方式,推薦使用。
案例三:路徑的優先級
根據上面的介紹,一個模塊要能別的程序引用(import)到,可以和程序放在一個目錄,可以放到python系統目錄下,可以放到PYTHONPATH 環境變量包含的目錄下,那哪個優先級最高。
這時我們可以測試下,寫三個同名文件,文件中定義同名的函數,函數只有一個print語句,但三個文件中的函數的print語句內容不同。
再編寫另外一個程序import上面這個文件,並調用定義的函數,看看輸出,就知道優先級了。
經過測試,發現優先級從高到底分別是:
1)當前目錄
2)環境變量PYTHONPATH包含的路徑
3)python系統目錄
這個其實也很好理解,正常情況,越是用戶的設置優先級越高。
案例四:包
我們上面的介紹,每個模塊都是獨立的一個python文件。為了讓python能發現他們,必須放在相應的目錄下。
沒有分層,容易造成命名沖突和管理上的混亂。
特別在實際情況下,一個功能往往由多個模塊(文件)組成,一般我們希望把這些代碼放在一個目錄下,便於管理。
這就要用到python的包的機制了。
python的包,物理上是一個目錄,它實際上也是一個模塊,只是比較特殊的模塊,就是它還能包含其它模塊。
下面我們舉例來說明:
我們創建一個目錄,如 testpackage
要想這個目錄成為一個python包,而不是一個普通的目錄,關鍵是在該目錄下創建一個文件 __init__.py , init的前后分別是兩個連續的下划線。
__init__.py文件名是固定的,但其中內容是任意的,就如同編寫一個模塊一樣,可以放置任意的代碼。如:
#coding=utf-8 print "hello,i am package" def hello(): print "good"
我們再在testpackage目錄 所在的目錄下建立一個test.py文件,內容如下
#coding=utf-8 import testpackage testpackage.hello()
test.py中就導入了testpackage,這時我們執行test.py,發現輸出:
hello,i am package
good
可以看出,testpackage就是一個特殊的模塊,但因為它本身不是一個python文件,而是一個目錄,那么它下面的__init__.py 就是模塊的內容,導入包,其實就是導入__init__.py文件。 而普通模塊對應的是python文件,要求模塊名和文件名一致。
我們再在 testpackage 下建立兩個文件,module1.py , module2.py ,內容分別是:
#coding=utf-8 def fun1(): print "module1"
#coding=utf-8 def fun2(): print "module2"
下面我們來使用這兩個模塊,修改test.py文件。修改后的test.py的內容如下:
#coding=utf-8 import testpackage import testpackage.module1 from testpackage import module2 testpackage.hello() testpackage.module1.fun1() module2.fun2()
執行test.py的輸出如下:
hello,i am package
good
module1
module2
下面我們來分析下test.py中的內容
可以看出,我們用兩種不同的方式導入了module1模塊和 module2模塊。
采用import testpackage.module1 方式,則要求在使用module1中的函數等時,需要全路徑引用,如testpackage.module1.fun1()。
采用from testpackage import module2方式,則要求在使用module2中的函數等時,可以省略包名引用,如module2.fun2()。
一般情況下,我們采用from導入的方式。
還有一點需要說明的是,導入包中的模塊,這時就不再需要導入包,因為會自動先導入模塊所在的包,也就是說會自動導入包的__init__.py文件。
另外一點,既然包是一個特殊的模塊,它的存放和普通模塊一樣,可以和用它的程序在一個目錄下,可以在python系統目錄下,也可以放到環境變量PYTHONPATH包含的目錄下。
有了包這個功能,對於復雜的程序,就可以更好的組織源代碼。
案例五:模塊中能放什么呢?
在前面的例子中,已經涉及到了一些內容,下面我們再來更為詳細的介紹下模塊中能放什么,怎么用的問題。
第一,模塊中能放什么,理論上說,跟普通的python代碼文件一樣,可以放 變量、函數定義、類定義,甚至直接的語句調用等內容。
第二,用的問題。首先就要被導入。
被導入時,模塊中直接寫的語句,如 print就會被立即執行,變量等也會被定義和初始化(如果有的話)。
第三,一個python文件,既可以作為作為主程序直接被執行,也可以作為一個模塊被其它程序(或模塊)導入。
那有的時候,我們希望有些直接寫在文件最頂層的代碼(不是函數或類)在作為程序直接執行 和 作為模塊導入時是有差別的,那該怎么辦?
我們還是看例子。
假設有 test1.py文件,其內容:
if __name__=="__main__": print "hello,i am run self" else: print "hello,i am import by other"
如果我們執行運行test1.py,如在命令行下執行: python test1.py,我們發現打印的是 hello,i am run self 。
如果我們在別的程序中導入test1.py,如 import test1。我們發現打印的是 hello,i am import by other。
__name__是一個系統變量,當其值是__main__時,表示它是作為主程序被執行的。
通過這種方式,我們就可以將一個py文件 作為主程序 和 模塊導入時 的差異化同時實現。