天天看點

Python3 子產品

子產品是一個包含所有你定義的函數和變量的檔案,其字尾名是.py。子產品可以被别的程式引入,以使用該子產品中的函數等功能。這也是使用 python 标準庫的方法,我們可以把寫好的一些可複用的函數,封裝成子產品然後釋出到Python的本地庫中。然後在其他的程式就可以導入你這個寫好的子產品了。簡單來說子產品就像一個常用的零件,例如組裝一個高達模型時,可以把現成的零件拿過來使用,加快我們的組裝速度,如果零件都需要我們自己制作就會慢很多而且難度也大。而标準庫中的子產品就是Python自帶的零件,提供我們去使用,我們也可以開發自己的子產品,自己開發的子產品釋出到本地後,一樣可以像标準庫中的子產品去使用它們。 

下面是一個使用 python 标準庫中子產品的例子。

1

2

3

4

5

6

7

8

9

<code>import</code> <code>sys  </code><code># 導入子產品的關鍵字是import,這裡導入了sys子產品</code>

<code>print</code><code>(</code><code>"指令行參數如下:"</code><code>)</code>

<code>for</code> <code>i </code><code>in</code> <code>sys.argv:  </code><code># 通過子產品可以調用它裡面的變量或者函數</code>

<code>    </code><code>print</code><code>(i)</code>

<code>print</code><code>(</code><code>"\n\nPython路徑為:"</code><code>)</code>

<code>for</code> <code>path </code><code>in</code> <code>sys.path:</code>

<code>    </code><code>print</code><code>(path)</code>

運作結果:

 指令行參數如下:   E:/PythonProject/TestMould.py Python路徑為:   E:\PythonProject   E:\Python3.6\python36.zip   E:\Python3.6\DLLs   E:\Python3.6\lib   E:\Python3.6   E:\Python3.6\lib\site-packages

1、import sys 引入 python 标準庫中的 sys.py 子產品;這是引入某一子產品的方法。 

2、sys.argv 是一個包含指令行參數的清單。 

3、sys.path 包含了一個 Python 解釋器自動查找所需子產品的路徑的清單。

import 語句用來導入Python庫中的子產品,子產品需要導入之後才能使用,文法如下:

import module1[, module2[,… moduleN]

當解釋器遇到 import 語句,如果子產品在目前的搜尋路徑就會被導入。搜尋路徑是一個解釋器會事先進行搜尋的所有目錄的清單。 

例如:我們在PyCharm工具中建立一個model.py檔案,這個檔案就相當于是一個子產品了,接着可以在檔案裡自定義一個函數:

然後再建立一個Hello.py檔案,在這個檔案中可以通過import導入這個子產品,通過這個導入的子產品就可以調用此子產品裡面的函數:

<code># Filename: Hello.py</code>

<code># 導入子產品</code>

<code>import</code> <code>model</code>

<code># 現在可以調用子產品裡包含的函數了</code>

<code>model.println()</code>

Test!

如果你打算經常使用一個函數,你可以把它指派給一個變量:

<code>println</code><code>=</code><code>model.println</code>

<code>println()</code>

以上示例屬于是一個自定義子產品的建立和導入過程。

一個子產品隻會被導入一次,不管你執行了多少次import語句,是以這樣可以防止導入子產品被一遍又一遍地執行。 

當我們使用import語句的時候,Python解釋器是怎樣找到對應的檔案的呢? 

這就涉及到了Python的搜尋路徑:

搜尋路徑是由一系列目錄名組成的

Python解釋器就依次從這些目錄中去尋找所引入的子產品。

搜尋路徑是在Python編譯或安裝的時候确定的,安裝新的庫應該也會修改。

搜尋路徑被存儲在sys子產品中的path變量

這看起來很像環境變量,事實上,也可以通過定義環境變量的方式來确定搜尋路徑。 

可以做一個簡單的實驗來檢視Python的搜尋路徑,代碼示例:

<code>import</code> <code>sys</code>

<code>print</code><code>(sys.path) </code><code># 将目錄清單列印出來</code>

[‘E:\PythonProject’, ‘E:\PythonProject’, ‘E:\Python3.6\python36.zip’, ‘E:\Python3.6\DLLs’, ‘E:\Python3.6\lib’, ‘E:\Python3.6’, ‘E:\Python3.6\lib\site-packages’]

sys.path 傳回的是一個清單,其中第一項,代表目前目錄(如果從解釋器中執行的話,會是一個空字元串),也就是這個.py檔案的所在路徑。

Python的from語句可以讓你從子產品中導入指定的函數或變量到目前的腳本中,文法如下:

from modname import name1[, name2[, … nameN]]

例如,要導入 model子產品的 println函數,代碼示例:

<code>from</code> <code>model </code><code>import</code> <code>println</code>

這個聲明不會把整個model子產品導入到目前的腳本中,它隻會将model裡的println函數引入進來,然後我們就可以直接對這個函數進行調用了。 

如果要導入多個函數或者變量的話,需要使用逗号隔開。

把一個子產品的所有内容全都導入到目前的腳本中是可行的,隻需使用如下聲明:

from modname import *

這提供了一個簡單的方法來導入一個子產品中的所有項目。然而這種聲明不該被過多地使用。

每一個子產品或者說腳本檔案都會有一個主程式或者說代碼的執行起點,類似于Java、C/C++、C#中的main方法,當腳本運作時,這個主程式就會被執行。這個主程式在執行時會被配置設定一個名字,但是這個名字并非是固定的,例如:在作為一個腳本被執行時,這個主程式的名字為_main_,在另一個腳本被做為一個導入的子產品執行時,這個主程式的名字就為子產品的名稱。而相對的每一個腳本都有一個自己的_name_屬性,這個屬性的值對應着目前主程式的名稱,下面用實際例子示範一下:

<code>#filename:TestMould.py</code>

<code>if</code> <code>__name__ </code><code>=</code><code>=</code> <code>'__main__'</code><code>:  </code><code># 作為一個腳本執行時主程式的名稱為__main__</code>

<code>    </code><code>print</code><code>(__name__)</code>

<code>    </code><code>print</code><code>(</code><code>"我被作為目前腳本運作"</code><code>)</code>

<code>else</code><code>:</code>

<code>    </code><code>print</code><code>(</code><code>"我被作為另一個腳本中的一個子產品運作"</code><code>)</code>

 _main_   程式自身在運作

如果在另一個腳本中被當做一個子產品執行時,_name_ 屬性的值就不會為_main_:

<code>#filename:Hello.py</code>

<code>import</code> <code>TestMould  </code><code># 被作為另一個腳本的導入子產品執行時,主程式的名稱為子產品的名稱</code>

 TestMould   我被作為另一個腳本中的一個子產品運作

說明: 每個子產品(腳本)都有一個_name_屬性,當其值是’_main_’時,表明該子產品自身在運作也就是作為腳本在運作,否則就是被作為引入子產品在運作。而不管是作為腳本執行還是作為引入子產品執行,主程式都會被執行,隻不過名稱不一樣罷了。

dir()是一個内置的函數(BIF),這個函數可以找到導入的子產品内定義的所有函數和屬性、變量的名稱。然後以一個字元串清單的形式傳回:

10

11

12

13

14

<code>&gt;&gt;&gt; </code><code>import</code> <code>sys</code>

<code>&gt;&gt;&gt; </code><code>dir</code><code>(sys)</code>

<code>[</code><code>'__displayhook__'</code><code>, </code><code>'__doc__'</code><code>, </code><code>'__excepthook__'</code><code>, </code><code>'__name__'</code><code>, </code><code>'__package__'</code><code>, </code><code>'__stderr__'</code><code>, '__stdin</code>

<code>__</code><code>', '</code><code>__stdout__</code><code>', '</code><code>_clear_type_cache</code><code>', '</code><code>_current_frames</code><code>', '</code><code>_getframe</code><code>', '</code><code>_mercurial</code><code>', '</code><code>api_version</code>

<code>', '</code><code>argv</code><code>', '</code><code>builtin_module_names</code><code>', '</code><code>byteorder</code><code>', '</code><code>call_tracing</code><code>', '</code><code>callstats</code><code>', '</code><code>copyright</code><code>', '</code><code>displa</code>

<code>yhook</code><code>', '</code><code>dllhandle</code><code>', '</code><code>dont_write_bytecode</code><code>', '</code><code>exc_clear</code><code>', '</code><code>exc_info</code><code>', '</code><code>exc_type</code><code>', '</code><code>excepthook</code><code>', '</code><code>e</code>

<code>xec_prefix</code><code>', '</code><code>executable</code><code>', '</code><code>exit</code><code>', '</code><code>flags</code><code>', '</code><code>float_info</code><code>', '</code><code>float_repr_style</code><code>', '</code><code>getcheckinterval',</code>

<code> </code><code>'getdefaultencoding'</code><code>, </code><code>'getfilesystemencoding'</code><code>, </code><code>'getprofile'</code><code>, </code><code>'getrecursionlimit'</code><code>, </code><code>'getrefcount'</code><code>, </code>

<code> </code><code>'getsizeof'</code><code>, </code><code>'gettrace'</code><code>, </code><code>'getwindowsversion'</code><code>, </code><code>'hexversion'</code><code>, </code><code>'long_info'</code><code>, </code><code>'maxint'</code><code>, </code><code>'maxsize'</code><code>, '</code>

<code> </code><code>maxunicode</code><code>', '</code><code>meta_path</code><code>', '</code><code>modules</code><code>', '</code><code>path</code><code>', '</code><code>path_hooks</code><code>', '</code><code>path_importer_cache</code><code>', '</code><code>platform</code><code>', '</code>

<code> </code><code>prefix</code><code>', '</code><code>ps1</code><code>', '</code><code>ps2</code><code>', '</code><code>py3kwarning</code><code>', '</code><code>setcheckinterval</code><code>', '</code><code>setprofile</code><code>', '</code><code>setrecursionlimit</code><code>', '</code><code>s</code>

<code> </code><code>ettrace</code><code>', '</code><code>stderr</code><code>', '</code><code>stdin</code><code>', '</code><code>stdout</code><code>', '</code><code>subversion</code><code>', '</code><code>version</code><code>', '</code><code>version_info</code><code>', '</code><code>warnoptions',</code>

<code>  </code><code>'winver'</code><code>]</code>

<code>&gt;&gt;&gt;</code>

如果dir函數中不傳遞參數,那麼 dir() 函數會羅列出目前腳本中定義的所有函數、子產品、變量、屬性的名稱:

<code>&gt;&gt;&gt; </code><code>def</code> <code>test():</code>

<code>...     </code><code>print</code><code>(</code><code>"test"</code><code>)</code>

<code>... </code>

<code>&gt;&gt;&gt; a</code><code>=</code><code>123</code>

<code>&gt;&gt;&gt; </code><code>dir</code><code>()</code>

<code>[</code><code>'__builtins__'</code><code>, </code><code>'__doc__'</code><code>, </code><code>'__name__'</code><code>, </code><code>'__package__'</code><code>, </code><code>'a'</code><code>, </code><code>'sys'</code><code>, </code><code>'test'</code><code>]</code>

Python 本身帶着一些标準的子產品庫,之前也提到過,具體的标準子產品會在一篇單獨的文章中介紹一些常用的(因為太多了)。 

有些子產品直接被建構在解析器裡,這些雖然不是一些語言内置的功能,但是他卻能很高效的使用,甚至是系統級調用也沒問題。 

這些元件會根據不同的作業系統進行不同形式的配置,比如 winreg 這個子產品就隻會提供給 Windows 系統。 

應該注意到這有一個特别的子產品 sys,sys是System(系統)的縮寫 ,它内置在每一個 Python 解析器中。變量 sys.ps1 和 sys.ps2 定義了主提示符和副提示符所對應的字元串:

<code>&gt;&gt;&gt; sys.ps1  </code><code># 解釋器中的主提示符</code>

<code>'&gt;&gt;&gt; '</code>

<code>&gt;&gt;&gt; sys.ps2  </code><code># 解釋器中的副提示符(當我們寫一個函數時就會顯示這個副提示符)</code>

<code>'... '</code>

<code>&gt;&gt;&gt; sys.ps1 </code><code>=</code> <code>'C&gt; '</code>  <code># 把解釋器中的主提示符更改為'C&gt; '</code>

<code>C&gt; </code><code>print</code><code>(</code><code>'Yuck!'</code><code>)</code>

<code>Yuck!</code>

<code>C&gt;</code>

包是一種管理 Python 子產品命名空間的形式,類似于一個檔案夾,而這個檔案夾下會有很多子檔案,這些子檔案就是一個個的子產品。當我們需要使用一個包下的某個子產品時,和其他程式設計語言一樣需要使用 . 來作為通路符。 

比如一個子產品的名稱是 A.B, 那麼他表示一個包 A中的子子產品 B 。 

就好像使用子產品的時候,你不用擔心不同子產品之間的全局變量互相影響一樣,采用點子產品名稱這種形式也不用擔心不同庫之間的子產品重名的情況。 

這樣不同的作者都可以提供 NumPy 子產品,或者是 Python 圖形庫。 

不妨假設你想設計一套統一處理聲音檔案和資料的子產品(或者稱之為一個”包”)。 

現存很多種不同的音頻檔案格式(基本上都是通過字尾名區分的,例如: .wav,:file:.aiff,:file:.au,),是以你需要有一組不斷增加的子產品,用來在不同的格式之間轉換。 

并且針對這些音頻資料,還有很多不同的操作(比如混音,添加回聲,增加均衡器功能,建立人造立體聲效果),所你還需要一組怎麼也寫不完的子產品來處理這些操作。 

這裡給出了一種可能的包結構(在分層的檔案系統中):

在導入一個包的時候,Python 會根據 sys.path 中的目錄來尋找這個包中包含的子目錄。 

目錄隻有包含一個叫做 init.py 的檔案才會被認作是一個包,主要是為了避免一些濫俗的名字(比如叫做 string)不小心的影響搜尋路徑中的有效子產品。 

最簡單的情況,放一個空的_init_.py檔案就可以了。當然這個檔案中也可以包含一些初始化代碼或者為(将在後面介紹的) _all_變量指派。 

使用者可以每次隻導入一個包裡面的特定子產品,示例:

<code>import</code> <code>sound.effects.echo    </code><code># sound為頂層包,effects為其子包,echo 為子包下的一個子產品</code>

上面這段代碼将會導入這樣形式的子子產品:sound.effects.echo。 他必須使用全名去通路子產品中的函數或屬性:

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

還有另一種導入子子產品的方法是:

<code>from</code> <code>sound.effects </code><code>import</code> <code>echo</code>

上面這段代碼會導入這樣形式的子子產品: echo,但是他不需要那些冗長的字首,是以他可以這樣去通路子產品中的函數或屬性:

echo.echofilter(input, output, delay=0.7, atten=4)

還有一種方式就是直接導入一個具體的函數或者變量:

<code>from</code> <code>sound.effects.echo </code><code>import</code> <code>echofilter</code>

同樣的,這種方法會導入子子產品: echo,并且可以直接使用他的 echofilter() 函數:

echofilter(input, output, delay=0.7, atten=4)

注意當使用from package import item這種形式的時候,對應的item既可以是包裡面的子子產品(子包),或者包裡面定義的其他名稱,比如函數,類或者變量。 

import文法會首先把item當作一個包定義的名稱,如果沒找到,再試圖按照一個子產品去導入。如果還沒找到,恭喜一個:ImportError 異常被抛出了。 

反之,如果使用形如import item.subitem.subsubitem這種導入形式,除了最後一項,都必須是包,而最後一項則可以是子產品或者是包,但是不可以是類,函數或者變量的名字。

設想一下,如果我們使用 from sound.effects import * 會發生什麼? 

Python 會進入檔案系統,找到這個包裡面所有的子子產品,一個一個的把它們都導入進來。 

但是很不幸,這個方法在 Windows平台上工作的就不是非常好,因為Windows是一個大小寫不區分的系統。 

在這類平台上,沒有人敢擔保一個叫做 ECHO.py 的檔案導入為子產品 echo 還是 Echo 甚至 ECHO。 

(例如,Windows 95就很讨厭的把每一個檔案的首字母大寫顯示)而且 DOS 的 8+3 命名規則對長子產品名稱的處理會把問題搞得更糾結。 

為了解決這個問題,隻能煩勞包作者提供一個精确的包的索引了。 

導入語句遵循如下規則:如果包定義檔案 _init_.py 存在一個叫做 _all_ 的清單變量,那麼在使用 from package import * 的時候就把這個清單中的所有名字作為包内容導入。 

作為包的作者,可别忘了在更新包之後保證 _all_ 也更新了啊。你說我就不這麼做,我就不使用導入*這種用法,好吧,沒問題,誰讓你是老闆呢。這裡有一個例子,在 sounds/effects/_init_.py中包含如下代碼:

<code>__all__ </code><code>=</code> <code>[</code><code>"echo"</code><code>, </code><code>"surround"</code><code>, </code><code>"reverse"</code><code>]</code>

這表示當你使用from sound.effects import *這種用法時,你隻會導入包裡面這三個子子產品。 

如果 _all_ 真的沒有定義,那麼使用from sound.effects import *這種文法的時候,就不會導入包 sound.effects 裡的任何子子產品。他隻是把包sound.effects和它裡面定義的所有内容導入進來(可能運作_init_.py裡定義的初始化代碼)。 

這會把 _init_.py 裡面定義的所有名字導入進來。并且他不會破壞掉我們在這句話之前導入的所有明确指定的子產品。看下這部分代碼:

<code>import</code> <code>sound.effects.echo</code>

<code>import</code> <code>sound.effects.surround</code>

<code>from</code> <code>sound.effects </code><code>import</code> <code>*</code>

這個例子中,在執行from…import前,包sound.effects中的echo和surround子產品都被導入到目前的命名空間中了。(當然如果定義了_all_就更沒問題了) 

通常我們并不主張使用*這種方法來導入子產品,因為這種方法經常會導緻代碼的可讀性降低。不過這樣倒的确是可以省去不少敲鍵的功夫,而且一些子產品都設計成了隻能通過特定的方法導入。 

記住,使用from Package import specific_submodule這種方法永遠不會有錯。事實上,這也是推薦的方法。除非是你要導入的子子產品有可能和其他包的子子產品重名。 

如果在結構中包是一個子包(比如這個例子中對于包sound來說),而你又想導入兄弟包(同級别的包)你就得使用導入絕對的路徑來導入。比如,如果子產品sound.filters.vocoder 要使用包sound.effects中的子產品echo,你就要寫成 from sound.effects import echo。

<code>from</code> <code>. </code><code>import</code> <code>echo</code>

<code>from</code> <code>.. </code><code>import</code> <code>formats</code>

<code>from</code> <code>..filters </code><code>import</code> <code>equalizer</code>

無論是隐式的還是顯式的相對導入都是從目前子產品開始的。主子產品的名字永遠是”_main_”,一個Python應用程式的主子產品,應當總是使用絕對路徑引用。 

包還提供一個額外的屬性_path_。這是一個目錄清單,裡面每一個包含的目錄都有為這個包服務的_init_.py,你得在其他_init_.py被執行前定義哦。可以修改這個變量,用來影響包含在包裡面的子產品和子包。 

這個功能并不常用,一般用來擴充包裡面的子產品。

本文轉自 ZeroOne01 51CTO部落格,原文連結:http://blog.51cto.com/zero01/1978895,如需轉載請自行聯系原作者