多態,最淺顯的意識就是同一事物具備多種形態,這很好理解,動物是一個大類,貓類屬於動物類,狗類屬於動物類,人也是屬於動物類,那能理解成,貓、狗、人是同樣的嗎?固然不是,還有,水,分爲液體、固體、氣體,但它們都是H20構成的,這也是多態。意味着對於不一樣的類的對象,能夠執行相同的操做,html
多態是面嚮對象語言的一個基本特性,多態就意味着變量不須要知道引用的對象是什麼,經過一個統一的接口,對引入的不一樣對象表現出不一樣的行爲方式。python
下面這個例子編程
1 import abc 2 class Animal(metaclass=abc.ABCMeta): 3 @abc.abstractmethod 4 def yell(self): 5 pass 6 class Dog(Animal): 7 def yell(self): 8 print("汪汪汪~") 9 class Cat(Animal): 10 def yell(self): 11 print("喵喵喵")
在上面這段代碼中,類Animal做爲一個大類,定義了yell方法,但對於Animal來講,有不少不一樣的形態表示,貓,狗、人等等都屬於Animal類,而每一個類都有不一樣的叫聲,這就是同一類,多種形態的表示方式。函數
面向對象的程序設計中,某個類把所須要的數據(也能夠說是類的屬性)和對數據的操做(也能夠說是類的行爲)所有都封裝在類中,分別稱爲類的成員變量和方法(或成員函數)。這種把成員變量和成員函數封裝在一塊兒的編程特性稱爲封裝。封裝,其實就是爲了對外部隱藏對象的代碼邏輯。測試
封裝在概念上和多態很類似,可是不等同於多態。多態可讓實例對不知道是什麼的類的對象進行屬性、方法的調用,而封裝是能夠不一樣關心對象是如何構建就能夠直接調用的。spa
在python中用雙下劃線開頭的方式,將屬性隱藏起來。雙下劃線+屬性名就是私有的標識符,會自動變形爲_類名__屬性名的形式,即__x都會變造成_類名__x的形式,能夠從類的屬性字典中很明顯的看到這種變形。這種變換不會關注標識符的語法位置,所以能夠用來定義類私有的實例和類變量、方法、全局變量。設計
看下面這個例子:建立一個類Person,類變量,全局變量,和方法都有經過雙下劃線__設置的私有變量代理
1 class Person: 2 __skin = "yellow" #設置類的數據屬性爲私有的 3 hair = "black" 4 def __init__(self,name,age,height): 5 self.__name = name #變形爲self._Person_name的形式 6 self.age = age 7 self.height = height 8 def __dance(self): #變形爲_Person__dance的形式 9 print("%s正在跳舞"%self.name) 10 def sing(self): 11 print("%s正在唱歌"%self.name) 12 p_1 = Person("Tony",18,160)
在這個例子中,__skin、__self.name、__dance都是私有變量,外部的實例是沒法直接訪問的,經過實例p_1.屬性\方法的形式,是沒法調用這3個屬性的,這就是私有變量的意義所在。code
但其實,這只是一種python的約定,若是非想在外部的實例調用其私有變量,也並非不可能。若是知道了類名和想調用的私有屬性名,就能夠調用。htm
即p_1._Person__skin這種方式,(注意:類名前是一個下劃線,私有屬性前是雙下劃線),調用方法p_1._Person__dance()
封裝並不只僅是爲了將類中的屬性隱藏起來,更多的需求是將私有屬性做爲外部調用的一個接口,用來對傳入的數據作相應的限制,以下面這個例子:
1 class Person: 2 def __init__(self,name,age): 3 self.__name = name 4 self.__age = age 5 def person_info(self): 6 print("My name is %s,My age is %s"%(self.__name,self.__age)) 7 def set_person_info(self,name,age): 8 if not isinstance(name,str): 9 raise TypeError("名字必須是字符串類型") 10 if not isinstance(age,int): 11 raise TypeError("年齡必須是整數類型") 12 self.__name = name 13 self.__age = age 14 p_1 = Person("Tony",23) 15 p_1.person_info() 16 p_1.set_person_info(11,"An") 17 p_1.person_info()
在上面這個例子中,想要調用person_info()這個方法來顯示出一我的的姓名和年齡,可是若是想要限制用戶輸入的姓名和年齡的數據類型,就要經過set_person_info()這個方法來做爲用戶輸入的一個接口。
單下劃線定義的模塊名,不用from..import..的方式導入
受權是封裝的一個特性,
反射(或自省)主要是指程序能夠訪問、檢測、和修改它自己狀態或行爲的一種能力,在python中,經過字符串的形式操做對象相關的屬性,python中的一切事物都是對象,即均可以使用反射。
hasattr、getattr、setattr、delattr
四個能夠實現自省的函數:
hasattr(object,name) |
判斷對象是否包含對應的屬性。 object -- 對象 name -- 字符串,屬性名 |
getattr(object,name,[default]) |
getattr() 函數用於返回一個對象屬性值。 object -- 對象。 name -- 字符串,對象屬性。 default -- 默認返回值,若是不提供該參數,在沒有對應屬性時,將觸發 AttributeError。 |
setattr(object,name,value) |
setattr 函數對應函數 getatt(),用於設置屬性值,該屬性必須存在。 object – 對象 name - 字符串,屬性名 value - 屬性值
|
delattr(object, name) |
delattr 函數用於刪除屬性。 delattr(x, 'foobar') 相等於 del x.foobar。 object -- 對象。 name -- 必須是對象的屬性。 |
經過下面這個實例,來演示一下4個函數的使用方法:
1 class Person: 2 skin = "yellow" 3 hair = "black" 4 def __init__(self,name,age): 5 self.name = name 6 self.age = age 7 def dance(self): 8 print("%s正在跳舞"%self.name) 9 p_1 = Person("Tony",18) 10 #判斷Person類中是否含有dance屬性, 11 print(hasattr(Person,"dance")) 12 #獲得Person類中的skin屬性的值,若是傳入的是方法名,返回的就是方法的內存地址 13 print(getattr(Person,"skin")) 14 print(getattr(Person,"skin11")) #屬性不存在,程序報錯 15 #修改Person類中skin屬性的值,類屬性字典中也會作相應改變,經過Person.__dict__查看 16 setattr(Person,"skin","black") 17 print(getattr(Person,"skin")) 18 #刪除Person類中hair這個屬性,類的屬性字典中也會刪除 19 delattr(Person,"hair") 20 delattr(Person,"hair11") #屬性不存在,報錯
反射有什麼好處?反射就能夠事先定義好接口,這意味這什麼呢?就是能夠先寫好主要的邏輯,而後再去後期實現實現的功能。
__getattr__、__setattr__、__delattr__
1 class Attr: 2 def __init__(self,x): 3 self.x = x 4 def __getattr__(self, item): 5 print("--->getter") 6 def __setattr__(self, key, value): 7 print("--->setattr") 8 def __delattr__(self, item): 9 print("--->delattr")
在這個例子中,重寫了__getattr__、__setattr__、__delattr__這三個方法。這三個方法的存在會對類發生什麼改變呢?
__getattr__:
只有在實例調用屬性且調用的屬性不存在纔會運行
1 print(a_1.x) 2 運行結果: 3 --->setattr #只要賦值操做,就會觸發__setattr__方法 4 6 5 print(a_1.y) 6 運行結果: 7 --->setattr 8 --->getter 9 None
還有一個__getattribute__方法,不管調用的屬性是否存在,都會觸發__getattribute__方法。當__getattribute__和__getattr__同時存在的話,只會執行__getattribute__,除非__getattribute__在執行的過程當中拋出異常
1 a_1 = Attr(6) 2 print(a_1.__dict__) 3 運行結果: 4 {'x': 6} 5 刪除註釋,再次執行: 6 運行結果: 7 --->setattr 8 {}
先將這兩行註銷,而後建立一個實例並打印這個實例的屬性字典:
建立了實例,可是屬性確沒有寫進屬性字典中,這是爲何?
這是由於,凡是賦值的操做,都會觸發__setter__()方法的運行,而在上面代碼中,重寫了__setattr__方法,代碼邏輯僅僅有打印「--> setattr」操做,根本就沒有將屬性值寫入實例屬性的字典的操做,天然不會被寫入,除非直接操做屬性字典,不然根本沒法往裏面寫入值。那麼就須要進行以下修改:
1 class Attr: 2 def __init__(self,x): 3 self.x = x 4 def __getattr__(self, item): 5 print("--->getter") 6 self.__dict__[key]=value 7 def __setattr__(self, key, value): 8 print("--->setattr") 9 def __delattr__(self, item): 10 print("--->delattr")
直接將實例化傳入的值,寫入實例的屬性字典,有些人很容易將這步操做寫成
self.key = value ,這條語句是錯誤的,會致使程序無限遞歸。
與__setattr__實質相同,在重寫了__delattr__方法後,也須要定義相應的代碼來從實例的屬性字典中刪除。即:
1 class Attr: 2 def __init__(self,x): 3 self.x = x 4 def __getattr__(self, item): 5 print("--->getter") 6 self.__dict__[key]=value 7 def __setattr__(self, key, value): 8 print("--->setattr") 9 def __delattr__(self, item): 10 print("--->delattr") 11 self.__dict__.pop(item)
能夠先經過a_1.__dict__["a"]=1在實例的屬性字典中添加屬性a,進行刪除測試,一樣的,不能夠在__delattr__()後經過del self.item的方式來刪除,這樣一樣會引發無限遞歸。
描述符是Python語言中很深奧也很重要的一個概念,簡而言之,描述符也是一個對象,該對象表明了一個屬性的值,這就意味着,若是一個類中有一個屬性name,若是給該類添加了描述符,那麼描述符就是另外一個能表明屬性name的對象。描述符協議中定義了_get_、_set_、_del_這些特殊的方法,描述符是實現其中的一個或多個方法的對象。這樣說你們可能沒有辦法理解,仍是經過代碼講解吧。
__get__():調用一個屬性時,觸發
__set__():爲一個屬性賦值時,觸發
__delete__():採用del刪除屬性時,觸發
定義一個描述符:
1 class Descriptor: 2 def __get__(self, instance, owner): 3 pass 4 def __set__(self, instance, value): 5 pass 6 def __delete__(self, instance): 7 pass
前面說過了,描述符就是一個類,若是一個類中定義了這三個方法,這個類就被稱爲一個描述符。
在前面講過了__getattr__、__setattr__、__delattr__、方法,在由類產生實例的過程當中會相應的觸發這三個方法,而對_get_、_set_、_delete_來講,並不會被觸發,那麼觸發這三個函數的條件又是什麼?
1 class Descriptor: #建立描述符Descriptor 2 def __get__(self, instance, owner): 3 print("--->get") 4 def __set__(self, instance, value): 5 print("--->set") 6 def __delete__(self, instance): 7 print("--->delete") 8 9 10 class Person: 11 name = Descriptor() #屬性name被Descriptor描述符描述 12 def __init__(self,name): 13 self.name = name
首先,描述符對描述符自身是沒有用的,顯而易見,之因此叫描述符,是用來描述其餘對象的(類也是對象),從上段程序的運行結果能夠明顯的看出,name屬性被描述符代理後,經過實例調用、設置、刪除該屬性,實質上執行的都是描述符Descriptor中的邏輯。描述符不能夠被定義到構造函數中,必須賦值給類屬性。這樣寫有什麼意義呢?
1 p_1 = Person("Tony") 2 print(p_1.__dict__) 3 p_1.name = "An" 4 print(p_1.__dict__) 5 p_1.age = 18 6 print(p_1.__dict__) 7 運行結果: 8 --->set 9 {} 10 --->set 11 {} 12 {'age': 18}
建立了一個Person類的實例p_1,首先會觸發_set_()方法,可是p_1的name=Tony這個屬性並無寫入到它的屬性字典中,修改name的屬性爲An,一樣沒有寫入。這是由於,將Person類中的name這個屬性經過name = Descriptor() 的方式被描述符代理了,因此調用該屬性的時候,實質上就會去觸發描述符Descriptor中的_set_方法,因此就沒法寫入。在最後添加了一個屬性age,成功的寫入到了實例p_1的屬性字典中,是由於age這個屬性沒有被其餘描述符代理,使用的就是類默認的邏輯來執行。
若是想要自定義的描述中也具有屬性寫入的功能,能夠將Descriptor中的_set_()修改以下:
1 def __set__(self, instance, value): 2 print("--->set") 3 instance.__dict__["name"]=value
經過這個例子,能夠看到描述符的強大之處,有不少時候,咱們須要控制某些變量屬性,就能夠經過描述符來自定義想要的功能。
描述符分爲兩種:
數據描述符:至少實現了_get_()方法和_set_()方法。
非數據描述符:沒有實現_set_()方法。
在調用的時候,應遵循如下優先級:類屬性-->數據描述符-->實例屬性-->非數據描述符-->__getattr__()方法(若是找不到屬性的時候觸發)
__str__()和__repr__()方法:
1 class Person: 2 def __init__(self,name): 3 self.name = name 4 def __str__(self): 5 return ("my name is %s"%self.name) 6 p_1 = Person("An") 7 print(p_1)
__str__發放就是能夠自定義對象的字符串返回形式,若是不自定義__str__()方法,返回結果是:An,定義後,會按照定義的形式輸出結果,即:my name is An。
__repr__()方法能夠理解爲__str__()方法的替代品,若是__str__沒有被定義,就會使用__repr__()方法來代替輸出。
__slots__屬性:
__slots__實際上是一個類變量,訪問類或實例的時候,實質上就是在訪問類和實例的屬性字典__dict__,(類的屬性字典是共享的,即全部所屬該類的實例均可以訪問,實例的屬性字典是是獨立的)。
1 p_1 = Person() 2 p_1.name = "Tony" 3 #p_1.age = 18 #報錯 4 #print(p_1.__dict__) #報錯,定義了__slots__,就再也不有dict 5 print(p_1.__slots__) 6 運行結果: 7 name
可是字典是很佔用計算機內存的,當類的屬性不多,可是須要不少所屬該類的實例的時候,就能夠用__slots__來代替__dict__,__slots__中定義的變量就變成了類的描述符,類的實例只能擁有這些變量,可是不會擁有屬於本身的屬性字典,所以也就是不能增長新的變量。
經過__slots__,能夠限制實例的屬性,即減少的佔用的內存空間,也會大大提高屬性的訪問速度。
__next__和__iter__實現迭代器協議。
1 class Iterator(): 2 def __init__(self,x): 3 self.x = x 4 def __iter__(self): 5 return self 6 def __next__(self): 7 8 self.x+=1 9 return self.x 10 i_1 = Iterator(1) 11 for i in i_1: 12 print(i)
這樣就經過__iter__和__next__方法實現了迭代器。
斐波那契數列:
1 class Fibo(): 2 def __init__(self,x,y): 3 self.x = x 4 self.y = y 5 def __iter__(self): 6 return self 7 def __next__(self): 8 if self.x > 20: 9 raise StopIteration("超出終止") 10 self.x = self.y 11 self.y = self.x + self.y 12 return self.x 13 f_1= Fibo(0,1) 14 for i in f_1: 15 print(i)
在管理文件操做的時候,能夠經過with..open的方式來自動釋放文件的內存,在類中,若是想讓對象兼容with語句,必須在這個類中聲明__enter__和__exit__方法
1 class File: 2 def __init__(self,x): 3 self.x = x 4 def __enter__(self): 5 print("--->enter") 6 def __exit__(self, exc_type, exc_val, exc_tb): 7 print("--->exit") 8 with File("test.txt") as f: 9 print("對象兼容with語句") 10 運行結果: 11 --->enter
對象兼容with語句
--->exit
從結果能夠看出,文件操做流程中,首先觸發__enter__方法,而後執行代碼塊中的內容,最後觸發__exit__方法來釋放文件內存。
__exit__()中的三個參數分別表明異常類型,異常值和追溯信息,with語句中代碼塊出現異常,則with後的代碼都沒法執行。
__call__:__call__方法的執行是經過對象名或類名後加()來執行的。