Python的标準安裝包括一組子產品,稱為标準庫。
10.1 子產品
>>>emport math
>>>math.sin(0)
0.0
10.1.1 子產品是程式
任何python程式都可以作為子產品導入。
#hello.py
print "hello,world!"
解釋器在哪裡尋找子產品。(windows)
>>>import sys
>>>sys.path.append('c:/python')
在unix系統中,不能隻簡單将字元串‘~/python’添加到sys.path中,必須使用完整路徑。如果你希望将這個操作自動化,可以使用sys.path.expanduser('~/python')
完成這樣之後就能導入自己的子產品了
>>>import hello
hello,world!
在導入子產品的時候,你可能會看到有新檔案出現-----在本例中是c:\python\hello.pyc。這個是以.pyc為擴充名的檔案是經過處理的,已經轉換成python能夠更加有效地處理的檔案。如果稍後導入同一個子產品,python會導入.pyc檔案而不是.py檔案,除非.py檔案已改變----在這種情況下,會生成新的.pyc檔案。删除.pyc檔案不會損害程式---必要的時候建立新的。
要重新載入hello子產品:
>>>hello = reload(hello)
python3.0已經去掉了reload函數。盡管用exec能夠實作同樣的功能,盡可能避免重新載入子產品。
10.1.2 子產品用于定義
1.在子產品中定義函數
#hello2.py
def hello():
>>>import hello2
子產品會被執行,這意味着hello函數在子產品的作用域被定義了,通路函數:
>>>hello2.hello()
為了讓代碼可重用,請将它子產品話。
2.在子產品中增加測試代碼
#hello3.py
#A test:
hello()
告知子產品本身是作為程式運作還是導入其他程式。為了實作這一點,需要使用__name__變量:
>>>__name__
'__main__'
>>>hello3.__name__
'hello3'
主程式中,變量__name__的值是‘__main__’.而在導入的子產品中,這個值就被設定為子產品的名字。是以,為了讓子產品的測試代碼更加好用,可以将其放置在if語句中。
#hello4.py
def test():
if __name__ == '__main__': test()
如果将它作為程式運作,hello函數會被執行。而作為函數會被執行。而作為子產品導入時,它的行為就會像普通子產品一樣:
>>>import hello4
>>>hello4.hello()
将測試代碼放入獨立的test函數會更靈活,這樣做即使再把子產品導入其他程式之後,仍然可以對其進行測試:
>>>hello4.test()
10.1.3 讓你的子產品可用
1.讓子產品放置在正确位置
>>>import sys,pprint
>>>pprint.pprint(sys.path)
列出放置的路徑
sysy.path 找到site-packages目錄是最佳選擇
2.告訴編譯器去哪裡找
對以下不适用:
不希望自己的子產品填滿python解釋器的目錄
沒有在python解釋器目錄中存儲檔案的權限
想将子產品放在其他地方
标準的實作方法是在PYTHONPATH環境變量中包含子產品所在的目錄。
10.1.4 包
為了組織好子產品,你可以将它們分組為包。包基本上就是另外一類子產品,有趣的地方就是他們能包含其他子產品。當子產品存儲在檔案中時,包就是子產品所在的目錄。為了讓python将其作為包對待,它必須包含一個命名為__init_py的檔案。如果将它作為普通子產品導入話,檔案的内容就是包的内容。
比如有個名為constants的包,檔案constants/__init___.py包括語句PI=3.14,那麼:
import constants
print constants.PI
為了将子產品放置在包内,直接把子產品放在包目錄内即可。
比如,要建立一個drawing的包,其中包括名為shapes和colors的子產品,你就需要下面所需檔案和目錄:
檔案/目錄 描述
~/python/ PYTHONPATH中德目錄
~/python/drawing/ 包目錄
~/python/drawing/__init__.py 包代碼
~/python/drawing/colors.py colors子產品
~/python/drawing/shapes.py shapes子產品
上面設定完,下面的語句都是合法的
import drawing
import drawing.colors
from drawing import shapes
10.2 探究子產品
10.2.1 子產品中有什麼
1.dir 檢視子產品包含的内容可以使用dir函數,它會将對象的所有特性(以及子產品的所有函數,類,變量等)列出。一些名字以下劃線開始--暗示他們并不是在子產品外部使用而準備的。是以用清單推導式過濾掉它們:
>>>[n for n in dir(copy) if not n.startswith('_')]
['Error','PyStringMap','copy','deepcopy','dispatch_table','error','name','t']
這個清單推導式是個包含dir(copy)中所有不以下劃線開頭的名字的清單。
2.__all__變量
>>>copy.__all__
['Error','copy','deepcopy']
它定義了子產品的公有接口。
from copy import *
那麼,隻能使用__all__變量中的4個函數,要導入‘t’的話,就得顯示的實作或者導入copy然後使用copy.t,或者from copy import t。
因為子產品有一大堆,是以__all__設定可以過濾掉一些不用的。
10.2.2 用help擷取幫助
>>>help(copy.copy)
解釋
>>>print copy.copy.__doc__
10.2.3 文檔
>>>print range.__doc__
可以檢視關于range函數的精确描述。
>>>print copy.__file__
檢視子產品屬性
10.3 标準庫
10.3.1 sys
sys這個子產品讓你能夠通路與python解釋器聯系緊密的變量和函數。
反序列印指令行參數
#reverseargs.py
import sys
args = sys.argv[1:]
args.reverse()
print ' '.join(args)
或者
[print ' '.join(reversed(sys.argv[1:])) ]
$python reverseargs.py this is a test
test a is this
10.3.2 os
os子產品為你提供了通路多個作業系統服務的功能。
例如
os.system('/usr/bin/firefox')
10.3.3 fileinput
可以輕松的周遊文本檔案的所有行。
$python some_script.py file1.txt file2.txt
這樣可以依次周遊所有的檔案
$cat file.txt | python some_script.py
為python腳本添加行号
#numberlines.py
import fileinput
for line fileinput.input(inplace=True):
line = line.rstrip()
num = fileinput.lineno()
print '%-40s # %2i' % (line,num)
$python numberlines.py numberlines.py
輸出
#numberlines.py #1
import fileinput #2
for line fileinput.input(inplace=True): #3
line = line.rstrip() #4
num = fileinput.lineno() #5
print '%-40s # %2i' % (line,num) #6
10.3.4 集合、堆和雙端隊列
1.集合
>>>set(range(10))
set([0,1,2,3,4,5,6,7,8,9])
集合是由序列建構的。它們主要用于檢查成員資格,是以副本時被忽略的:
>>>set([0,1,2,3,0,1,2,3,4,5])
set([0,1,2,3,4,5])
和字典一樣,集合元素的順序是随意的,是以我們不應該以元素的順序作為依據進行程式設計:
>>>set(['fee','fie','foe'])
set(['foe','fee','fie'])
除了檢查成員資格外,還可以使用标準的集合操作,比如求并集和交集,可以使用方法,也可以使用對整數進行位操作時使用的操作。比如想要找出兩個集合的并集,可以使用其中一個集合的union方法或者使用按位與運算符;
>>>a = set([1,2,3])
>>>b = set([2,3,4])
>>>a.union(b)
set([1,2,3,4])
>>>a | b
>>>c = a & b
>>>c.issubset(a)
True
>>>c <= a
>>>c.issuperset(a)
False
>>>c >= a
>>>a.intersection(b)
set([2,3])
>>>a & b
>>>a.difference(b)
set([1])
>>>a - b
>>>a.symetric_difference(b)
set([1,4])
>>> a ^ b
>>>a.copy()
set([1,2,3])
>>>a.copy() is a
如果需要一個函數,用于查找并且列印兩個集合的并集,可以使用來自set類型的union方法的未綁定版本,這種做法很有用,比如結合reduce來使用:
>>>mySets = []
>>>for i in range(10):
mySets.append(set(range(i,i+5)))
>>reduce(set.union,mySets)
set([0,1,2,3,4,5,6,7,8,9,10,11,12,13])
集合是可變的,是以不能用作字典的鍵。另外一個問題就是集合本身隻能包含不可變值,是以也就不能包含其他集合。
frozenset類型,用于代表不可變的集合:
>>>a = set()
>>>b = set()
>>>a.add(b)
報錯
>>>a.add(frozenset(b))
frozenset構造函數建立給定集合的副本,不管是将集合作為其他集合成員還是字典的鍵,frozenset都很有用。
2.堆
python中并沒有獨立的堆類型---隻有一個包含一些堆操作函數的子產品,這個子產品叫做heapq。
heappush函數用于增加堆得項。注意,不能将它用于任何之前講述的清單中---它隻能用于通過各種堆函數建立的清單中。原因時元素的順序很重要(盡管看起來是随意排列,元素并不是進行嚴格排序的)。
>>>from heapq import *
>>>from random import shuffle
>>>date = range(10)
>>>shuffle(data)
>>>heap = []
>>>for n in data:
heappush(heap,n)
>>>heap
[0,1,3,6,2,8,4,7,9,5]
>>>heappush(heap,0.5)
[0,0.5,3,6,1,8,4,7,9,5,2]
元素的順序并不像看起來那麼随意。它們雖然不是嚴格排序的,但是也是有規則的:位于i置上的元素總比i//2位置處的元素大,這是底層堆算法的基礎,而這個特性成為堆屬性。
heappop函數彈出最小的元素---一般來說都是在索引0處的元素,并且會確定剩餘元素中最小的那個占據這個位置。一般來說,盡管彈出清單的第一個元素并不是很有效率,但是這裡不是問題,因為heappop會做一些精巧的移位操作:
>>>heappop(heap)
0.5
[2,5,3,6,9,8,4,7]
heapify函數使用任意清單作為參數,并且通過盡可能少的移位操作,将其轉換為合法的堆。如果沒有用heappush建立堆,那麼在使用heappush和heappop前應該使用這個函數。
>>>heap = [5,8,0,3,6,7,9,1,4,2]
>>>heapify(heap)
[0,1,5,3,2,7,9,8,4,6]
heapreplace函數不像其他函數那麼常用。它彈出堆得最小元素,并且将新元素推入。這樣做比調用heappop之後調用heappush更高效。
>>>heapreplace(heap,0.5)
[0.5,1,5,3,2,7,9,8,4,6]
>>>heapreplace(heap,10)
[1,2,5,3,6,7,9,8,4,10]
heapq子產品中剩下的兩個函數nlargest(n,iter)和nsmallest(n,iter)分别用來尋找任何可疊代對象iter中第n大或第n小的元素。你可以使用排序和分片來完成這個工作,但是堆算法更快而且更有效的使用記憶體。
3.雙端隊列(以及其他集合類型)
雙端隊列在需要按照元素增加的順序來移除元素時非常有用。
雙端隊列通過可疊代對象建立,而且有些非常有用的方法。
>>>from collections import deque
>>>q = deque(range(5))
>>>q.append(5)
>>>q.appendleft(6)
>>>q
deque([6,0,1,2,3,4,5])
>>>q.pop()
5
>>>q.popleft()
6
>>>q.rotate(3)
deque([2,3,4,0,1])
>>>q.rotate(-1)
deque([3,4,,0,1,2])
雙端隊列好用的原因是它能夠有效地在開頭增加和彈出元素,這是在清單中無法實作的。除此之外,使用雙端隊列的好處還有:能夠有效地旋轉元素。雙端隊列對象還有extend和extendleft方法,extend和清單的extend方法差不多,extendleft則類似于appendleft。注意,extendleft使用的可疊代對象中的元素會反序出現在雙端隊列中。
10.3.5 time
time子產品所包括的函數能夠實作以下功能:獲得目前時間、操作時間和日期、從字元串讀取時間以及格式化時間為字元串。比如,元組:
(2008,1,21,12,2,56,0,21,0)
年 月 日 時 分 秒 周 儒曆日 夏令時
秒的範圍是0~61是為了應付閏秒和雙閏秒。夏令時的數字是布爾值,但是如果使用了-1,mktime就會工作正常。
>>>time.asctime()
'Fri Dec 21 05:41:27 2008'
10.3.6 random
random子產品包括傳回随機數的函數,可以用于模拟或者用于任何産生随機輸出的程式。
from random import *
from time import *
date1 = (2008,1,1,0,0,0,-1,-1,-1)
time1 = mktime(date1)
date2 = (2009,1,1,0,0,0,-1,-1,-1)
time2 = mktime(date2)
然後就能在這個範圍内均一地生成随機數:
>>>random_time = uniform(time1,time2)
然後,可以将數字轉換為易讀的日期形式:
>>>print asctime(localtime(random_time))
Mon Jun 24 21:35:19 2008
接下來,我們要求使用者選擇投擲的色子數以及每個色子具有的面數。投色子機制可以由randrange和for循環實作:
from random import randrange
num = input('How many dice?')
sum = 0
for i in range(num):sum += randrange(sides) + 1
print 'The result is ',sum
如果将代碼存為腳本檔案并且執行,那麼會看到下面的互動操作:
How many dice? 3
How many sides per die? 6
The result is 10
接下來假設有一個建立的文本檔案,它的每一行文本都代表一種運勢,那麼我們就可以使用前面介紹的fileinput子產品将“運勢”都存入清單中,再進行随機選擇:
#fortune.py
import fileinput ,random
fortunes = list(fileinput.input())
print random.choice(fortunes)
在UNIX中,可以對标準字典檔案/usr/dict/words進行測試,以獲得一個随機單詞:
$python fortune.py /usr/dict/words
dodge
最後一個例子,假設你希望程式能夠在每次敲擊回車的時候都為自己發一張牌,同時還要確定不會獲得相同的牌,首先要建立“一副牌”-------字元串清單:
>>>values = range(1,11) + 'Jack Queen King'.split()
>>>suits = 'diamonds clubs hearts spades'.split()
>>>deck = ['%s of %s' % (v,s) for v in values for s in suits]
現在建立的牌還不太适合進行遊戲,讓我們來看看現在的牌:
>>>from pprint import pprint
>>>pprint(deck[:12])
['1 of diamonds',
'1 of clubs'
'1 of hearts
............]
>>>shuffle(deck)
['3 of spades',
'2 of diamonds'
.............]
最後,為了讓python在每次按回車的時候都給你發一張牌,直到發完為止,那麼隻需要建立一個小的while循環即可。假設将建立牌的代碼放在程式檔案中,那麼隻需要在程式的結尾處加入下面這行代碼:
while deck:raw_input(deck.pop())
10.3.7 shelve
如何在檔案中存儲資料,隻需要一個簡單的存儲方案,shelve子產品就可以。
1.潛在的陷阱
意識到shelve.open函數傳回的對象并不是普通的映射是很重要的,如果下面的例子所示:
>>>import shelve
>>>s = shelve.open('test.dat')
>>>s['x'] = ['a','b','c']
>>>s['x'].append('d')
>>>s['x']
['a','b','c']
'd'去哪了?
當你在shelf對象中查找元素的時候,這個對象都會根據已經存儲的版本進行重新建構,當你将元素賦給某個鍵的時候,它就被存儲了。
1.清單['a','b','c']存儲在鍵x下;
2.獲得存儲的表示,并且根據它來建立新的清單,而‘d’被添加到這個副本中,修改的版本還沒被儲存。
3.最終,再次獲得原始版本-------沒有‘d’
為了正确的使用shelve子產品修改存儲的對象,必須将臨時變量綁定到獲得的副本上,并且在它被修改後重新存儲這個副本:
>>>temp = s['x']
>>>temp.append('d')
>>>s['x'] = temp
['a','b','c','d']
将open函數的writeback參數設為true,如果這樣做,所有從shelf讀取或者指派到shelf的資料結構都會儲存在記憶體中,并且隻有在關閉shelf的時候才寫回到磁盤中。如果處理的資料不大,并且不想考慮這些問題,那麼将writeback設為true的方法還是不錯的。
2.簡單的資料庫示例
#database.py
import sys.shelve
def store_person(db):
pid = raw_input('Enter unique ID number:')
person = {}
person['name'] = raw_input('Enter name:')
person['age'] = raw_input('Enter age:')
person['phone'] = raw_input('Enter phone number:')
db[pid] = person
def lookup_person(db):
pid = raw_input('Enter ID number:')
filed = raw_input('What would you like to know?(name,age,phone)')
filed = filed.strip().lower
print filed.capitalize() + ':',\
db[pid][filed]
def print_help():
print 'The available commands are:'
print 'store : Stores information about a person'
print 'lookup : Looks up a person from ID number'
print 'quit : Save changes and exit'
print '? : Prints this message'
def enter_command():
cmd = raw_input('Enter command (? for help):')
cmd = cmd.strip().lower()
return cmd
def main():
database = shelve.open('C:\\database.bat')
try:
while True:
cmd = enter_command()
if cmd == 'store':
store_person(database)
elif cmd == 'lookup':
lookup_person(database)
slif cmd == '?'
print_help()
slif cmd == 'quit'
return
finally:
database.close()
if __name__ == '__main__':main()
10.3.8 re
re子產品包含正規表達式的支援。
‘python\\.org’ 或者 r'python\.org'
>>>some_text = 'alpha,beta,,,,,gamma delta'
>>>re.split('[,]+',some_text)
['alpha','beta','gamma','delta']
如果模式包含小括号,那麼括起來的字元組合會散步在分割後的子字元串之間,例如,re.split('o(o)',foobar) 會生成['f','o','bar']
從上述例子可以看出,傳回值是字元串的清單。maxsplit參數表示字元串最多可以分割的次數:
>>>re.split('[,]+',some_text,maxsplit=2)
['alpha','beta','gamma delta']
>>>re.split('[,]+',some_text,maxsplit=1)
['alpha','beta,,,,gamma delta']
函數re.findall以清單形式傳回給定模式的所有比對項。比如,要在字元串中查找所有的單詞,可以像下面這麼做:
>>>pat = '[a-zA-Z]'
>>>text = '"Hm...Err -- are you sure?" he said, sounding insecure'
>>>re.findall(pat,text)
['Hm','Err','are','you','sure','he','said','sounding','insecure']
或者查找标點符号:
>>>pat = r'[.?\-",]+'
['"','...','--','?"',',','.']
注意,橫線被轉義了,是以python不會将其解釋為字元範圍的一部分。
函數re.sub的作用在于:使用給定的替換内容将比對模式的子字元串替換掉。
>>>pat = '{name}'
>>>text = 'Dear {name}...'
>>>re.sub(pat,'Mr. Gumby',text)
'Dear Mr. Gumby'
re.escape是一個很實用的函數,它可以對字元串中所有可能被解釋為正則運算符的字元進行轉義的應用函數。如果字元串很長且包含很多特殊字元,而你又不想輸入一大堆反斜線,或者字元串來自于使用者,且要用作正規表達式的一部分的時候,可以使用這個函數。下面的例子向你示範了該函數是如何工作的:
>>>re.escape('www.python.org')
'www\\.python\\.org'
>>>re.escape('But where is the ambiguity?')
'But\\ where\\ is\\ the\\ ambiguity\\?'
3. 比對對象群組
對于re子產品中那些能夠對字元串進行模式比對的函數而言,當能找到比對項的時候,它們都會傳回MatchObject對象。這些對象包括比對模式的子字元串的資訊。它們還包含了哪個模式比對了子字元串哪部分的資訊----這些“部分”叫做組。
組就是放置在圓括号内的子模式。組的序号取決于它左側的括号數。組0就是整個模式,是以在下面的模式中:
‘There (was a (wee)(cooper) who (lived in Fyfe))’
包含下面這些組
0 There was a wee cooper who lived in Fyfe
1 was a wee cooper
2 wee
3 cooper
4 lived in Fyfe
一般來說,如果組中包含諸如通配符或者重複運算符之類的特殊字元,那麼你可能會對是什麼與給定組實作了比對感興趣,比如下面的模式:
r'www\.(.+)\.com$'
組0包含整個字元串,而組1則包含位于‘www.’和‘.com’之間的所有内容。像這樣建立模式的話,就可以取出字元串中感興趣的部分了。
除了整體比對外,我們隻能使用1~99
>>>m = re.match(r'www\.(.*)..{3}','www.python.org')
>>>m.group(1)
'python'
>>>m.start(1)
4
>>>m.end(1)
10
>>>m.span(1)
(4,10)
4.作為替換的組号和函數
'*something*' 用 '<em>something</em>' 替換掉
>>>emphasis_pattern = r'\*([^\*]+)\*'
讓正規表達式更易讀是在re函數中使用VERBOSE标志。
>>>emphasis_pattern = re.compile(r'''
\* #解釋
( #...
[^\*]+ #...
) #...
\* #...
''',re.VERBOSE)
現在模式已經搞定,接下來就可以使用re.sub進行替換了:
>>>re.sub(emphasis_pattern,r'<em>\1</em>','Hello,*world*!')
'Hello,<em>world</em>!'
貪婪和非貪婪模式
>>>emphasis_pattern = r'\*(.+)\*'
>>>re.sub(emphasis_pattern,r'<em>\1<em>','*This* is *it*!')
'<em>This* is *it</em>!'
>>>emphasis_pattern = r'\*\*(.+?)\*\*'
>>>re.sub(emphasis_pattern,r'<em>\1<em>','**This** is **it**!')
'<em>This</em> is <em>it</em>!'
這裡用+?運算符代替了+,意味着模式會像之前那樣對一個或者多個通配符進行比對,但是它是盡可能少的比對,因為是非貪婪的。
5.模版系統示例
模闆是一種通過放入具體值進而得到某種已完成文本的檔案。比如,你可能會有隻需要插入收件人姓名的郵件模版。python有一種進階的模版機制:字元串格式化。但是使用正規表達式可以讓系統更加進階。假設需要把所有'[somethings]'的比對項替換為通過python表達式計算出來的something結果,是以下面的字元串:
'The sum of 7 and 9 is [7+=9]' 被翻譯為:
'The sum of 7 and 9 is 16'
同時,還可以在字段内進行指派,是以下面的字元串:
'[name="Mr. Gumby"]Hello,[name]' 被翻譯成
'Hello, Mr. Gumby'
下面是簡單實作:
#templates.py
import fileinput,re
#比對中括号裡的字段:
filed_pat = re.compile(r'\[(.+?)\]')
#我們将變量收集到這裡:
scope = {}
#用于re.sub中:
def replacement(match):
code = match.group(1)
#如果字段可以求值,傳回它:
return str(eval(code,scope))
except SyntaxError:
#否則執行相同作用域的指派語句。。。
exec code in scope
#.....傳回空字元串:
return ‘’
#将所有文本以一個字元串的形式擷取:
lines = []
for line in fileinput.input():
lines.append(line)
text = ''.join(lines)
#filed模式的所有比對項都替換掉:
print field_pat.sub(replacement,text)
簡單來說,程式做了下面的事情:
定義了用于比對字段的模式
建立充當模版作用域的字典
定義具有下列功能的替換函數
将組1從比對中取出,放入code中:
通過将作用域字典作為命名空間來對code進行求值,将結果轉換為字元串傳回,如果成 功的話,字段就是個表達式,一切正常。否則,跳到下一步。
執行在相同命名空間内的字段來對表達式求值,傳回空字元串
使用fileinput讀取所有可用的行,将其放入清單,組合成一個大字元串。
将所有field_pat比對項用re.sub中的替換函數進行替換,并且列印結果。
本文轉自潘闊 51CTO部落格,原文連結:http://blog.51cto.com/pankuo/1661444,如需轉載請自行聯系原作者