一、物件導向的三大特徵
物件導向的三大特徵指的是 封裝、繼承、多型。
封裝(encapsulation,有時稱為資料隱藏)是處理物件的一個重要概念。從形式上看,封裝就是將資料和行為組合在一個包中,並對物件的使用者隱藏具體的實現方式。
繼承(inheritance)的基本思想是,可以基於已有的類建立新類。繼承以存在的類就是複用(繼承)這些類的方法,而且可以增加一些新的屬性和方法,使新類能夠適應新的情況。
多型(polynirphic)就是一個類的多種形態。
二、封裝性
2.1、什麼是封裝性
所謂的封裝,就是把客觀事物封裝成抽象概念的類,並且類可以把自己的資料和方法只向可信的類或物件開放,向沒必要開放的類或物件異常資訊。封裝性就是隱藏內部物件的複雜性,只對外公開簡單的介面。便於外部呼叫,從而提高系統的可擴充套件性、可維護性。通俗的來說,把該隱藏的隱藏起來,該暴露的暴露出來。
當我們建立一個類時,我們可以透過“物件.屬性”的方式,對物件的屬性進行賦值。此時,賦值操作要受到屬性的資料型別和儲存範圍的制約。除此之外,沒有其它制約條件。但是,在實際問題中,我們往往需要給屬性賦值加入額外的限制條件。這個條件就不能在屬性宣告時體現,只能透過方法進行限制條件的新增。同時,我們需要避免使用者在使用“物件.屬性”的方式對屬性進行賦值,則需要將屬性宣告為私有的(private)。此時,針對於屬性就體現了封裝性。
使用封裝後,確實增加了類的定義的複雜程度,但是它也確保了資料的安全性。我們隱藏了屬性名,使呼叫者無法任意修改物件中的屬性。並且我們增加了 getter() 和 setter() 方法,可以很好的控制屬性是否為只讀的。如果希望屬性是隻讀的,則可以直接去掉 setter() 方法,如果希望屬性不能被外部訪問,則可以直接去掉 getter() 方法。而且在使用 setter() 方法設定屬性時,可以增加資料的驗證,確保資料的值是正確的。在使用 getter() 方法獲取屬性,使用 setter() 方法設定屬性時,可以在讀取屬性和修改屬性的同時做一些其它的處理。
2.2、封裝性的體現
我們將類的屬性(xxx)隱藏,同時,提供的公共的方法類獲取(getter())和設定(setter(self))此屬性的值。在 Python 中,我們可以為物件的屬性使用雙下劃線開頭 __xxx
,雙下劃線開頭的屬性,是物件的隱藏屬性,隱藏屬性值只能在類的內部訪問,無法透過物件訪問。
class Person:
def __init__(self,name,age):
self.__name = name
self.__age = age
def get_name(self):
return self.__name
def set_name(self,name):
self.__name = name
def get_age(self):
return self.__age
def set_age(self,age):
if age >= 0:
self.__age = age
def __show_info(self):
print(f"name: {self.__name}, age: {self.__age}")
如果我們直接訪問類中的隱藏屬性,會報以下錯誤:
p1 = Person("Sakura",10)
print(p1.__name)
AttributeError: 'Person' object has no attribute '__name'
此時,我們可以透過公共的 getter() 方法和 setter() 方法呼叫類的私有屬性:
p1 = Person("Sakura",10)
print("name: ", p1.get_name() , ", age: ", p1.get_age())
p1.set_age(12)
print("name: ", p1.get_name() , ", age: ", p1.get_age())
p1.set_age(-10)
print("name: ", p1.get_name() , ", age: ", p1.get_age())
其實,隱藏屬性是假隱藏,只不過是 Python 自動為屬性改了一個名字。實際上,Python 將名字修改為 _型別__屬性名
。
p1 = Person("Sakura",10)
p1._Person__age = 12
print(p1._Person__age)
p1._Person__show_info()
使用雙下劃線開頭的屬性,實際上依然可以在外部訪問。在開發中,我們可以將一些私有屬性以下劃線開頭(實際上是公開的屬性),告訴開發人員沒有特殊需要不要修改私有屬性。
2.3、property裝飾器
property 裝飾器,用來將一個 getter() 方法,轉換為物件的屬性,新增了 property 裝飾器以後,我們就可以向呼叫屬性一樣使用 getter() 方法。使用 property 裝飾的方法必須和屬性名是一樣的。setter() 方法的裝飾器為:@屬性名.setter;
class Person:
def __init__(self,name):
self.__name = name
@property
def name(self):
print("get_name(self)方法執行了")
return self.__name
@name.setter
def name(self,name):
print("set_name(self,name)方法執行了")
self.__name = name
@name.deleter
def name(self):
print("del_name(self,name)方法執行了")
p = Person("Sakura")
print(p.name,end="\n\n")
p.name = "Mikoto"
print(p.name,end="\n\n")
del p.name
在一些較老版本的 Python 直譯器中,我們可以透過如下的方法實現。
class Person:
def __init__(self,name):
self.__name = name
def get_name(self):
print("get_name(self)方法執行了")
return self.__name
def set_name(self,name):
print("set_name(self,name)方法執行了")
self.__name = name
def del_name(self):
print("del_name(self,name)方法執行了")
name = property(get_name, set_name, del_name)
p = Person("Sakura")
print(p.name,end="\n\n")
p.name = "Mikoto"
print(p.name,end="\n\n")
del p.name
三、繼承性
3.1、什麼是繼承性
繼承,其基本思想就是基於某個父類的擴充套件,制定出一個新的子類,子類可以繼承父類原有的屬性和方法,也可以增加原來父類所不具備的屬性和方法,或者直接重寫父類中的某些方法。
透過繼承可以直接讓子類獲取父類的方法和屬性,避免編寫重複性的程式碼,並且也符合 OCP 原則。所以,我們經常需要透過繼承來對一個類進行擴充套件。
3.2、繼承性的應用
使用繼承後,我們可以減少程式碼的冗餘,提高了程式碼的複用性,並且使用繼承後,便於功能的擴充。繼承的出現讓類與類之間產生了 is-a
的關係。
在 Python 中,繼承性的格式如下:
class A(父類):
pass
其中,A 類代表 子類(派生類、subclass),B 類代表 父類(基類、superclass)。一旦 子類 A 繼承 父類 B 之後,子類 A 中就獲取 父類 B 中宣告的所有的結構:屬性、方法(包括特殊方法);子類繼承父類以後,還可以宣告自己特有的屬性和方法,實現功能的擴充;
class Animal:
def run(self):
print("動物在跑")
def sleep(self):
print("動物在睡覺")
class Dog(Animal):
def bark(self):
print("汪汪汪")
dog = Dog()
dog.run()
dog.sleep()
dog.bark()
print()
# isinstance()檢查一個物件是否是一個類的例項
# 如果這個類是這個物件的父類,也會返回true
print(isinstance(dog,Dog))
print(isinstance(dog,Animal))
print()
# issubclass()檢查一個類是或否是另一個類的子類
print(issubclass(Dog,Animal))
如果在建立類時,省略了父類,則預設父類為 object,它是所有類的父類,所有類都繼承 object;
3.3、方法重寫
如果在子類中有和父類同名的方法,則透過子類例項呼叫方法時,會呼叫子類的方法而不是父類的方法,這個特點我們成為 方法重寫(覆蓋,override)。
當我們呼叫一個物件的方法時,它會優先去當前物件中尋找是否具有該方法,如果有則直接呼叫,如果沒有則取當前物件的父類中尋找,如果父類中有則直接呼叫父類中的方法,如果沒有則取父類中的父類中尋找,以此類推,直到找到 object,如果依然沒有找到則報錯。
class Animal:
def run(self):
print("動物在跑")
def sleep(self):
print("動物在睡覺")
class Dog(Animal):
def run(self):
print("狗在跑")
def bark(self):
print("汪汪汪")
dog = Dog()
dog.run()
dog.sleep()
dog.bark()
3.4、super()方法的使用
父類中所有結構:屬性 和 方法(包括特殊方法)都會被子類繼承。如果我們希望直接呼叫父類中的結構,我們可以使用 super() 方法獲取當前類的父類,並且透過 super() 呼叫父類方法時,不需要傳遞 self。
class Animal:
def __init__(self,name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
self.__name = name
class Dog(Animal):
def __init__(self,name,age):
# 直接呼叫父類中的__init__(self)來初始化父類中定義的屬性
super().__init__(name)
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self,age):
self.__age = age
dog = Dog("旺財",3)
print("name: ", dog.name, ", age: ", dog.age)
3.5、多重繼承
在 Python 中是支援多重繼承的,也就是我們可以為一個類同時指定多個父類。我們可以在類名的 () 後面新增多個類,來實現多重繼承。多重繼承會使子類同時擁有多個父類,並且或獲取所有父類中的方法。如果多個父類中有同名的方法,則會先在第一個父類中尋找,然後找第二個,然後找第三個,...,前面父類的方法會覆蓋後面父類的方法。
class A:
def test(self):
print("AAA")
class B:
def test(self):
print("BBB")
class C(A,B):
pass
# 類名.__bases__這個屬性可以獲取當前類的所有父類
print(C.__bases__)
# 我們可以透過類.mro()檢視屬性的查詢順序
print(C.mro())
c = C()
c.test()
super() 在呼叫父類的時候,它需要計算出當前到底呼叫哪個父類,在 Python 中實現這個功能的演算法叫 C3 演算法;
在 Python 中子類可以同時繼承多個父類的屬性,這樣可以最大限度地重用程式碼,但是使用多繼承會導致擴充套件性變差,並且有可能會導致稜形問題(鑽石問題)。稜形問題(鑽石問題)指的是一個子類繼承的多個父類彙集到一個非 object 類的身上。
class A:
def test(self):
print("from A")
class B(A):
def test(self):
print("from B")
class C(A):
def test(self):
print("from C")
class D(B):
def test(self):
print("from D")
class E(C):
def test(self):
print("from E")
class F(A):
def test(self):
print("from F")
class G(D,E,F):
pass
# 類名.__bases__這個屬性可以獲取當前類的所有父類
print(G.__bases__)
# 我們可以透過類.mro()檢視屬性的查詢順序
print(G.mro())
obj = G()
obj.test()
如果真的涉及到一個子類不可避免使用多個父類的屬性,應該使用 Mixins 機制。Mixin 類表示某一功能,而不是某個物品,Python 對於Mixin 類的命名方式一般以 Mixin,able,ible 為字尾。我們可以將相關的功能放在一個 Mixin 類中,如果有多個不同功能,那可以寫多個 Mixin 類,一個類可以繼承多個 Mixin 類,但應該只繼承一個表示其歸屬含義的父類,以確保遵循繼承的 "is-a" 原則。Mixin 類不應該依賴於子類的實現,子類即便沒有繼承這個 Mixin 類,也可以照常工作,只是缺少了某個功能而已。
class Vehicle:
pass
class FlyerMixin:
def fly(self):
pass
class CivilAircraft(FlyerMixin,Vehicle):
pass
class Helicopter(FlyerMixin,Vehicle):
pass
class Car(Vehicle):
pass
四、多型性
多型性指的是一個事物的多種形態;同樣的行為(函式),傳入不同的物件,得到不同的狀態;多型性指的是可以在不考慮物件具體型別的情況下而直接使用物件。
它的格式如下:
class A:
def __init__(self,name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
self.__name = name
class B:
def __init__(self,name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
self.__name = name
a = A("Sakura")
b = B("Mikoto")
# 對於say_hello()這個函式來說,只要物件中含有name屬性,它就可以作為引數傳遞
# 這個函式並不會考慮物件的型別,只要有name屬性即可
def say_hello(obj):
print("你好,%s" %obj.name)
say_hello(a)
say_hello(b)
import abc
# 使用模組abc統一所有子類的標準
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod
def speak(self):
pass
class Dog(Animal):
def speak(self):
print("汪汪汪!")
class Cat(Animal):
def speak(self):
print("喵喵喵!")
def make_noise(animal):
animal.speak()
#animal = Animal() # 不能例項化抽象類自己
dog = Dog()
cat = Cat()
make_noise(dog)
make_noise(cat)
看上去呼叫相同的方法,但實際上需要這個看這個物件是父類還是子類建立的物件,如果是父類建立的物件,一定呼叫父類中定義的方法,如果是子類建立的物件,那麼就要看子類是否重寫了父類的方法,如果子類要是重寫了父類的方法,那麼會呼叫子類的方法,如果子類沒有重寫父類方法,那麼會呼叫父類的方法。