文章目錄
- 1 模塊和包
- 1.1 模塊和包
- 1.1.1 模塊
- 1.1.2 包
- 1.1.3 簡單使用
- 1.2 import 語句
- 1.2.1 import
- 1.2.2 from … import 語句
- 1.2.3 from … import * 語句
- 1.4 深入模塊
- 1.4.1 模塊符號表
- 1.4.2 __name__屬性
- 1.4.3 dir() 函數
- 1.4.4 作用域
- 1.5 常用內置模塊
- 1.5.1 collections
- 1.5.1.1 namedtuple
1 模塊和包
1.1 模塊和包
1.1.1 模塊
在計算機程序的開發過程中,隨著程序代碼越寫越多,在一個文件里代碼就會越來越長,越來越不容易維護。
為了編寫可維護的代碼,我們把很多函數分組,分別放到不同的文件里,這樣,每個文件包含的代碼就相對較少,很多編程語言都采用這種組織代碼的方式。在Python中,一個.py
文件就稱之為一個模塊(Module
)。
模塊
是一個包含所有定義的函數和變量
的文件,其后綴名是.py
。模塊可以被別的程序引入,以使用該模塊中的函數等功能。
1.1.2 包
最大的好處是大大提高了代碼的可維護性。其次,編寫代碼不必從零開始。當一個模塊編寫完畢,就可以被其他地方引用。我們在編寫程序的時候,也經常引用其他模塊,包括Python內置的模塊和來自第三方的模塊。
使用模塊還可以避免函數名
和變量名
沖突。相同名字的函數和變量完全可以分別存在不同的模塊中,因此,我們自己在編寫模塊時,不必考慮名字會與其他模塊沖突。但是也要注意,盡量不要與內置函數名字沖突
但是如果不同的人編寫的模塊名相同怎么辦,為了避免模塊名沖突,Python
又引入了按目錄來組織模塊的方法,稱為包(Packag
)。
包
是一種管理 Python
模塊命名空間的形式,采用點模塊名稱
。比如一個模塊的名稱是 A.B
, 那么表示一個包 A中的子模塊 B 。就好像使用模塊的時候,不用擔心不同模塊之間的全局變量相互影響一樣,采用點模塊名稱這種形式也不用擔心不同庫之間的模塊重名的情況。
舉個例子,一個abc.py的文件就是一個名字叫abc的模塊,一個xyz.py的文件就是一個名字叫xyz的模塊。
現在,假設我們的abc和xyz這兩個模塊名字與其他模塊沖突了,于是我們可以通過包來組織模塊,避免沖突。方法是選擇一個頂層包名,比如mycompany,按照如下目錄存放:
mycompany__init__.pyabc.pyxyz.ph
引入了包以后,只要頂層的包名不與別人沖突,那所有模塊都不會與別人沖突。現在,abc.py模塊的名字就變成了mycompany.abc,類似的,xyz.py的模塊名變成了mycompany.xyz。
注意
,每一個包目錄下面都會有一個__init__.py
的文件,這個文件是必須存在的,否則,Python就把這個目錄當成普通目錄,而不是一個包。__init__.py
可以是空文件,也可以有Python代碼,因為__init__.py
本身就是一個模塊,而它的模塊名就是mycompany
。
在導入一個包的時候,Python
會根據 sys.path
中的目錄來尋找這個包中包含的子目錄。
默認情況下,Python解釋器
會搜索當前目錄、所有已安裝的內置模塊和第三方模塊
,搜索路徑存放在sys模塊的path變量中。
目錄只有包含一個叫做 __init__.py
的文件才會被認作是一個包,主要是為了避免一些濫俗的名字(比如叫做 string)不小心的影響搜索路徑中的有效模塊。
最簡單的情況,放一個空的:file:__init__.py
就可以了。當然這個文件中也可以包含一些初始化代碼或者為 __all__
變量賦值。
注意
:當使用 from package import item 這種形式的時候,對應的 item 既可以是包里面的子模塊(子包),或者包里面定義的其他名稱,比如函數,類或者變量。
import
語法會首先把 item
當作一個包定義的名稱,如果沒找到,再試圖按照一個模塊去導入。如果還沒找到,拋出一個 :exc:ImportError
異常。如果使用形如 import item.subitem.subsubitem
這種導入形式,除了最后一項,都必須是包,而最后一項則可以是模塊或者是包,但是不可以是類,函數或者變量的名字。
無論是隱式的還是顯式的相對導入都是從當前模塊開始的。主模塊的名字永遠是__main__
,一個Python應用程序的主模塊,應當總是使用絕對路徑引用。
包還提供一個額外的屬性__path__
。這是一個目錄列表,里面每一個包含的目錄都有為這個包服務的__init__.py
。可以修改這個變量,用來影響包含在包里面的模塊和子包。
1.1.3 簡單使用
下面是一個使用 python 標準庫中模塊的例子。
#!/usr/bin/python3
# 文件名: using_sys.py
'模塊說明'
__author__ = '開發者名字'import sysprint('命令行參數如下:')
for i in sys.argv:print(i)print('\n\nPython 路徑為:', sys.path, '\n')執行結果如下所示:
$ python using_sys.py 參數1 參數2
命令行參數如下:
using_sys.py
參數1
參數2Python 路徑為: ['/root', '/usr/lib/python3.4', '/usr/lib/python3.4/plat-x86_64-linux-gnu', '/usr/lib/python3.4/lib-dynload', '/usr/local/lib/python3.4/dist-packages', '/usr/lib/python3/dist-packages']
import sys
引入python
標準庫中的sys.py
模塊;這是引入某一模塊的方法。sys.argv
是一個包含命令行參數的列表。sys.path
包含了一個 Python 解釋器自動查找所需模塊的路徑的列表。
1.2 import 語句
1.2.1 import
想使用 Python 源文件,只需在另一個源文件里執行 import 語句,語法如下:
import module1[, module2[,... moduleN]
當解釋器遇到 import
語句,如果模塊在當前的搜索路徑就會被導入。搜索路徑時一個解釋器會先進行搜索的所有目錄的列表。
一個模塊只會被導入一次,不管你執行了多少次 import
。這樣可以防止導入模塊被一遍又一遍地執行。
當我們使用 import
語句的時候,Python解釋器
是怎樣找到對應的文件的呢?
這就涉及到 Python 的搜索路徑,搜索路徑是由一系列
目錄名
組成的,Python解釋器
就依次從這些目錄中去尋找所引入的模塊。
這看起來很像環境變量,事實上,也可以通過定義環境變量的方式來確定搜索路徑。
搜索路徑是在Python 編譯或安裝
的時候確定的,安裝新的庫應該也會修改。搜索路徑被存儲在sys
模塊中的 path 變量,做一個簡單的demo,在交互式解釋器中,輸入以下代碼:
>>> import sys
>>> sys.path
['', '/usr/lib/python3.4', '/usr/lib/python3.4/plat-x86_64-linux-gnu', '/usr/lib/python3.4/lib-dynload', '/usr/local/lib/python3.4/dist-packages', '/usr/lib/python3/dist-packages']
>>>
sys.path
輸出是一個列表,其中第一項是空串 ''
,代表當前目錄(若是從一個腳本中打印出來的話,可以更清楚地看出是哪個目錄),亦即我們執行python解釋器
的目錄(對于腳本的話就是運行的腳本所在的目錄)。
因此若在當前目錄下存在與要引入模塊同名的文件,就會把要引入的模塊屏蔽掉。
1.2.2 from … import 語句
Python 的 from
語句可以從模塊中導入一個指定的部分到當前命名空間中,語法如下:from modname import name1[, name2[, ... nameN]]
例如,要導入模塊 fibo 的 fib 函數,使用如下語句:
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
這個聲明不會把整個fibo模塊導入到當前的命名空間中,它只會將fibo里的fib函數
引入進來。
1.2.3 from … import * 語句
把一個模塊的所有內容全都導入到當前的命名空間也是可行的,只需使用如下聲明:from modname import *
這提供了一個簡單的方法來導入一個模塊中的所有項目。然而這種聲明不該被過多地使用。
1.4 深入模塊
1.4.1 模塊符號表
模塊除了方法定義
,還可以包括可執行的代碼
。這些代碼一般用來初始化這個模塊。這些代碼只有在第一次被導入時才會被執行。
每個模塊有各自獨立的符號表
,在模塊內部為所有的函數當作全局符號表來使用。
所以,模塊的開發者可以放心大膽的在模塊內部使用這些全局變量,而不用擔心把其他用戶的全局變量搞混。
從另一個方面,可以通過 modname.itemname
這樣的表示法來訪問模塊內的函數。
模塊是可以導入其他模塊的。在一個模塊(或者腳本,或者其他地方)的最前面使用 import
來導入一個模塊,當然這只是一個慣例,而不是強制的。被導入的模塊的名稱將被放入當前操作的模塊的符號表中。
使用 import
直接把模塊內(函數,變量的)名稱導入到當前操作模塊。但是,這種導入的方法不會把被導入的模塊的名稱放在當前的字符表中(所以在這個例子里面,fibo 這個名稱是沒有定義的)
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
一次性的把模塊中的所有(函數,變量)名稱都導入到當前模塊的字符表:
>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
這將把所有的名字都導入進來,但是那些由單一下劃線(_
)開頭的名字不在此例。大多數情況, Python程序員不使用這種方法,因為引入的其它來源的命名,很可能覆蓋了已有的定義。
1.4.2 __name__屬性
一個模塊被另一個程序第一次引入時,其主程序將運行。如果我們想在模塊被引入時,模塊中的某一程序塊不執行,我們可以用__name__
屬性來使該程序塊僅在該模塊自身運行時執行。
#!/usr/bin/python3
# Filename: using_name.pyif __name__ == '__main__':print('程序自身在運行')
else:print('我來自另一模塊')
運行輸出如下:$ python using_name.py
程序自身在運行
$ python
>>> import using_name
我來自另一模塊
說明: 每個模塊都有一個__name__
屬性,當其值是__main__
時,表明該模塊自身在運行,否則是被引入。
1.4.3 dir() 函數
內置的函數 dir()
可以找到模塊內定義的所有名稱。以一個字符串列表的形式返回:
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__loader__', '__name__','__package__', '__stderr__', '__stdin__', '__stdout__','_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe','_home', '_mercurial', '_xoptions', 'abiflags', 'api_version', 'argv','base_exec_prefix', 'base_prefix', 'builtin_module_names', 'byteorder','call_tracing', 'callstats', 'copyright', 'displayhook','dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix','executable', 'exit', 'flags', 'float_info', 'float_repr_style','getcheckinterval', 'getdefaultencoding', 'getdlopenflags','getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit','getrefcount', 'getsizeof', 'getswitchinterval', 'gettotalrefcount','gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info','intern', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path','path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1','setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit','setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout','thread_info', 'version', 'version_info', 'warnoptions']
如果沒有給定參數,那么 dir() 函數會羅列出當前定義的所有名稱:
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir() # 得到一個當前模塊中定義的屬性列表
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
>>> a = 5 # 建立一個新的變量 'a'
>>> dir()
['__builtins__', '__doc__', '__name__', 'a', 'sys']
>>>
>>> del a # 刪除變量名a
>>>
>>> dir()
['__builtins__', '__doc__', '__name__', 'sys']
1.4.4 作用域
在一個模塊中,我們可能會定義很多函數和變量,但有的函數和變量我們希望給別人使用,有的函數和變量我們希望僅僅在模塊內部使用。在Python中,是通過_
前綴來實現的。
正常的函數和變量名是公開的(public),可以被直接引用,比如:abc,x123,PI等;
類似 __xxx__
這樣的變量是特殊變量,可以被直接引用,但是有特殊用途,比如上面的 __author__
,__name__
就是特殊變量,hello模塊定義的文檔注釋也可以用特殊變量 __doc__
訪問,我們自己的變量一般不要用這種變量名;
類似_xxx
和__xxx
這樣的函數或變量就是非公開的(private),不應該被直接引用,比如_abc,__abc等;
之所以我們說,private函數和變量“不應該”被直接引用,而不是不能
被直接引用,是因為Python并沒有一種方法可以完全限制訪問private函數或變量,但是,從編程習慣上不應該引用private函數或變量。
private
函數或變量不應該被別人引用,那它們有什么用呢?請看例子:
def _private_1(name):return 'Hello, %s' % namedef _private_2(name):return 'Hi, %s' % namedef greeting(name):if len(name) > 3:return _private_1(name)else:return _private_2(name)
我們在模塊里公開greeting()函數,而把內部邏輯用private函數隱藏起來了,這樣,調用greeting()函數不用關心內部的private函數細節,這也是一種非常有用的代碼封裝和抽象的方法,外部不需要引用的函數全部定義成 private
,只有外部需要引用的函數才定義為 public
1.5 常用內置模塊
1.5.1 collections
collections
是Python內建的一個集合模塊,提供了許多有用的集合類。
1.5.1.1 namedtuple
我們知道tuple可以表示不變集合,例如,一個點的二維坐標就可以表示成:p = (1, 2)
但是,看到(1, 2),很難看出這個tuple
是用來表示一個坐標的。
定義一個class又小題大做了,這時,namedtuple就派上了用場:
>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(1, 2)
>>> p.x
1
>>> p.y
2
namedtuple
是一個函數,它用來創建一個自定義的tuple
對象,并且規定了tuple元素的個數,并可以用屬性而不是索引來引用tuple
的某個元素。
這樣一來,我們用namedtuple
可以很方便地定義一種數據類型,它具備tuple
的不變性,又可以根據屬性來引用,使用十分方便。
可以驗證創建的Point對象是tuple的一種子類:
>>> isinstance(p, Point)
True
>>> isinstance(p, tuple)
True
類似的,如果要用坐標和半徑表示一個圓,也可以用namedtuple定義:
# namedtuple('名稱', [屬性list]):
Circle = namedtuple('Circle', ['x', 'y', 'r'])
deque
使用list存儲數據時,按索引訪問元素很快,但是插入和刪除元素就很慢了,因為list是線性存儲,數據量大的時候,插入和刪除效率很低。
deque是為了高效實現插入和刪除操作的雙向列表,適合用于隊列和棧:
from collections import deque
q = deque([‘a’, ‘b’, ‘c’])
q.append(‘x’)
q.appendleft(‘y’)
q
deque([‘y’, ‘a’, ‘b’, ‘c’, ‘x’])
deque除了實現list的append()和pop()外,還支持appendleft()和popleft(),這樣就可以非常高效地往頭部添加或刪除元素。
defaultdict
使用dict時,如果引用的Key不存在,就會拋出KeyError。如果希望key不存在時,返回一個默認值,就可以用defaultdict:
from collections import defaultdict
dd = defaultdict(lambda: ‘N/A’)
dd[‘key1’] = ‘abc’
dd[‘key1’] # key1存在
‘abc’
dd[‘key2’] # key2不存在,返回默認值
‘N/A’
注意默認值是調用函數返回的,而函數在創建defaultdict對象時傳入。
除了在Key不存在時返回默認值,defaultdict的其他行為跟dict是完全一樣的。
OrderedDict
使用dict時,Key是無序的。在對dict做迭代時,我們無法確定Key的順序。
如果要保持Key的順序,可以用OrderedDict:
from collections import OrderedDict
d = dict([(‘a’, 1), (‘b’, 2), (‘c’, 3)])
d # dict的Key是無序的
{‘a’: 1, ‘c’: 3, ‘b’: 2}
od = OrderedDict([(‘a’, 1), (‘b’, 2), (‘c’, 3)])
od # OrderedDict的Key是有序的
OrderedDict([(‘a’, 1), (‘b’, 2), (‘c’, 3)])
注意,OrderedDict的Key會按照插入的順序排列,不是Key本身排序:
od = OrderedDict()
od[‘z’] = 1
od[‘y’] = 2
od[‘x’] = 3
od.keys() # 按照插入的Key的順序返回
[‘z’, ‘y’, ‘x’]
OrderedDict可以實現一個FIFO(先進先出)的dict,當容量超出限制時,先刪除最早添加的Key:
from collections import OrderedDict
class LastUpdatedOrderedDict(OrderedDict):
def __init__(self, capacity):super(LastUpdatedOrderedDict, self).__init__()self._capacity = capacitydef __setitem__(self, key, value):containsKey = 1 if key in self else 0if len(self) - containsKey >= self._capacity:last = self.popitem(last=False)print 'remove:', lastif containsKey:del self[key]print 'set:', (key, value)else:print 'add:', (key, value)OrderedDict.__setitem__(self, key, value)
Counter
Counter是一個簡單的計數器,例如,統計字符出現的個數:
from collections import Counter
c = Counter()
for ch in ‘programming’:
… c[ch] = c[ch] + 1
…
c
Counter({‘g’: 2, ‘m’: 2, ‘r’: 2, ‘a’: 1, ‘i’: 1, ‘o’: 1, ‘n’: 1, ‘p’: 1})
Counter實際上也是dict的一個子類,上面的結果可以看出,字符’g’、‘m’、'r’各出現了兩次,其他字符各出現了一次。
小結
collections模塊提供了一些有用的集合類,可以根據需要選用。