day27--多態、封裝、反射
一、多態
多態指的是一類事物有多種形態,(一個抽象類有多個子類,因而多態的概念依賴於繼承)
import abc class Animal(metaclass=abc.ABCMeta): #同一類事物:動物 @abc.abstractmethod def talk(self): pass class People(Animal): #動物的形態之一:人 def talk(self): print(‘say hello‘) class Dog(Animal): #動物的形態之二:狗 def talk(self):print(‘say wangwang‘) class Pig(Animal): #動物的形態之三:豬 def talk(self): print(‘say aoao‘)
多態性是指具有不同功能的函數可以使用相同的函數名,這樣就可以用一個函數名調用不同內容的函數。(取決於是哪個具體的對象調用了那個函數名)
在面向對象方法中一般是這樣表述多態性:向不同的對象發送同一條消息,不同的對象在接收時會產生不同的行為(即方法)。也就是說,每個對象可以用自己的方式去響應共同的消息。所謂消息,就是調用函數,不同的行為就是指不同的實現,即執行不同的函數。
>>> deffunc(animal): #參數animal就是對態性的體現 ... animal.talk() ... >>> people1=People() #產生一個人的對象 >>> pig1=Pig() #產生一個豬的對象 >>> dog1=Dog() #產生一個狗的對象 >>> func(people1) say hello >>> func(pig1) say aoao >>> func(dog1) say wangwang #綜上我們也可以說,多態性是‘一個接口(函數func)
多態性優點:
1.增加了程序的靈活性
以不變應萬變,不論對象千變萬化,使用者都是同一種形式去調用,如func(animal)
2.增加了程序額可擴展性
通過繼承animal類創建了一個新的類,使用者無需更改自己的代碼,還是用func(animal)去調用
>>> class Cat(Animal): #屬於動物的另外一種形態:貓 ... def talk(self): ... print(‘say miao‘) ... >>> def func(animal): #對於使用者來說,自己的代碼根本無需改動 ... animal.talk() ... >>> cat1=Cat() #實例出一只貓 >>> func(cat1) #甚至連調用方式也無需改變,就能調用貓的talk功能 say miao ‘‘‘ 這樣我們新增了一個形態Cat,由Cat類產生的實例cat1,使用者可以在完全不需要修改自己代碼的情況下。使用和人、狗、豬一樣的方式調用cat1的talk方法,即func(cat1) ‘‘‘
二、封裝
從封裝本身的意思去理解,封裝就好像是拿來一個麻袋,把超市買來的一堆零食起裝進麻袋,然後把麻袋封上口子。你不知道裏面到底裝的是什麽好吃的(片面的理解)。
封裝其實分為兩個層面,但無論哪種層面的封裝,都要對外界提供好訪問你內部隱藏內容的接口(接口可以理解為入口,有了這個入口,使用者無需且不能夠直接訪問到內部隱藏的細節,只能走接口,並且我們可以在接口的實現上附加更多的處理邏輯,從而嚴格控制使用者的訪問)
第一個層面的封裝(什麽都不用做):創建類和對象會分別創建二者的名稱空間,我們只能用類名.或者obj.的方式去訪問裏面的名字,這本身就是一種封裝
註意:對於這一層面的封裝(隱藏),類名.和實例名.就是訪問隱藏屬性的接口
第二個層面的封裝:類中把某些屬性和方法隱藏起來(或者說定義成私有的),只在類的內部使用、外部無法訪問,或者留下少量接口(函數)供外部訪問。
在python中用雙下劃線的方式實現隱藏屬性(設置成私有的)
類中所有雙下劃線開頭的名稱如__x都會自動變形成:_類名__x的形式:
class A: __N=0 #類的數據屬性就應該是共享的,但是語法上是可以把類的數據屬性設置成私有的如__N,會變形為_A__N def __init__(self): self.__X=10 #變形為self._A__X def __foo(self): #變形為_A__foo print(‘from A‘) def bar(self): self.__foo() #只有在類內部才可以通過__foo的形式訪問到.
1.類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果。
2.這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。
3.在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。
註意:對於這一層面的封裝(隱藏),我們需要在類中定義一個函數(接口函數)在它內部訪問被隱藏的屬性,然後外部就可以使用了。
python並不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,如果模塊名以單下劃線開頭,那麽from module import *時不能被導入,但是你from module import _private_module依然是可以導入的。
三、反射
自省給與你,程序員,某種能力來進行像‘手工類型檢查‘的工作,它也被稱為反射。主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力(自省)
python面向對象中的反射:通過字符串的形式操作對象相關的屬性。python中的一切事物都是對象(都可以使用反射)
判斷object中有沒有一個name字符串對應的方法或屬性
有則返回True 沒有返回False
hasattr(object,name)
def getattr(object, name, default=None): # known special case of getattr """ getattr(object, name[, default]) -> value Get a named attribute from an object; getattr(x, ‘y‘) is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn‘t exist; without it, an exception is raised in that case. """ pass 得到的是相應的內存地址 default 為指定報錯內容(字符串形式)getattr(object,name,default=None)
def setattr(x, y, v): # real signature unknown; restored from __doc__ """ Sets the named attribute on the given object to the specified value. setattr(x, ‘y‘, v) is equivalent to ``x.y = v‘‘ """ passsetattr(x,y,v)
def delattr(x, y): # real signature unknown; restored from __doc__ """ Deletes the named attribute from the given object. delattr(x, ‘y‘) is equivalent to ``del x.y‘‘ """ passdefault()
class BlackMedium: feature=‘Ugly‘ def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print(‘%s 黑中介賣房子啦,傻逼才買呢,但是誰能證明自己不傻逼‘ %self.name) def rent_house(self): print(‘%s 黑中介租房子啦,傻逼才租呢‘ %self.name) b1=BlackMedium(‘萬成置地‘,‘回龍觀天露園‘) #檢測是否含有某屬性 print(hasattr(b1,‘name‘)) print(hasattr(b1,‘sell_house‘)) #獲取屬性 n=getattr(b1,‘name‘) print(n) func=getattr(b1,‘rent_house‘) func() # getattr(b1,‘aaaaaaaa‘) #報錯 print(getattr(b1,‘aaaaaaaa‘,‘不存在啊‘)) #設置屬性 setattr(b1,‘sb‘,True) setattr(b1,‘show_name‘,lambda self:self.name+‘sb‘) print(b1.__dict__) print(b1.show_name(b1)) #刪除屬性 delattr(b1,‘addr‘) delattr(b1,‘show_name‘) delattr(b1,‘show_name111‘)#不存在,則報錯 print(b1.__dict__)四個方法的使用演示
class Foo(object): staticField = "old boy" def __init__(self): self.name = ‘wupeiqi‘ def func(self): return ‘func‘ @staticmethod def bar(): return ‘bar‘ print getattr(Foo, ‘staticField‘) print getattr(Foo, ‘func‘) print getattr(Foo, ‘bar‘)類也是對象
#!/usr/bin/env python # -*- coding:utf-8 -*- import sys def s1(): print ‘s1‘ def s2(): print ‘s2‘ this_module = sys.modules[__name__] hasattr(this_module, ‘s1‘) getattr(this_module, ‘s2‘)反射當前模塊成員
導入其他模塊,利用反射查找該模塊是否存在某個方法
def test(): print(‘from the test‘)module_test.py
#!/usr/bin/env python # -*- coding:utf-8 -*- """ 程序目錄: module_test.py index.py 當前文件: index.py """ import module_test as obj #obj.test() print(hasattr(obj,‘test‘)) getattr(obj,‘test‘)()
反射好處一:可以事先定義好接口,接口只有在被完成後才會真正執行,這實現了即插即用,這其實是一種‘後期綁定‘,什麽意思?即你可以事先把主要的邏輯寫好(只定義接口),然後後期再去實現接口的功能。
class FtpClient: ‘ftp客戶端,但是還麽有實現具體的功能‘ def __init__(self,addr): print(‘正在連接服務器[%s]‘ %addr) self.addr=addr還沒有實現功能
#from module import FtpClient f1=FtpClient(‘192.168.1.1‘) if hasattr(f1,‘get‘): func_get=getattr(f1,‘get‘) func_get() else: print(‘---->不存在此方法‘) print(‘處理其他的邏輯‘)不影響另一個人寫代碼
反射好處二:動態導入模塊(基於反射當前模塊成員)
__import__(‘package.test‘) #定位到package(解釋器內部的) import importlib m = importlib.import_module(‘package.test‘) #定位到package.test #官方建議用第二種方法
四、__setattr__,__delattr__,__getattr__
class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): print(‘----> from getattr:你找的屬性不存在‘) def __setattr__(self, key, value): print(‘----> from setattr‘) # self.key=value #這就無限遞歸了,你好好想想 # self.__dict__[key]=value #應該使用它 def __delattr__(self, item): print(‘----> from delattr‘) # del self.item #無限遞歸了 self.__dict__.pop(item) #__setattr__添加/修改屬性會觸發它的執行 f1=Foo(10) print(f1.__dict__) # 因為你重寫了__setattr__,凡是賦值操作都會觸發它的運行,你啥都沒寫,就是根本沒賦值,除非你直接操作屬性字典,否則永遠無法賦值 f1.z=3 print(f1.__dict__) #__delattr__刪除屬性的時候會觸發 f1.__dict__[‘a‘]=3#我們可以直接修改屬性字典,來完成添加/修改屬性的操作 del f1.a print(f1.__dict__) #__getattr__只有在使用點調用屬性且屬性不存在的時候才會觸發 f1.xxxxxx三者的用法演示
五、二次加工標準類型(包裝)
包裝:python為大家提供了標準數據類型,以及豐富的內置方法,其實在很多場景下我們都需要基於標準數據類型來定制我們自己的數據類型,新增/改寫方法,這就用到了我們剛學的繼承/派生知識(其他的標準類型均可以通過下面的方式進行二次加工)
class List(list): #繼承list所有的屬性,也可以派生出自己新的,比如append和mid def append(self, p_object): ‘ 派生自己的append:加上類型檢查‘ if not isinstance(p_object,int): raise TypeError(‘must be int‘) super().append(p_object) @property def mid(self): ‘新增自己的屬性‘ index=len(self)//2 return self[index] l=List([1,2,3,4]) print(l) l.append(5) print(l) # l.append(‘1111111‘) #報錯,必須為int類型 print(l.mid) #其余的方法都繼承list的 l.insert(0,-123) print(l) l.clear() print(l)二次加工標準類型(基於繼承實現)
授權:授權是包裝的一個特性, 包裝一個類型通常是對已存在的類型的一些定制,這種做法可以新建,修改或刪除原有產品的功能。其它的則保持原樣。授權的過程,即是所有更新的功能都是由新類的某部分來處理,但已存在的功能就授權給對象的默認屬性。
實現授權的關鍵點就是覆蓋__getattr__方法
import time class FileHandle: def __init__(self,filename,mode=‘r‘,encoding=‘utf-8‘): self.file=open(filename,mode,encoding=encoding) def write(self,line): t=time.strftime(‘%Y-%m-%d %T‘) self.file.write(‘%s %s‘ %(t,line)) def __getattr__(self, item): return getattr(self.file,item) f1=FileHandle(‘b.txt‘,‘w+‘) f1.write(‘你好啊‘) f1.seek(0) print(f1.read()) f1.close()授權示範一
#_*_coding:utf-8_*_ __author__ = ‘Linhaifeng‘ #我們來加上b模式支持 import time class FileHandle: def __init__(self,filename,mode=‘r‘,encoding=‘utf-8‘): if ‘b‘ in mode: self.file=open(filename,mode) else: self.file=open(filename,mode,encoding=encoding) self.filename=filename self.mode=mode self.encoding=encoding def write(self,line): if ‘b‘ in self.mode: if not isinstance(line,bytes): raise TypeError(‘must be bytes‘) self.file.write(line) def __getattr__(self, item): return getattr(self.file,item) def __str__(self): if ‘b‘ in self.mode: res="<_io.BufferedReader name=‘%s‘>" %self.filename else: res="<_io.TextIOWrapper name=‘%s‘ mode=‘%s‘ encoding=‘%s‘>" %(self.filename,self.mode,self.encoding) return res f1=FileHandle(‘b.txt‘,‘wb‘) # f1.write(‘你好啊啊啊啊啊‘) #自定制的write,不用在進行encode轉成二進制去寫了,簡單,大氣 f1.write(‘你好啊‘.encode(‘utf-8‘)) print(f1) f1.close()授權示範二
#練習一 class List: def __init__(self,seq): self.seq=seq def append(self, p_object): ‘ 派生自己的append加上類型檢查,覆蓋原有的append‘ if not isinstance(p_object,int): raise TypeError(‘must be int‘) self.seq.append(p_object) @property def mid(self): ‘新增自己的方法‘ index=len(self.seq)//2 return self.seq[index] def __getattr__(self, item): return getattr(self.seq,item) def __str__(self): return str(self.seq) l=List([1,2,3]) print(l) l.append(4) print(l) # l.append(‘3333333‘) #報錯,必須為int類型 print(l.mid) #基於授權,獲得insert方法 l.insert(0,-123) print(l) #練習二 class List: def __init__(self,seq,permission=False): self.seq=seq self.permission=permission def clear(self): if not self.permission: raise PermissionError(‘not allow the operation‘) self.seq.clear() def __getattr__(self, item): return getattr(self.seq,item) def __str__(self): return str(self.seq) l=List([1,2,3]) # l.clear() #此時沒有權限,拋出異常 l.permission=True print(l) l.clear() print(l) #基於授權,獲得insert方法 l.insert(0,-123) print(l)練習題(授權)
資料來源:海峰老師
day27--多態、封裝、反射