- 示例代碼
"""Framework for getting filetype-specific metadata. Instantiate appropriate class with filename. Returned object acts like a dictionary, with key-value pairs for each piece of metadata. import fileinfo info = fileinfo.MP3FileInfo("/music/ap/mahadeva.mp3") print "\\n".join(["%s=%s" % (k, v) for k, v in info.items()]) Or use listDirectory function to get info on all files in a directory. for info in fileinfo.listDirectory("/music/ap/", [".mp3"]): ... Framework can be extended by adding classes for particular file types, e.g. HTMLFileInfo, MPGFileInfo, DOCFileInfo. Each class is completely responsible for parsing its files appropriately; see MP3FileInfo for example. """ import os import sys from UserDict import UserDict def stripnulls(data) "strip whitespace and nulls" return data.replace("\00", "").strip() class FileInfo(UserDict): "store file metadata" def __init__(self, filename=None): UserDict.__init__(self) self["name"] = filename class MP3FileInfo(FileInfo): "store ID3v1.0 MP3 tags" tagDataMap = {"title" : ( 3, 33, stripnulls), "artist" : ( 33, 63, stripnulls), "album" : ( 63, 93, stripnulls), "year" : ( 93, 97, stripnulls), "comment" : ( 97, 126, stripnulls), "genre" : (127, 128, ord)} def __parse(self, filename): "parse ID3v1.0 tags from MP3 file" self.clear() try: fsock = open(filename, "rb", 0) try: fsock.seek(-128, 2) tagdata = fsock.read(128) finally: fsock.close() if tagdata[:3] == "TAG": for tag, (start, end, parseFunc) in self.tagDataMap.items(): self[tag] = parseFunc(tagdata[start:end]) except IOError: pass def __setitem__(self, key, item): if key == "name" and item: self.__parse(item) FileInfo.__setitem__(self, key, item) def listDirectory(directory, fileExtList): "get list of file info objects for files of particular extensions" fileExtList = [ext.upper() for ext in fileExtList] fileList = [os.path.join(directory, f) for f in os.listdir(directory) \ if os.path.splitext(f)[1].upper() in fileExtList] def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]): "get file info class from filename extension" subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:] return hasattr(module, subclass) and getattr(module, subclass) or FileInfo return [getFileInfoClass(f)(f) for f in fileList] if __name__ == "__main__": for info in listDirectory("/music/_singles/", [".mp3"]): print "\n".join(["%(key)s=%(value)s" % vars() for key, value in info.items()]) print
修改目錄路徑到自己機器的MP3文件路徑
- 面向對象綜述:模塊,類,函數,文檔字符串,讀代碼找到總體印象,Python是完全面向對象的。
- 定義類:可以自己定義類,從自己的或者是內置的類繼承,然后對類進行實例化;只要定義類,然后編碼就行了;類以保留子class開始,跟着是類的名字;
- 最簡單的python類
class foo: #類的名字,沒有從任何地方繼承。 pass #python保留字,是一條什么都不做的語句,刪除函數或者類的時候是一個很好的占位符
實際上大多數的類都是繼承而來的,然后定義自己的類方法和屬性,Python 有一個同c++構造器類似的東西__init__方法。
- 定義FIleInfo類
from UserDict import UserDict class FileInfo(UserDict): #FileInfo是從UserDict類繼承而來的,是一個像字典一樣的動作的類,允許完全子類化字典數據類型,同時增加自己的行為 "store file metadata" #文檔字符串 def __init__(self, filename=None): #__init__在類的實例創建后即被調用,可能會誘使你,但是不正確的,叫它為類的構造器。看上去像是構造器,是類第一個定義的方法,在一個新創建的類實例中,它是首先被執行的代碼,不正確的是,是一位對象在__init__被調用的時候已經被構造出來了,已經有了對類的新實例一個有效的引用。 #self,每一個類的方法的第一個參數,都是指向了類的當前實例的一個引用,按照習慣被定義成self。像c++中的this,__init__中self指向新創建的對象,在其他方法中,他指向方法被調用的對象。 #__init__方法可以接受任意個數的參數,就像函數一樣,參數可以用缺省值定義,可以設置成對於調用者可選 UserDict.__init__(self) #一些偽面向對象語言,父類的方法在子類的方法執行前被自動調用,Python不行,必須要顯示調用在父類中合適的方法。而且調用父類的方法的時候要包含參數self,跟着類方法所接受的任何其他類型。此處,除了self沒有使用其他參數調用UserDict類的__init__方法 self["name"] = filename #這個類像字典一樣動作,將參數filename賦值給對象name關鍵字,作為它的值。init方法不返回任何東西。
每個類的方法的第一個參數都命名為self,是一個強烈的習慣。
__init__方法是可選的,但是一旦定義了一個,就必須記得顯示調用父類的__init__方法。注意:任何一個子類想要擴展父類的行為,后代方法必須在適當的時機,使用適當的參數,顯示的調用父類的方法。
- 最簡單的python類
- 類的實例化:實例化一個類,只需要簡單的調用它,傳遞給它的是__init__函數中的參數。返回值將是新創建的對象,沒有c++中的new操作符。
- FileInfo實例(定義在fileinfo模塊里)
>>>import fileinfo >>>f = fileinfo.FileInfo("/media/shi.mp3") #創建一個實例,賦給變量f,傳入一個參數,即__init__里邊的filename >>> f.__class__ #對象的類是內置屬性 <class fileinfo.FileInfo at 010EC204> >>> f.__doc__ #一個類的所有實例共享一個字符串 'base class for file info' >>> f #init將文件名參數賦值給self['name'],傳遞的參數從右向左發送給init方法 {'name':'/media/shi.mp3'}
不需要顯示的釋放實例,應為當它們賦值給的變量超出作用域的時候,自動的釋放,內存泄露在python中很少見。
- 嘗試內存泄露
>>> def leakmem(): ... f = fileinfo.FileInfo('/shi.mp3') #每次leakmen被調用,創建一個實例給變量f,這個變量是函數內的一個局部變量。然后函數結束沒有釋放f,認為有泄露,但是不是,函數結束的時候f已經釋放掉了 ... >>> for i in range(100): #每一次調用leakmem函數,決不會泄露內存,每一次,python將在leakmem返回前破壞掉新創建的FileInfo類。 ... leakmem()
對於這種垃圾收集的方式,技術上的術語叫做‘引用計數’。python維護着為每一個實例創建的實例的引用列表,上邊對實例的引用只有一個f,函數結束的時候,f超出了作用域,所以引用計數變成0,python自動破壞實例。
- FileInfo實例(定義在fileinfo模塊里)
- UserDict:一個封裝類
- 在UserDict模塊中的UserDict類,是FileInfo類的父類,沒什么特別的。
- 不能子類化字符串,列表,字典的內置數據類型,作為補充,提供了封裝類,可以模擬這些內置數據類型的行為,UserString,UserList,UserDict通過使用一個由普通和專用方法組成的聯合體,UserDict是一個字典的出色模仿品,僅僅是一個類它,可以對它進行子類化,提供可定制的字典的類,比如,FileInfo。
class UserDict: #是一個基類,不是繼承而來 def __init__(self, dict=None): #2 self.data = {} #3 if dict is not None: self.update(dict) #4
2,FIleInfo中覆蓋了的 __init__方法。這個父類的參數與子類的不同,有的語言支持通過參數列表的函數重載,也就是,同名的多個函數,有着不同個數的參數,或者不同類型的參數。另外的語言甚至支持通過參數名的重載,也就是,同名的多個函數,有相同個數相同類型的參數,但是參數名字不同。這個python 都是不支持的,不管怎么樣都沒有函數重載的方式。一個__init__方法就是一個__init__方法,不管它有什么樣的參數,每個類只能擁有一個__init__方法,如果一個子類擁有一個__init__方法,它總是覆蓋父類的__init__方法,甚至子類可以用不同的參數列表來定義它。3,python 支持數據屬性即數據成員,它是由某個特定的實例所擁有的屬性,要從類外引用這個名字需要在名字前邊加上實例的名字來限制它,instanc.data,限定的方法與你用模塊的名字來限定函數一樣。要在類的內部引用一個數據屬性,使用self作為限定符。為了方便,所有的數據屬性的值都在__init__方法中初始化為有意義的值,然而這並不是必須的,因為數據屬性,像局部變量一樣,首次賦值給它的時候突然產生。4,當在一個塊中僅有一條語句的一個簡寫,也可以用縮進代碼,但是不能混用。
- UserDict普通方法
def cleaf(self): self.data.clear() #1 def copy(self): #2 if self.__class__ is UserDict: #3 return UserDict(self.data) import copy #4 return copy.copy(self) def keys(self): return self.data.keys() #5 def items(self):return self.data.items() def values(self): return self.data.values()
1,python類中的普通方法,python 不會替你調用,只是像類的一般方法一樣調用,類封裝的基本技術:保存一個真正的字典作為數據屬性,定義所有的字典有擁有的方法,並且將每個類方法重定向到真正字典上的響應方法。(clear方法刪除它所有關鍵字和對應的值)。2,真正的字典copy方法會返回一個新的字典,是原始字典原樣的復制。但是不能簡單的重定向到self.data.copy,因為那個方法返回的是一個真正的字典,而我們想返回的是同一個類的一個新的實例,就像是self。3,使用__class__屬性來查看self是否是一個userdict,如果是 就創建一個新的userdict實例返回,並傳給他真正的字典,這個字典已經放在了self.data中了。4,self.__class__不是Userdict,那么self一定是userdict的某個子類,userdict不知道怎么生成一個它的子類的一個原樣的拷貝,有可能在子類中定義了一些新的數據屬性,所以我們只能完全拷貝他們,確定拷貝了他們的全部內容。幸運的是,python的一個模塊可以正確的完成這件事情,叫做copy,copy能夠拷貝任何的python對象return copy.copy(self)。5,其他的方法直接重定向到self.data的內置函數上去。
- 專用類方法
- 除了普通的類方法,還要有一些對於Python類可以定義的專用方法,專用方法是在特殊情況下或者是當使用特別的語法時候python替你調用的,而不是在代碼中直接調用,上一節中,普通的方法對在類中封裝字典很有幫助。但是只有普通方法是不夠的,因為除了字典調用方法之外,還有很多事情可以做,例如,可以通過一種沒有包含顯示方式調用的語法來得到和設置數據項,這就是專用方法產生的原因:提供了一種方法,可以將非方法調用影射到方法調用上。
- __getitem__專用方法
def __getitem__(self, key): return self.data[key] >>f = fileinfo.FileInfo("/music/ka.mp3") >>f {"name":"/music/ka.mp3"} >>f.__getitem__("name") #這個方法只是重定向到字典,返回了字典的值,但是怎么調用它呢,可以直接調用這個方法,但是實際的操作過程當中不會那樣做,在這里執行只是告訴怎么工作的,正確的調用__getitem__是python替我們調用的 "music/ka.mp3" >>f["name"] #這個看上去像一個得到字典值的語法,事實上它返回你期望的值。暗地里邊python已經轉化了過程。其實是:f.__getitme__("name")的方法調用。這就是為什么說是專用的類方法,可以自己調用也可以python幫助我們調用,"music/ka.mp3"
- __setitem__專用方法
def __setitem__(self, key, item): self.data[key]=item >>> f {'name':'/music/_singles/kairo.mp3'} >>> f.__setitem__("genre", 31) #通常不會直接調用的 >>> f {'name':'/music/_singles/kairo.mp3', 'genre':31} >>> f["genre"] = 32 #暗地調用了上邊的方法 >>> f {'name':'/music/_singles/kairo.mp3', 'genre':32}
__setitem__是一個專用類方法,也仍然是一個類方法,在userdict中定義__setitem__方法一樣容易,我們可以在子類中重新對它進行定義,對父類的方法進行重新覆蓋,允許我們定義出某些方面像字典的類,但是我們可以自己定義它的行為,超過和超出內置的字典。這個是學習的整個框架的基礎。每個文件類型可以擁有一個處理器類,這些類知道如何從一個特殊的文類型得到元數據。一旦知道了某些屬性比如文件名和位置,處理器類就知道如何自動的得到其他的屬性。它的實現是通過覆蓋__setitem__方法,檢查特別的關鍵字,然后當找到后加入額外的處理。
- 在MP3FileInfo中覆蓋__setitem__:MP3FileInfo是FileInfo的一個子類,在設置一個MP3FileInfo的name時候,並不只是設置了name關鍵字(父類中這么做的),它還要在文件自身內部進行搜索MP3的標記然后填充一整套關鍵字集合。
def __setitem(self, key, item): #進行定義的時候要嚴格按照父類方法相同的形式定義,技術上說,參數的名字沒有關系,只是個數 if key == 'name' and item #如果想給name賦值我們還想做額外的事情。 self.__parse(item) #對name所作的額外處理封裝在了__parase方法中,這是定義在MP3FileInfo中的另一個類的方法,我們調用它的時候,使用self對其限定,否則僅僅是調用__parse將只會看成定義在類中的一個類方法,當然用同樣的方法來引用數據屬性 FileInfo.__setitem__(self, key, item) #做完額外的處理時后,需要調用父類的方法,Python中不會自動完成,需要手工進行執行,盡管沒有一個直接的類方法,我們還是直接用父類調用,Python會沿着父類樹走,直到它找到一個有着我們正在調用方法的類,這行代碼最終會找到並且調用定義在userdict中的__setitem__
在一個類中存取數據類型的時候,需要限定屬性名字:self.attribute, 調用類中的其他方法的時候,需要限定方法:self.method。
- 設置一個MP3FileInfo的name
>>> import fileinfo >>> mp3file = fileinfo.MP3FileInfo() #創建一個實例,但是沒有傳遞問鍵名字,應為__init__方法中的filename參數是可選的,MP3FileInfo中沒有__init__方法,沿着父類樹走,在FileInfo中的__init__方法,這個__init__方法手工調用了父類的__init__方法,然后設置name關鍵字為filename,它為none。因為我們沒傳入一個文件名,所以mp3file最初看上去像有關鍵字 >>> mp3file {'name':None} >>> mp3file["name"] = "/music/_singles/kairo.mp3" #這個時候設置name關鍵字觸發了MP3FileInfo的__setitem__(不是userdict),這個方法我們用一個真實的值來設置name關鍵字,接着調用self.__parse方法。通過輸出可以看到,設置了其他的幾個關鍵字 >>> mp3file {'album': 'Rave Mix', 'artist': '***DJ MARY-JANE***', 'genre': 31, 'title': 'KAIRO****THE BEST GOA', 'name': '/music/_singles/kairo.mp3', 'year': '2000', 'comment': 'http://mp3.com/DJMARYJANE'} >>> mp3file["name"] = "/music/_singles/sidewinder.mp3" #python 調用__setitem__,__setitem__調用__parse,self.parse設置其他所有的關鍵字。 >>> mp3file {'album': '', 'artist': 'The Cynic Project', 'genre': 18, 'title': 'Sidewinder', 'name': '/music/_singles/sidewinder.mp3', 'year': '2000', 'comment': 'http://mp3.com/cynicproject'}
- 高級專用類方法:剛才的基礎上還有更多的專用函數,模擬出許多甚至可能不知道的功能
- 在UserDict中更多的專用方法
def __repr__(self): return repr(self.data) #返回一個對象的字符串表示。可以用再任何對象上,而不僅僅是實例, def __cmp__(self, dict): #在比較類實例的時候被調用,通常可以使用==比較任意兩個Python對象,不只是實例。有一些規則,定義何時內置數據類型被認為是相等的,例如,字典再有着相同的關鍵字和值的時候是相等的。對於類實例可以自己編寫比較邏輯,然后可以使用==來比較你的類,python將會替你調用寫的__cmp__專用方法。 if isinstance(dict, UserDict): return cmp(self, data, dict.data) def __len__(self): return len(self.data) #返回一個對象的長度,是一個內置的函數len(instance),字典的len是它的關鍵字的個數,列表或者序列的是元素的個數,對於類實例,定義len方法,可以自己編寫長度的計算,然后調用len(instance),python替你調用__len__方法。 def __delitem(self, key): del self.data[key] #從字典中刪除單個元素的方法,調用 del instance[key]。
- 所有的這些操作只是為了在類中做一些我可以對一個內置數據類型所作的操作,不錯,如果你能夠從像字典一樣的內置數據類型進行繼承的話,事情就容易多了,但是也許可以,專用方法仍然是有用的,因為他們可以用給任何類,而不是只像userdict的封裝類。
- 專用方法意味着,任何類可以像字典一樣保存鍵-值對,只要定義__setitem__對任何類可以表現的像一個序列,只要通過定義__getitem__方法,任何定義了__cmp__方法的類可以用==進行比較,並且如果你的類表現擁有類似長度的東西,不要定義GetLength方法,而定義__len__方法,使用len(instance)。
- 存在許多其他的專用方法。有一整套的專用方法,可以讓類表現得像數值一樣,允許你在類上進行加,減,和執行其他數學操作,__call__方法讓一個類表現的像一個函數,允許你直接調用一個類實例,並且存在其他的專用函數,允許類只有讀或者寫的數據屬性。
- 在UserDict中更多的專用方法
- 類屬性:數據屬性是被一個特定類定例所擁有的變量。python也支持類屬性,由類本身擁有的。
- 類屬性介紹
class MP3FileInfo(FileInfo): "store ID3v1.0 MP3 tags" tagDataMap = {"title" : ( 3, 33, stripnulls), "artist" : ( 33, 63, stripnulls), "album" : ( 63, 93, stripnulls), "year" : ( 93, 97, stripnulls), "comment" : ( 97, 126, stripnulls), "genre" : (127, 128, ord)} >>>import fileinfo >>>fileinfo.MP3FileInfo # MP3FileInfo是類本身不是任何類實例 <class fileinfo.MP3FileInfo at 01257fdc >>>fileinfo.MP3FileInfo.tagDataMap #tagDataMap是類屬性,是創建任何實例之前就有效了。 {'title': (3, 33, <function stripnulls at 0260C8D4>), 'genre': (127, 128, <built-in function ord>), 'artist': (33, 63, <function stripnulls at 0260C8D4>), 'year': (93, 97, <function stripnulls at 0260C8D4>), 'comment': (97, 126, <function stripnulls at 0260C8D4>), 'album': (63, 93, <function stripnulls at 0260C8D4>)} >>>m = fileinfo.MP3FileInfo() #類屬性既可以通過直接對類的引用,也可以通過對類的任意實例的引用來使用 >>>m.tagDataMap {'title': (3, 33, <function stripnulls at 0260C8D4>), 'genre': (127, 128, <built-in function ord>), 'artist': (33, 63, <function stripnulls at 0260C8D4>), 'year': (93, 97, <function stripnulls at 0260C8D4>), 'comment': (97, 126, <function stripnulls at 0260C8D4>), 'album': (63, 93, <function stripnulls at 0260C8D4>)}
類屬性可以作為類級別的常量來使用,這就是為什么我們在MP3FileInfo中使用他們,但是他們不是真正的常量,可以修改他們。
- 修改類屬性
>>> class counter: ... count = 0 #count是counter類的一個類屬性 ... def __init__(self): ... self.__class__.count += 1 #__class__是每個類實例的一個內置屬性,也是每個類的。它是一個類的引用,而self是一個類的實例。 ... >>> counter <class __main__.counter at 0x84a929c> >>> counter.count #count是一個類屬性,可以在創建任何類實例前通過直接對類的引用而得到 0 >>> c = counter() #創建一個類實例會調用__init__方法,方法中會給類屬性count加1,這樣會影響到類本身,不只是新創建的實例。 >>> c.count 1 >>> counter.count 1 >>> d = counter() #創建第二個實例會再次增加類屬性count,類屬性被類和所有類實例所共享,有些語言叫做共享變量。 >>> d.count 2 >>> c.count 2 >>> counter.count 2
- 類屬性介紹
- 私有函數
- python也有私有函數的概念,私有函數不可以在他們的模塊外邊被調用,私有類方法,不能夠從他們的類外面被調用,私有屬性不能從他們的類外邊被使用,一個python函數,方法,屬性是私有還是公有,完全取決於它的名字。
- 在MP3FileInfo中有兩個方法,__parse和__setitem__。正如我們已經通過的,__setitem__是一個專有方法,通常,不直接調用它而是通過一個類上使用字典語法來調用它,但它是公有的,並且如果有一個好的理由,可以直接調用它(甚至在fileinfo模塊的外邊),但是,__parse是私有的,因為它的名字前邊有兩個下划線。
- 如果一個Python函數的名字,類方法,或屬性以兩個下划線開始(但不是結束),它是私有的;其他所有的都是公有的。
- 在python中,所有的專用方法(__setitem__)或者內置屬性(像__doc__)遵守一個標准的命名習慣:開始和結束都有兩個下划線,不要對自己的方法和屬性用這種方法命名,后便只會搞亂你。
- python沒有類方法保護的概念,只能用於他們自己類和子父類中,類方法要不私有(只能在自己類中使用)要不公有(任何地方都可以使用)
- 嘗試調用一個私有方法
>>>import fileinfo >>>m = fileinfo.MP3FileInfo() >>>m.__parse("/musi/shi.mp3") #試圖調用一個類的私有方法,引發一個有些誤導的異常,稱那個方法不存在,它確實存在,但是它是私有的,在類外是不可使用的 Traceback (innermost last): File "<interactive input>", line 1, in ? AttributeError: 'MP3FileInfo' instance has no attribute '__parse'
- 嚴格的蘇和哦,私有方法在類外是有效的,只是不容易處理。在Python中沒有什么是真正私有的。你可以通過 _MP3FileInfo__parse 名字來使用MP3FileInfo 類的 __parse 方法。知道了這個方法很有趣,然后要保證決不在真正的代碼中使用它。私有方法由於某種原因而私有,但是象其它很多在Python中的東西一樣,它們的私有化基本上是習慣問題,而不是強迫的。
- 處理異常:python具有異常處理,通過使用try...except塊。
- 標准python庫中每個模塊都有使用他們,python自己是在不同的情況下引發他們
- 使用不存在的字典關鍵字引發:keyerror異常
- 搜索列表中不存在的值引發:valueerror異常
- 調用不存在的方法:attributeerror異常
- 引用不存在的變量:nameerror異常
- 未強制轉換就混合數據類型:typeerror異常
- 這些情況下我們都是簡單的使用了python ide:一個錯誤發生,異常被打印出來,並且就是這些,傳出一些調試信息然后程序終止,但是如果發生在真正的Python程序運行的時候,真個程序將會終止。
- 一個異常不一定引起程序的完全崩潰,異常引發時候,可以處理掉,有的時候異常是因為代碼中的bug但是,許多時候,一個異常是可以預計的,如果知道一行代碼會引發異常(打開一個不存在的文件,連到一個可能不能處理的數據庫),應該使用一個try except塊來處理異常。
- 打開一個不存在的文件
>>>fsock = open("/notthere", "r") #文件不存在 引發IOError異常 Traceback (innermost last): File "<interactive input>", line 1, in ? IOError: [Errno 2] No such file or directory: '/notthere' >>>try: ... fsock = open("/nottehrer") ... except IOError: #捕捉異常接着執行我們自己的代碼塊,這個代碼快打出自己定義的代碼 ... print "The file does not exist, exiting gracefully" ... print "This line will always print" #異常被處理,在塊后繼續執行最后一樣總是打印出來,無論異常是否發生。 The file does not exist, exiting gracefully This line will always print
- 異常看上去不友好,不能捕捉異常,整個程序將崩潰,但是考慮一下別的方法,寧願找回對於不存在文件的不可用的文件對象么?不管怎么樣都要檢查它的有效性,而且如果你忘記了,程序將會在下面某個地方給出奇怪的錯誤,這樣就的追到源程序,使用異常,一旦發生錯誤,就可以找到問題源頭。
- 異常處理還有許多其他的用處,在標准python庫中的一個普通的方法就是試着導入一個模塊,然后檢查他是否能使用,導入一個並不存在的模塊將引發一個importerror異常,使用這種方法來定義許多級別的功能,依靠在運行的時候哪個模塊是有效的,支持多種平台(平台特定的代碼被分離到不同的模塊中)
- 支持特定平台功能,這個代碼來源於getpass模塊,一個從用戶得到口令的封裝模塊。得到口令在UNIX,WINDOWS,和MACOS平台上的實現是不同的,但是這個代碼封裝了所有的不同
# Bind the name getpass to the appropriate function try: import termios, TERMIOS #termios 是個特定的模塊,提供了對於輸入終端的底層控制。如果模塊無效,導入失敗,引發異常。 except ImportError: try: import msvcrt #試試msvcrt,十一個windows特的模塊,可以提供在Microsoft Visual C++運行服務中的許多有用的函數的一個API。如果導入失敗,Python會引發我們捕捉的 ImportError 異常。 except ImportError: try: from EasyDialogs import AskPassword #如果前兩個不能工作,我們試着從 EasyDialogs 導入一個函數,它是一個MacOS特定模塊,提供了各種各樣類型的彈出對話框。再一次,如果導入失敗,Python會引發一個我們捕捉的 ImportError 異常。 except ImportError: getpass = default_getpass #這些平台特定的模塊沒有一個有效(有可能,因為Python已經移植到了許多不同的平台上了),所以我們需要回頭使用一個缺省口令輸入函數(這個函數定義在 getpass 模塊中的別的地方)。注意,我們在這里做的:我們將函數 default_getpass 賦給變量 getpass。如果你讀了官方 getpass 文檔,它會告訴你 getpass 模塊定義了一個 getpass 函數。它是這樣做的:通過綁定 getpass 到正確的函數來適應你的平台。然后當你調用 getpass 函數時,你實際上調用了平台特定的函數,是這段代碼已經為你設置好的。你不需要知道或關心你的代碼正運行在何種平台上;只要調用 getpass,則它總能正確處理。 else: getpass = AskPassword else: getpass = win_getpass else: #一個 try...except 塊可以有一條 else 子句,就象 if 語句。如果在 try 塊中沒有異常引發,然后 else 子句被執行。在本例中,那就意味着如果 from EasyDialogs import AskPassword 導入可工作,所以我們應該綁定 getpass 到 AskPassword 函數。其它每個 try...except 塊有着相似的 else 子句,當我們找到一個 import 可用時,來綁定 getpass 到適合的函數 。 getpass = unix_getpass
- 標准python庫中每個模塊都有使用他們,python自己是在不同的情況下引發他們
- 文件對象
- python 有一個內置函數,open,用來打開磁盤上的文件,open返回一個文件對象,擁有一些方法和屬性,可以得到打開文件的信息,和對打開的文件進行操作。
- 打開文件
>>> f = open("/home/shishang/Interface.first", "rb") #open接受三個參數,文件名,參數,緩沖區參數。只有一個參數是必須的就是文件名,這里以二進制方式打開文件讀取 >>> f #open函數返回一個對象,一個文件對象有幾個有用的屬性 <open file '/home/shishang/Interface.first', mode 'rb' at 0xb76eff40> >>> f.mode #告訴文件打開模式 'rb' >>> f.name #文件的名字 '/home/shishang/Interface.first'
- 讀取文件
>>> f <open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988> >>> f.tell() #打開的文件當前位置,文件沒做任何事,當前位置是0,最開始處 0 >>> f.seek(-128, 2) #0表示移動到一個絕對位置,1移到一個絕對位置,2表示對於文件尾的一個相對位置,我們使用2並且告訴文件對象從文件尾移動到128字節的位置, >>> f.tell() #確認移到了當前位置, 7542909 >>> tagData = f.read(128) # read 方法從打開文件中讀取指定個數的字節,並且返回含有讀取數據的字符串。可選參數指定了讀取的最大字節數。如果沒有指定參數,read 將讀到文件末尾。(我們本可以在這里簡單地說一下 read(),因為我們確切地知道在文件的何處,事實上,我們讀的是最后128個字節。)讀出的數據賦給變量 tagData,並且當前的位置根據所讀的字節數作了修改。 >>> tagData 'TAGKAIRO****THE BEST GOA ***DJ MARY-JANE*** Rave Mix 2000http://mp3.com/DJMARYJANE \037' >>> f.tell() #確認了當前位置已經移動了。 7543037
- 關閉文件
>>> f <open file '/home/shishang/Interface.first', mode 'rb' at 0xb76eff40> >>> f.closed #文件對象的cloesd屬性表示對象是否打開或關閉了文件,這里依然打開着,處理完畢,需要關閉文件 False >>> f.close() #關閉文件,調用文件對象的close方法,這樣就釋放了在文件上的鎖 >>> f <closed file '/home/shishang/Interface.first', mode 'rb' at 0xb76eff40> >>> f.closed True >>> f.seek(0) #一旦文件關閉,可操作打開文件的方法沒有一個可以使用,引發異常,文件被關閉,但是不意味着對象文件停止存在,變量f繼續存在,直到超出作用域或者手工刪除。 Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: I/O operation on closed file >>> f.tell() Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: I/O operation on closed file >>> f.read() Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: I/O operation on closed file >>> f.close() #不會發生異常,靜靜的失敗
- MP3FileInfo中的文件對象
try: #打開和讀取文件有風險,並且可能引發異常,所有這些都用一個try...except塊封裝 fsock = open(filename, "rb", 0) #open 函數可能引發異常, try: fsock.seek(-128, 2) #seek方法可能引發異常 tagdata = fsock.read(128) #可能引發異常 finally: #新的finally模塊,一旦文件被成功打開,我們應該絕對保證把它關閉,甚至由於seek或者read方法引發一個異常。try...finally可以用來:finally中的代碼將總被執行,甚至某些東西在try塊中引發一個異常也會執行。可以這樣考慮,不管在路上發生什么,代碼都會被即將滅亡的執行。 fsock.close() . . . except IOError: #最后處理IO異常 ,它可能是由調用 open,seek,或 read 引發的 IOError 異常。這里,我們其實不關心,因為將要做的事就是靜靜地忽略它然后繼續。(記住,pass 是一條不做任何事的Python語句。)這樣完全合法,“處理”一個異常可以明確表示不做任何事。它仍然被認為處理過了,並且處理將正常繼續,從 try...except 塊的下一行代碼。 pass
- for循環:在其他方面python太出色,通常不需要他們。其它大多數語言沒有象Python一樣的強大的列表數據類型,所以你需要親自做很多事情,指定開始,結束和步長,來定義一定范圍的整數或字符或其它可重復的實體。但是在Python中,for 循環簡單地在一個列表上循環,與映射列表的工作方式相同。
>>> li = ['a', 'b', 'e'] >>> for s in li: # for循環類似於映射列表 ... print s ... a b e >>> print "\n".join(li) #當你想要的只是一個join或是列表映射的時候,代替了for循環 a b e
- 一次賦多個值:最酷的程序簡寫之一就是可以使用序列一次賦多個值
- 一次賦值多個
>>> v = ('a', 'b', 'e') #v是一個三個元素的序列,(x,y,z)是一個三個變量的序列,依次賦值。 >>> (x, y, z) = v >>> x 'a' >>> y 'b' >>> z 'e'
構建可重用的模塊的時候,經常給一個名字賦以一系列的值,C或者C++里邊,經常用enum並且手工的列出每個常量和它對應的值,值是連續的時候顯示的特別繁瑣。python里邊可以使用內置的range函數來迅速的給多個變量賦予連續值。
- 賦值連續
>>> range(7) #內置的range是一個證書列表。接受一個上限 返回一個從0開始計數但是不包括上限的一個列表。 [0, 1, 2, 3, 4, 5, 6] >>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7) #MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,和 SUNDAY 是我們定義的變量。(這個例子來自 calendar 模塊,一個有趣的用來打印日歷的小模塊,就象UNIX程序 cal。calendar 模塊定義了星期每天的整數常量。) >>> MONDAY 0 >>> TUESDAY 1 >>> SUNDAY 6
使用這種技術,可以構建返回多值的函數,只要通過返回包含所有值的一個序列,調用者可以把返回值看成一個序列,或者將值賦給單個變量。
- 函數中返回多值
>>> import os # os.path操作文件路徑的函數,split函數可以分割整個路徑,返回一個包含路徑和文件名的序列。 >>> os.path.split("/music/ap/mahadeva.mp3") ('/music/ap', 'mahadeva.mp3') >>> (filepath, filename) = os.path.split("/music/ap/mahadeva.mp3") >>> filepath '/music/ap' >>> filename 'mahadeva.mp3' >>> (shortname, extension) = os.path.splitext(filename) # 分割了一個文件名 >>> shortname 'mahadeva' >>> extension '.mp3'
只要有可能,你最好使用在 os 和 os.path 中的函數來處理對文件,目錄,和路徑的操作。這些模塊是對於平台特定模塊的封裝產物,所以象 os.path.split 之類的函數可以工作在UNIX,Windows,MacOS,和其它任何支持Python的平台上。利用多變量賦值甚至有更多可以做的,可以用在遍歷一個序列列表的時候,意味着可將它用於for循環和列表映射當中,可能認為序列列表不是每天都要用到的東西,但是實際上字典的Items方法就返回一個序列列表,每個序列的形式為(key, value)。所以多變量賦值允許你通過簡單的方法來遍歷字典的元素。
- 遍歷字典
>>> for k, v in os.environ.items() #os.environ 是一個在你的系統中所定義的環境變量的字典。在Windows下,它們是你的用戶和系統變量,可以容易地從MS-DOS中得到。在UNIX下,它們是輸出(export)到你的shell啟動腳本的變量。在MacOS下,沒有環境變量的概念,所以這個字典為空。 ... print "%s=%s" % (k, v) USERPROFILE=C:\Documents and Settings\mpilgrim OS=Windows_NT PROCESSOR_IDENTIFIER=x86 Family 6 Model 6 Stepping 10, GenuineIntel COMPUTERNAME=MPILGRIM USERNAME=mpilgrim #os.environ.items() 返回一個序列列表:[(key1,value1),(key2,value2),...]。for 循環遍歷這個列表。第一輪,它將 key1 賦給 k 而 value1 賦給 v,所以 k = USERPROFILE 而 v = C:\Documents and Settings\mpilgrim。第二輪,k 得到第二個關鍵字,OS,而 v 得到相對的值,Windows_NT。 […snip…]
使用多變量賦值不是絕對必需的。它是一種方便的簡寫,且可以讓你的代碼更加可讀,特別是當處理字典時(通過 items方法)。但是如果發現你迫使自已的代碼通過種種周折(為了以正確的形式得到數據),只是為了讓你可以一次給兩個變量賦值,可能就不值得那么做了。
- 通過多變量賦值進行字典映射
>>> print "\n".join(["%s=%s" % (k, v) for k, v in os.environ.items()]) USERPROFILE=C:\Documents and Settings\mpilgrim OS=Windows_NT PROCESSOR_IDENTIFIER=x86 Family 6 Model 6 Stepping 10, GenuineIntel COMPUTERNAME=MPILGRIM USERNAME=mpilgrim […snip…] #多變量賦值也可以用於列表映射,使用這種簡捷的方法來將字典映射成列表。本例中,我們通過將列表連接成一個字符串使得這種用法更深一步。注意它的輸出與前例中的 for 循環一樣。這就是為什么你在Python中看到那么少的 for 循環的原因;許多復雜的事情可以不用它們完成。你可以討論是否這種方法更易讀,但是它相當快,因為只有一條輸出語句而不是許多。
- 在MP3FileInfo中的多變量for循環
tagDataMap = {"title" : ( 3, 33, stripnulls), "artist" : ( 33, 63, stripnulls), "album" : ( 63, 93, stripnulls), "year" : ( 93, 97, stripnulls), "comment" : ( 97, 126, stripnulls), "genre" : (127, 128, ord)} . . . if tagdata[:3] == "TAG": for tag, (start, end, parseFunc) in self.tagDataMap.items(): self[tag] = parseFunc(tagdata[start:end])
tagDataMap 是一個類屬性,它定義了我們正在一個MP3文件中所查找的標記。標記被保存在定長的字段中;一旦我們讀出文件的最后128個字節,字節3到32是歌曲的題目,33-62是歌手名字,63-92是專集名字,等等。注意tagDataMap 是一個序列字典,每個序列包含兩個整數和一個函數引用。#這個看上去有些復雜,其實不是。for 變量結構與通過 items 返回的列表元素的結構相匹配。記住,items 返回一個形式為(key,value)的序列列表。列表的第一個元素是("title", (3, 33, <function stripnulls>)),所以循環的第一輪,tag 得到 "title",start 得到 3,end 得到 33,而 parseFunc 得到函數 stripnulls。#現在我們已經提取出了單個MP3標記的所有參數,保存標記數據很容易。我們從 start 到end 划分 tagdata 以得到這個標記的實際數據,調用 parseFunc來對數據進行后續處理,然后將它作為關鍵字的值賦給偽字典 self 的 tag 關鍵字。在遍歷了 tagDataMap 中所有元素之后, self 擁有所有標記的值,並且你知道那看上去象什么。
- 一次賦值多個