Python學習筆記整理(十六) 類的設計

如何使用類來對有用的對象進行建模? 一、Python和OOP Python和OOP實現可以概括為三個概念。 繼承     繼承是基於Python中屬性查找(在X.name表達式中) 多態     在X.method方法中,method的意義取決於X的類型(類) 封裝     方法和運算符實現行為,數據隱藏默認是一種慣例。 封裝指的是在Python中打包,也就是把實現的細節隱藏在對象介面之後。這並不代表有強制的私有性。封裝可以讓對象介面的現實 出現變動時,不影響這個對象的用戶。 1、不要通過調用標記進行重載 不要在同一個類中對同一個方法名定義兩次,後面的會覆蓋前面,也不要對對象類型進行測試。應該把程式程式碼寫成預期的對象介面。而不是特定類型的數據類型。 2、類作為記錄 通過類的實例來創建多個記錄。 3、類和繼承:是「一個」關係 (is a) 從程式設計師的角度來看,繼承是由屬性點號運算啟動的,由此觸發實例,類以及任何超類中變數名搜索。 從設計師的角度看,繼承是一種定義集合成員關係的方式:類定義了一組內容屬性,可由更具體的集合(子類)繼承和訂製。 子類和超類的繼承是1對1的關係. PizzaRobot是一種Chef,Chef是一種Employee.以OOP術語來看,我們稱這些關係為「是一個連接」(is a):機器人是個主廚,主廚是一個員工。 class Employee:         def __init__(self,name,salary=0):                 self.name=name                 self.salary=salary         def giveRaise(self,percent):                 self.salary=self.salary+(self.salary*percent)         def work(self):                 print self.name,"does stuff"         def __repr__(self):                 return "<Employee:name=%s,salary=%s>" % (self.name,self.salary) class Chef(Employee):         def __init__(self,name):                 Employee.__init__(self,name,5000)         def work(self):                 print self.name,"make food" class Server(Employee):         def __init__(self,name):                 Employee.__init__(self,name,40000)         def work(self):                 print self.name,"interface with customer" class PizzaRobot(Chef):            def __init__(self,name):#有點想不明白,既然繼承就夠了,為什麼還要在這裡構造                 Chef.__init__(self,name)    #Chef.__init__(self,name) =》Employee.__init__(self,name,5000)=>__init__(self,name,salary=0)         def work(self):                 print self.name,"make pizza" if __name__=='__main__':         bob=PizzaRobot('bob')         print bob         bob.work()         bob.giveRaise(0.20)         print bob;print # python employees.py   <Employee:name=bob,salary=5000> bob make pizza <Employee:name=bob,salary=6000.0> 理解有問題的地方 class PizzaRobot(Chef):            def __init__(self,name):#有點想不明白,既然繼承就夠了,為什麼還要在這裡構造,下面拿掉這裡做對比                 Chef.__init__(self,name)    #Chef.__init__(self,name) =》Employee.__init__(self,name,5000)=>__init__(self,name,salary=0) 連接過程         def work(self):                 print self.name,"make pizza" 下面拿掉PizzaRobot類中的def __init__(self,name): # python employees.py <Employee:name=bob,salary=5000> bob make pizza <Employee:name=bob,salary=6000.0 結果一樣,那這個函數還有什麼必要做構造,直接繼承就可以了 導入進行其他測試 >>> import employees >>> obj=employees.Employee(employees.Employee.__name__) >>> obj.work Employee does stuff if下面增加如下程式碼:         for klass in Employee,Chef,Server,PizzaRobot:                 obj=klass(klass.__name__)                 obj.work() 程式碼對應的結果 #python employees.py Employee does stuff Chef make food Server interface with customer PizzaRobot make pizza 4、類和組合:」有一個「關係 (has a) 從程式設計師的角度來看,組合設計到把其他對象嵌入到容器對象內,並使其實現容器方法。 對設計師來說,組合是另一種表示問題領域中的關係的方法。 但是組合不是集合的成員關係,而是組件,也是整體的組成部分。 組合也反映了個組成部分之間的關係,通常稱為「有一個」(has a)關係。Python中,「組合」(聚合)就是指內嵌對象集合體。 組合類一般都提供自己的介面,並通過內嵌的對象來實現介面。 現在,我們有了員工,把他們放到披薩店。我們的披薩店是一個組合對象,有烤爐,也有服務員和主廚這些員工。當顧客來下單 時,店裡的組件就開始行動:服務員接下訂單,主廚製作披薩等,pizzashop.py模擬 from employees import PizzaRobot,Server class Customer:         def __init__(self,name):                 self.name=name         def order(self,server):                 print self.name,"oders from",server         def pay(self,server):                 print self.name,"pays for item to",server class Oven:         def bake(self):                 print "oven bakes" class PizzaShop:         def __init__(self):                 self.server=Server('Pat')                 self.chef=PizzaRobot('Bob')                 self.oven=Oven()         def order(self,name):                 customer=Customer(name)                 customer.order(self.server)                 self.chef.work()                 self.oven.bake()                 customer.pay(self.server) if __name__=="__main__":         scene=PizzaShop()         scene.order('lily')         print '...'         scene.order('kelly') # python pizzashop.py lily oders from <Employee:name=Pat,salary=40000> Bob make pizza oven bakes lily pays for item to <Employee:name=Pat,salary=40000> ... kelly oders from <Employee:name=Pat,salary=40000> Bob make pizza oven bakes kelly pays for item to <Employee:name=Pat,salary=40000> PizzaShop類是容器和控制器,其構造器會創建employees模組中員工類的實例,並將其嵌入。當模組的自我檢查程式程式碼 調用PizzaShop的order方法時,內嵌的對象會按順序進行工作。注意:每份訂單創建了Customer對象,而且把內嵌的Server 對象傳遞給Customer方法,顧客是流動的,但是服務員是披薩店的組成部分。另外,員工也涉及到了繼承關係,組合和繼承 是互補工具。 if檢測改寫一下,通過參數傳遞客戶給模組 if __name__=="__main__":         try:                 customer=getargv[1]         except:                 print "please give argv as customer!"         else:                 scene=PizzaShop()                 scene.order(customer)                 print '...' # python pizzashop.py tom tom oders from <Employee:name=Pat,salary=40000> Bob make pizza oven bakes tom pays for item to <Employee:name=Pat,salary=40000> ... 5、重訪串流處理器 # vim streams.py class Processor:         def __init__(self,reader,writer):                 self.reader=reader                 self.writer=writer         def process(self):                 while 1:                         data=self.reader.readline()                         if not data:break #這裡有錯誤是not                         data=self.converter(data)                         self.writer.write(data)  #這裡有錯誤write         def converter(self,data)                 assert 0,'converter must be defined # vim converter.py            from streams import Processor from sys import stdout as output class Uppercase(Processor):                 def converter(self,data):                         return data.upper() if __name__=="__main__":         Uppercase(openfile('/etc/rc.conf'),output).process() 錯誤from sys import open as openfile ofreebsd# python converter.py Traceback (most recent call last):   File "converter.py", line 2, in <module>     from sys import open as openfile open是內置函數無需導入、 6、類和持續性 持續性:保證數據的持續性,將數據保存在文件或者資料庫,繼續保存。 pickle和shelve模組和類實例結合起來使用效果很好,通過單個步驟存儲到硬碟上。 >>> from pizzashop import PizzaShop >>> import pickle >>> obj=PizzaShop() >>> obj.server <Employee:name=Pat,salary=40000> file=open('pizzashop_data','w') >>> pickle.dump(obj.server,file) #寫入 >>> file.close() # cat pizzashop_data (iemployees Server p0 (dp1 S'salary' p2 I40000 sS'name' p3 S'Pat' p4 讀取 >> objread=pickle.load(file)       >>> print objread <Employee:name=Pat,salary=40000>ile=open('pizzashop_data','r') pickle機制把記憶體中的對象轉換成序列化的位元組流,可以保存在文件中。 Shelve會自動把對象pickle生成按鍵讀取的資料庫,而此資料庫導出類似於字典的介面. >>> from pizzashop import PizzaShop >>> obj=PizzaShop() >>> obj.server <Employee:name=Pat,salary=40000> >>> import shelve >>> dbase=shelve.open('datafile') >>> dbase['key']=obj.server 寫入 >>> dbase.sync <bound method DbfilenameShelf.sync of {'key': <Employee:name=Pat,salary=40000>}> # cat datafile.db >>> dbase['name']='diege' #寫入 >>> dbase.sync          <bound method DbfilenameShelf.sync of {'name': 'diege', 'key': <Employee:name=Pat,salary=40000>}> >>> dbase.close() # cat datafile.db a?        (?n} ¤?¤S'diege' p1 .name(iemployees Server p1 (dp2 S'salary' p3 I40000 sS'name' p4 S'Pat' p5 讀取 >>> import shelve >>> shtest=shelve.open('datafile') >>> shtest['key'] <Employee:name=Pat,salary=40000> >>> shtest['name']       'diege' 二、OOP和委託 所謂的委託,通常就是指控制器對象內嵌其他對象,而把運算請求傳給那些對象。控制器負責管理工作。 在Python中,委託通常是以__getattr__鉤子方法實現的,因為這個方法會攔截對不存在屬性的讀取,包裝類(代理類)可以使用__getattr__把任意讀取轉發給包裝的對象。包裝類包有被包括對象的介面。而且自己也可以增加其他運算。 class wrapper:     def __init__(self,object):         self.wrapped=object     def __getattr__(self,attrname):        #__getattr__點號運算,這裡重載內置getattr方法列印傳入類執行的方法,並把屬性請求傳入給對象,使用對象默認的方法。委託         print 'Trace:',attrname         return getattr(self.wrapped,attrname) __getattr__會獲得屬性名稱字元串。這個程式程式碼利用getattr內置函數,以變數名字元串從包裹對象取出屬性:getattr(X,Z) 就像X.N,只不過N是表達式,可以在運行時計算出字元串,而不是變數。getattr(X,Z)類似於X.__dict__[N]. 可以使用這個模組包裝類的做法,管理人和帶有屬性的對象的存取:列表,字典甚至是類和實例。 在這裡wrapper類只是在每個屬性讀取時列印跟蹤資訊,【並把屬性請求委託給嵌入的wrapped對象。(對象自己的方法)】 >>> from trac import wrapper >>> x=wrapper([1,2,3]) >>> x.append Trace: append <built-in method append of list object at 0x2850f7ec> >>> x.append(4) Trace: append >>> for n in x:print n ... Trace: __iter__ 1 2 3 4 >>> x.wrapped [1, 2, 3, 4] >>> x.__dict__ {'wrapped': [1, 2, 3, 4]} >>> x=wrapper({"a":1,"b":2}) >>> x.keys() Trace: keys ['a', 'b'] >>> x.__dict__ {'wrapped': {'a': 1, 'b': 2}} >>> x         Trace: __repr__ {'a': 1, 'b': 2} 三、多重繼承 在class語句中,首行括弧內可以列出一個以上的超類。當這麼做時,就在使用所謂的多重繼承:類和其實例繼承了列出的所有超類的變數。搜索屬性時,Python會由左到右搜索類首行中的超類,直到找到相符者。 通常意義上講,多重繼承是模擬屬於一個集合以上的對象的好辦法,例如一個人可以是工程師,作家,音樂家。因為,可以繼承這些集合的特性。 多重繼承最常見的用戶是作為「混合」超類的通用方法。這類超類一般都稱呼混合類:他們提供方法,可以通過繼承將其加入應用類。 >>> x.__class__ <class trac.wrapper at 0x28503f8c> >>> x.__class__.__name__ 'wrapper' 每個實例都有內置的__class__屬性,引用了它所繼承的類,而每個類都有__name__屬性,用用了首行中的變數名,所以self.__class__.__name__是取出實例的類的名稱 >>> x.__class__.__module__       'trac' >>> x.__module__          'trac 而是用self.__module__或 self.__class__.__module__則取出實例引用模組的名稱 內置id函數傳回任意對象的地址(從定義上來將。這就是唯一的對象識別碼),從而獲得實例的記憶體地址。 # vim mytool.py class Lister:         def __repr__(self): #列印重載                 return ("<Instance of %s,address %s:n%s" %                                 (self.__class__.__name__,                                  id(self),                                  self.attrnames()) ) #抽象類         def attrnames(self):                 result=''                 for attr in self.__dict__.keys():                         if attr[:2]=='__':                                 result=result+"tname %s=<built-in>n" % attr                         else:                                 result=result+"tname %s=%sn" % (attr,self.__dict__[attr])                 return result 從這個類衍生的實例會在列印時自動顯示器屬性。 >>> from mytool import Lister >>> dir(Lister) ['__doc__', '__module__', '__repr__', 'attrname'] >>> class Diege(Lister): ...     def __init__(self): ...             self.data1='food' ... >>> y=Diege() >>> y <Instance of Diege,address 676368300:         name data1=food > 將__repr_修改為__str__ >>> y=Diege() >>> y >>> print y <Instance of Diege,address 676368300:         name data1=food > Lister類對所寫的任何類都有用:即時是已經有超類的類,這裡就是多重繼承方便之處:把Lister加到類首行的超類列表中(將其混合進來) 就可以獲得其__repe,同時依然繼承現有的超類。 # vim Tsmixin.py from mytool import Lister class Super:         def __init__(self):                 self.data1='diege' class Sub(Super,Lister):         def __init__(self):                 Super.__init__(self)                 self.data2="eggs"                 self.data3=42 if __name__=='__main__':         X=Sub()         print X # python Tsmixin.py   <Instance of Sub,address 676367724:         name data1=diege         name data3=42         name data2=eggs > 如果稍後你決定擴展Lister的__repr__,也要列印實例繼承的所有類屬性,那也很安全。因為這是繼承的方法,修改Lister.__repr__會自動更新每個導入類, 並將顯示器混合進來的子類的情況。 總之,混合類相當於模組:可用戶在各種客戶端的方法包。以下是Lister用在不同類實例上,採用單個繼承模式的情況。 >>> class X(Lister): ...     pass ...  >>> t=x() >>> t <Instance of X,address 676380940: > >>> t <Instance of X,address 676380940:         name a=1         name c=3         name b=2 > OOP其實就是程式碼重用,而混合是強大的工具。多重繼承是高級功能,如果用的過度或太隨意,就變得很複雜。 四、類是對象:通用對象的工廠 類是對象,因此它很容易在程式中進行傳遞,保存在資料庫結構中。也可以把類傳給產生任意種類對象的函數。這類函數在OOP設計領域偶爾稱為工廠。 工廠式的函數或程式程式碼,在一些情況下很方便,因為他們可以讓我們取出並傳入沒有預先在程式程式碼中硬編碼的類。實際上,這些類在編寫程式時可能還不存在。抽象類。 >>> def factory(aClass,*args): ...     return apply(aClass,args) ... >>> class Spam: ...     def doit(self,message): ...             print message ... >>> class Person:                   ...     def __init__(self,name,job): ...             self.name=name ...             self.job=job >>> object1=factory(Spam) >>> object2=factory(Person,"diege","lily") 這裡定義了一個對象生成器函數,稱為factory.它預期傳入的是類對象(任何對象都行),還有該類構造器的一個或多個參數。這個函數使用apply調用該函數並返回實例。 可以改進之處就是,在構造器調用中支援關鍵詞參數。函數factory能夠通過**agrs參數手機參數。當第三個參數傳給apply時: def factory(aClass,*args,**kagrs): ...     return apply(aClass,kargs) 在Python中一切都是對象。 五、方法是對象:綁定或無綁定 方法也是一種對象,很像函數。類方法能有實例或類來讀取。實際上Python中就有兩種方式。 無綁定類方法對象:無self     通過對類進行點號運算從而獲取類的函數屬性,會傳回無綁定(unboud)方法對象。調用該方法時,【必須明確提供實例對象】作為第一個參數。 綁定實例方法對象:self+函數對     通過對實例進行全運算從而獲取類的函數屬性,會傳回綁定(bound)方法對象。Python在綁定方法對象中自動把實例和函數打包,所以,不用傳遞實例去調用該方法。實例已擁有該方法。 這兩種方法都是功能齊全的對象,可四處傳遞,保持在列表內等。執行時兩者都需要第一參數的實例(也就是self的值). 調用綁定方法對象時,Python會自動提供實例來創建綁定方法對象的實例。也就是說綁定方法對象通常都可以和簡單函數對象互換,而且對原本就是針對函數而編寫的介面而言,非常用有。 >>> class Spam: ...     def doit(self,message): ...             print message >>> object1=Spam() >>> object1.doit('hellow world')            hellow world 可以幫這個綁定方法賦值給另一個變數名,然後像簡單函數那樣進行調用。 >>> object1=Spam() >>> x=object1.doit >>> x('hello world') hello world 六、類和模組 都是命名空間 模組         * 是數據/邏輯套件         * 由Python文件或C擴展編寫成         * 通過導入使用 類         *實現新的對象         *由class語句創建         *通過調用使用         *總是存在於模組中。 類支援其他模組不支援的功能。例如,運算符重載,產生多個實例以及繼承。 七、類陷阱 修改類屬性的副作用 多重繼承:順序很重要 類,方法以及嵌套作用域 小結: 委託:把對象包裝在代理類中 組合:控制嵌入的對象 繼承:從其他類中獲取行為 多重繼承,綁定方法,工廠函數

Tags: