天天看點

簡明Python教程學習筆記_3_子產品子產品包安裝第三方子產品使用__future__

如果你想要在其他程式中重用很多函數,那麼你該如何編寫程式呢?你可能已經猜到了,答案是使用子產品。子產品基本上就是一個包含了所有你定義的函數和變量的檔案。為了在其他程式中重用子產品,子產品的檔案名必須以<code>.py</code>為擴充名。

python有三種導入子產品的方法

其一,

import modname : 子產品是指一個可以互動使用,或者從另一python 程式通路的代碼段。隻要導入了一個子產品,就可以引用它的任何公共的函數、類或屬性。子產品可以通過這種方法來使用其它子產品的功能。

用import語句導入子產品,就在目前的名稱空間(namespace)建立了一個到該子產品的引用.這種引用必須使用全稱,也就是說,當使用在被導入子產品中定義的函數時,必須包含子產品的名字。是以不能隻使用 funcname,而應該使用 modname.funcname

其二,

from modname import funcname

from modname import fa, fb, fc

或者 from modname import *

與第1種方法的差別:funcname 被直接導入到本地名字空間去了,是以它可以直接使用,而不需要加上子產品名的限定

* 表示,該子產品的所有公共對象(public objects)都被導入到 目前的名稱空間,也就是任何隻要不是以”_”開始的東西都會被導入。

modname沒有被定義,是以modname.funcname這種方式不起作用。并且,如果funcname如果已經被定義,它會被新版本(該導入子產品中的版本)所替代。如果funcname被改成指向其他對象,modname不能不會覺察到。

建議:

1)如果你要經常通路子產品的屬性和方法,且不想一遍又一遍地敲入子產品名,使用 from module import

2)如果你想要有選擇地導入某些屬性和方法,而不想要其它的,使用 from module import

3)如果子產品包含的屬性和方法與你的某個子產品同名,你必須使用import module來避免名字沖突

4)盡量少用 from module import * ,因為判定一個特殊的函數或屬性是從哪來的有些困難,并且會造成調試和重構都更困難。

其三

内建函數__import__()

除了前面兩種使用import關鍵字的方法以外,我們還可以使用内建函數 __import__() 來導入 module。兩者的差別是,import 後面跟的必須是一個類型(type),而__import__() 的參數是一個字元串,這個字元串可能來自配置檔案,也可能是某個表達式計算結果。例如

mymodule = __import__ (’module_name’)

附注:

1)子產品的内容都放在一個子產品檔案中,如 mymodule 的内容應該放在pythonpath 目錄下的一個mymodule.py中,c實作的除外

2)包可以将幾個子產品名稱空間組織起來, 如a.b 就表示在包a中的一個子子產品b

可以單獨導入某一個子子產品,如python文檔中給出的例子

import sound.effects.echo

這樣必須使用全稱對裡面的對象進行引用,如

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

還可以使用下面的語句來加載echo子子產品

from sound.effects import echo

它在沒有包字首的情況下也可以使用, 是以它可以如下方式調用:

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

不主張從一個包或子產品中用import * 導入所有子產品,因為這樣的通常會導緻可讀性很差。

from package import specific_submodule的用法并沒有錯,實際上這還是推薦的用法,除非導入的子產品需要使用其它包中的同名子子產品(the importing module needs to use submodules with the same name from different packages).

綜上所述,一般情況應該使用import , 但有幾個例外

1)module文檔告訴你要用from-import的

2)導入一個包元件。需要一個包裡面的某個子子產品,一般用from a.b import c比import a.b.c 更友善 且不會冒混淆的危險.

使用子產品

在python中用關鍵字import來引入某個子產品,比如要引用子產品math,就可以在檔案最開始的地方用import math來引入。在調用math子產品中的函數時,必須這樣引用:

子產品名.函數名

為什麼必須加上子產品名這樣調用呢?因為可能存在這樣一種情況:在多個子產品中含有相同名稱的函數,此時如果隻是通過函數名來調用,解釋器無法知道到底要調用哪個函數。是以如果像上述這樣引入子產品的時候,調用函數必須加上子產品名。

有時候我們隻需要用到子產品中的某個函數,隻需要引入該函數即可,此時可以通過語句

  from 子產品名 import 函數名1,函數名2....

當然可以通過不僅僅可以引入函數,還可以引入一些常量。通過這種方式引入的時候,調用函數時隻能給出函數名,不能給出子產品名,但是當兩個子產品中含有相同名稱函數的時候,後面一次引入會覆寫前一次引入。也就是說假如子產品a中有函數function( ),在子產品b中也有函數function( ),如果引入a中的function在先、b中的function在後,那麼當調用function函數的時候,是去執行子產品b中的function函數。

  如果想一次性引入math中所有的東西,還可以通過from math import *來實作,但是不建議這麼做。

python本身就内置了很多非常有用的子產品,隻要安裝完畢,這些子產品就可以立刻使用。以内建的sys子產品為例,編寫一個hello的子產品:

使用sys子產品的第一步,就是導入該子產品:

import sys

導入sys子產品後,我們就有了變量sys指向該子產品,利用sys這個變量,就可以通路sys子產品的所有功能。

sys子產品有一個argv變量,用list存儲了指令行的所有參數。argv至少有一個元素,因為第一個參數永遠是該.py檔案的名稱,

例如:

運作python hello.py獲得的sys.argv就是['hello.py'];

運作python hello.py 123abc獲得的sys.argv就是['hello.py', '123abc']。

最後,注意到這兩行代碼:

當我們在指令行運作hello子產品檔案時,python解釋器把一個特殊變量__name__置為__main__。

而如果在其他地方導入該hello子產品時,if判斷将失敗。

是以,這種if測試可以讓一個子產品通過指令行運作時執行一些額外的代碼,最常見的就是運作測試。

一個子產品頂層定義的變量,會自動變成子產品的屬性

data變量就是子產品的一個屬性。其實printme也是一個屬性,隻不過是一個函數罷了。

别名

導入子產品時,還可以使用别名,這樣,可以在運作時根據目前環境選擇最合适的子產品。

比如python标準庫一般會提供stringio和cstringio兩個庫,這兩個庫的接口和功能是一樣的,但是cstringio是c寫的,速度更快,是以,你會經常看到這樣的寫法:

這樣就可以優先導入cstringio。如果有些平台不提供cstringio,還可以降級使用stringio。導入cstringio時,用import ... as ...指定了别名stringio,是以,後續代碼引用stringio即可正常工作。

還有類似simplejson這樣的庫,在python 2.6之前是獨立的第三方庫,從2.6開始内置,是以,會有這樣的寫法:

由于python是動态語言,函數簽名一緻接口就一樣,是以,無論導入哪個子產品後續代碼都能正常工作。

作用域

在一個子產品中,會定義很多函數和變量,但有的函數和變量希望給别人使用,有的函數和變量僅僅在子產品内部使用。在python中,是通過_字首來實作的。

正常的函數和變量名是公開的(public),可以被直接引用,比如:abc,x123,pi等;

類似__xxx__這樣的變量是特殊變量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊變量,hello子產品定義的文檔注釋也可以用特殊變量__doc__通路,我們自己的變量一般不要用這種變量名;

類似_xxx和__xxx這樣的函數或變量就是非公開的(private),不應該被直接引用,比如_abc,__abc等;

之是以說,private函數和變量“不應該”被直接引用,而不是“不能”被直接引用,是因為python并沒有一種方法可以完全限制通路private函數或變量,但是,從程式設計習慣上不應該引用private函數或變量。

private函數或變量不應該被别人引用,那它們有什麼用呢?請看例子:

我們在子產品裡公開greeting()函數,而把内部邏輯用private函數隐藏起來了,這樣,調用greeting()函數不用關心内部的private函數細節,這也是一種非常有用的代碼封裝和抽象的方法,即:外部不需要引用的函數全部定義成private,隻有外部需要引用的函數才定義為public。

它如何工作

首先,我們利用<code>import</code>語句 輸入 <code>sys</code>子產品。基本上,這句語句告訴python,我們想要使用這個子產品。<code>sys</code>子產品包含了與python解釋器和它的環境有關的函數。

當python執行<code>import sys</code>語句的時候,它在<code>sys.path</code>變量中所列目錄中尋找<code>sys.py</code>子產品。如果找到了這個檔案,這個子產品的主塊中的語句将被運作,然後這個子產品将能夠被你使用 。注意,初始化過程僅在我們第一次 輸入子產品的時候進行。另外,“sys”是“system”的縮寫。

<code>sys</code>子產品中的<code>argv</code>變量通過使用點号指明——<code>sys.argv</code>——這種方法的一個優勢是這個名稱不會與任何在你的程式中使用的<code>argv</code>變量沖突。另外,它也清晰地表明了這個名稱是<code>sys</code>子產品的一部分。

如果你使用ide編寫運作這些程式,請在菜單裡尋找一個指定程式的指令行參數的方法。

這裡,當我們執行<code>python using_sys.py we are arguments</code>的時候,我們使用python指令運作<code>using_sys.py</code>子產品,後面跟着的内容被作為參數傳遞給程式。python為我們把它存儲在<code>sys.argv</code>變量中。

記住,腳本的名稱總是<code>sys.argv</code>清單的第一個參數。是以,在這裡,<code>'using_sys.py'</code>是<code>sys.argv[0]</code>、<code>'we'</code>是<code>sys.argv[1]</code>、<code>'are'</code>是<code>sys.argv[2]</code>以及<code>'arguments'</code>是<code>sys.argv[3]</code>。注意,python從0開始計數,而非從1開始。

<code>sys.path</code>包含輸入子產品的目錄名清單。我們可以觀察到<code>sys.path</code>的第一個字元串是空的——這個空的字元串表示目前目錄也是<code>sys.path</code>的一部分,這與<code>pythonpath</code>環境變量是相同的。這意味着你可以直接輸入位于目前目錄的子產品。否則,你得把你的子產品放在<code>sys.path</code>所列的目錄之一。

from..import語句

如果你想要直接輸入<code>argv</code>變量到你的程式中(避免在每次使用它時打<code>sys.</code>),那麼你可以使用<code>from sys import argv</code>語句。如果你想要輸入所有<code>sys</code>子產品使用的名字,那麼可以使用<code>from sys import *</code>語句。這對于所有子產品都适用。一般說來,避免使用<code>from..import</code>而使用<code>import</code>語句,因為這樣可以使你的程式更加易讀,也可以避免名稱的沖突。

下面是一個使用<code>from..import</code>文法的版本。

命名空間和作用域

變量是擁有比對對象的名字(辨別符)。

命名空間是一個包含了變量名稱們(鍵)和它們各自相應的對象們(值)的字典。

一個python表達式可以通路局部命名空間和全局命名空間裡的變量。同名隐藏的原則同c/c++

每個函數都有自己的命名空間。類的方法的作用域規則和通常函數的一樣。預設任何在函數内指派的變量都是局部的。是以,如果要給全局變量在一個函數裡指派,必須使用global語句。global varname的表達式會告訴python, varname是一個全局變量,這樣python就不會在局部命名空間裡尋找這個變量了。

例如,我們在全局命名空間裡定義一個變量money。我們再在函數内給變量money指派,然後python會假定money是一個局部變量。然而,我們并沒有在通路前聲明一個局部變量money,結果就是會出現一個unboundlocalerror的錯誤。 取消global語句的注釋就能解決這個問題。

globals()和locals()函數

根據調用地方的不同,globals()和locals()函數可被用來傳回全局和局部命名空間裡的名字。

如果在函數内部調用locals(),傳回的是所有能在該函數裡通路的命名。

如果在函數内部調用globals(),傳回的是所有在該函數裡能通路的全局名字。

兩個函數的傳回類型都是字典。是以名字們能用keys()函數摘取

子產品的__name__

每個子產品都有一個名稱,在子產品中可以通過語句來找出子產品的名稱。這在一個場合特别有用——就如前面所提到的,當一個子產品被第一次輸入的時候,這個子產品的主塊将被運作。假如我們隻想在程式本身被使用的時候運作主塊,而在它被别的子產品輸入的時候不運作主塊,我們該怎麼做呢?這可以通過子產品的__name__屬性完成。

每個python子產品都有它的<code>__name__</code>,如果它是<code>'__main__'</code>,這說明這個子產品被使用者單獨運作,我們可以進行相應的恰當操作。

建立你自己的子產品

上面是一個 子產品 的例子。你已經看到,它與我們普通的python程式相比并沒有什麼特别之處。我們接下來将看看如何在我們别的python程式中使用這個子產品。

記住這個子產品應該被放置在我們輸入它的程式的同一個目錄中,或者在<code>sys.path</code>所列目錄之一。

在python中,一個.py檔案就稱之為一個子產品(module)。子產品的名字就是檔案的名字。

  比如有這樣一個檔案test.py,在test.py中定義了函數add:

那麼在其他檔案中就可以先import test,然後通過test.add(a,b)來調用了,當然也可以通過from test import add來引入。

dir()函數

你可以使用内建的<code>dir</code>函數來列出子產品定義的辨別符。辨別符有函數、類和變量。

當你為<code>dir()</code>提供一個子產品名的時候,它傳回子產品定義的名稱清單。如果不提供參數,它傳回目前子產品中定義的名稱清單。

首先,我們來看一下在輸入的<code>sys</code>子產品上使用<code>dir</code>。我們看到它包含一個龐大的屬性清單。

接下來,我們不給<code>dir</code>函數傳遞參數而使用它——預設地,它傳回目前子產品的屬性清單。注意,輸入的子產品同樣是清單的一部分。

為了觀察<code>dir</code>的作用,我們定義一個新的變量<code>a</code>并且給它賦一個值,然後檢驗<code>dir</code>,我們觀察到在清單中增加了以上相同的值。我們使用<code>del</code>語句删除目前子產品中的變量/屬性,這個變化再一次反映在<code>dir</code>的輸出中。

關于<code>del</code>的一點注釋——這個語句在運作後被用來 删除 一個變量/名稱。在這個例子中,<code>del a</code>,你将無法再使用變量<code>a</code>——它就好像從來沒有存在過一樣。

如果不同的人編寫的子產品名相同怎麼辦?為了避免子產品名沖突,python又引入了按目錄來組織子產品的方法,稱為包(package)。

舉個例子,一個abc.py的檔案就是一個名字叫abc的子產品,一個xyz.py的檔案就是一個名字叫xyz的子產品。

假設abc和xyz這兩個子產品名字與其他子產品沖突了,可以通過包來組織子產品,避免沖突。方法是選擇一個頂層包名,比如mycompany,按照如下目錄存放:

簡明Python教程學習筆記_3_子產品子產品包安裝第三方子產品使用__future__

引入了包以後,隻要頂層的包名不與别人沖突,那所有子產品都不會與别人沖突。

現在,abc.py子產品的名字就變成了mycompany.abc,類似的,xyz.py的子產品名變成了mycompany.xyz。

請注意,每一個包目錄下面都會有一個__init__.py的檔案,這個檔案是必須存在的,否則,python就把這個目錄當成普通目錄,而不是一個包。

__init__.py可以是空檔案,也可以有python代碼,因為__init__.py本身就是一個子產品,而它的子產品名就是mycompany。

類似的,可以有多級目錄,組成多級層次的包結構。比如如下的目錄結構:

簡明Python教程學習筆記_3_子產品子產品包安裝第三方子產品使用__future__

檔案www.py的子產品名就是mycompany.web.www,兩個檔案utils.py的子產品名分别是mycompany.utils和mycompany.web.utils。

mycompany.web也是一個子產品,請指出該子產品對應的.py檔案。

在python中,安裝第三方子產品,是通過setuptools這個工具完成的。python有兩個封裝了setuptools的包管理工具:easy_install和pip。目前官方推薦使用pip。

強烈推薦安裝 pip 安裝 python 第三方子產品

安裝一個第三方庫——python imaging library,這是python下非常強大的處理圖像的工具庫。

有了pil,處理圖檔易如反掌。随便找個圖檔生成縮略圖:

子產品搜尋路徑

預設情況下,python解釋器會搜尋目前目錄、所有已安裝的内置子產品和第三方子產品,搜尋路徑存放在sys子產品的path變量中:

如果要添加自己的搜尋目錄,有兩種方法:

一是直接修改sys.path,添加要搜尋的目錄:

這種方法是在運作時修改,運作結束後失效。

第二種方法是設定環境變量pythonpath,該環境變量的内容會被自動添加到子產品搜尋路徑中。設定方式與設定path環境變量類似。注意隻需要添加你自己的搜尋路徑,python自己本身的搜尋路徑不受影響。

python每個新版本都會增加一些新功能,或對原來功能作一些改動。有些改動是不相容舊版本的,即在目前版本運作正常的代碼,到下一個版本運作就可能不正常。

從python 2.7到python 3.x就有不相容的一些改動,比如2.x裡的字元串用'xxx'表示str,unicode字元串用u'xxx'表示unicode,而在3.x中,所有字元串都被視為unicode,是以,寫u'xxx'和'xxx'是完全一緻的,而在2.x中以'xxx'表示的str就必須寫成b'xxx',以此表示“二進制字元串”。

要直接把代碼更新到3.x是比較冒進的,因為有大量的改動需要測試。相反,可以在2.7版本中先在一部分代碼中測試一些3.x的特性,如果沒有問題,再移植到3.x不遲。

python提供了__future__子產品,把下一個新版本的特性導入到目前版本,于是我們就可以在目前版本中測試一些新版本的特性。舉例說明如下:

為了适應python 3.x的新的字元串的表示方法,在2.7版本的代碼中,可以通過unicode_literals來使用python 3.x的新的文法:

注意到上面的代碼仍然在python 2.7下運作,但結果顯示去掉字首u的'a string'仍是一個unicode,而加上字首b的b'a string'才變成了str:

類似的情況還有除法運算。在python 2.x中,對于除法有兩種情況,如果是整數相除,結果仍是整數,餘數會被扔掉,這種除法叫“地闆除”:

要做精确除法,必須把其中一個數變成浮點數:

而在python 3.x中,所有的除法都是精确除法,地闆除用//表示:

如果你想在python 2.7的代碼中直接使用python 3.x的除法,可以通過__future__子產品的division實作:

由于python是由社群推動的開源并且免費的開發語言,不受商業公司控制,是以,python的改進往往比較激進,不相容的情況時有發生。python為了確定你能順利過渡到新版本,特别提供了__future__子產品,讓你在舊的版本中試驗新版本的一些特性。