面向對象的三大特性 - 繼承、多態、封裝


 

 

 


Top

一、面向對象的三大特性---繼承

1.繼承

繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為基類或超類,新建的類稱為派生類或子類

python中類的繼承分為:單繼承和多繼承

復制代碼
 1 class ParentClass1: #定義父類
 2     pass
 3 
 4 class ParentClass2: #定義父類
 5     pass
 6 
 7 class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
 8     pass
 9 
10 class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類
11 pass
復制代碼

 

查看繼承

1 >>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個子類,__bases__則是查看所有繼承的父類
2 (<class '__main__.ParentClass1'>,)
3 >>> SubClass2.__bases__
4 (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

提示:如果沒有指定基類,python的類會默認繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現。

1 >>> ParentClass1.__bases__
2 (<class 'object'>,)
3 >>> ParentClass2.__bases__
4 (<class 'object'>,)

 

2.繼承與抽象(先抽象再繼承)

抽象即抽取類似或者說比較像的部分。

抽象分成兩個層次: 

1.將奧巴馬和梅西這倆對象比較像的部分抽取成類; 

2.將人,豬,狗這三個類比較像的部分抽取成父類。

抽象最主要的作用是划分類別(可以隔離關注點,降低復雜度)

 

繼承:是基於抽象的結果,通過編程語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。

抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類

 

3.繼承與重用性

使用繼承來解決代碼重用的例子

 

==========================第一部分
例如
 
  貓可以:喵喵叫、吃、喝、拉、撒
 
  狗可以:汪汪叫、吃、喝、拉、撒
 
如果我們要分別為貓和狗創建一個類,那么就需要為 貓 和 狗 實現他們所有的功能,偽代碼如下:
  View Code

 

==========================第二部分
上述代碼不難看出,吃、喝、拉、撒是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次。如果使用 繼承 的思想,如下實現:
 
  動物:吃、喝、拉、撒
 
     貓:喵喵叫(貓繼承動物的功能)
 
     狗:汪汪叫(狗繼承動物的功能)
 
偽代碼如下:
復制代碼
 1 class 動物:
 2 
 3     def 吃(self):
 4         # do something
 5 
 6     def 喝(self):
 7         # do something
 8 
 9     def 拉(self):
10         # do something
11 
12     def 撒(self):
13         # do something
14 
15 # 在類后面括號中寫入另外一個類名,表示當前類繼承另外一個類
16 class 貓(動物):
17 
18     def 喵喵叫(self):
19         print '喵喵叫'
20         
21 # 在類后面括號中寫入另外一個類名,表示當前類繼承另外一個類
22 class 狗(動物):
23 
24     def 汪汪叫(self):
25         print '汪汪叫'
復制代碼

 

==========================第三部分
#繼承的代碼實現
復制代碼
 1 class Animal:
 2 
 3     def eat(self):
 4         print("%s 吃 " %self.name)
 5 
 6     def drink(self):
 7         print ("%s 喝 " %self.name)
 8 
 9     def shit(self):
10         print ("%s 拉 " %self.name)
11 
12     def pee(self):
13         print ("%s 撒 " %self.name)
14 
15 
16 class Cat(Animal):
17 
18     def __init__(self, name):
19         self.name = name
20         self.breed = '貓'
21 
22     def cry(self):
23         print('喵喵叫')
24 
25 class Dog(Animal):
26 
27     def __init__(self, name):
28         self.name = name
29         self.breed='狗'
30 
31     def cry(self):
32         print('汪汪叫')
復制代碼

 

執行結果

復制代碼
1 c1 = Cat('小白家的小黑貓')
2 c1.eat()
3 
4 c2 = Cat('小黑的小白貓')
5 c2.drink()
6 
7 d1 = Dog('胖子家的小瘦狗')
8 d1.eat()
復制代碼

 

在開發程序的過程中,如果我們定義了一個類A,然后又想新建立另外一個類B,但是類B的大部分內容與類A的相同時

我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。

通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數據屬性和函數屬性),實現代碼重用

復制代碼
 1 class Animal:
 2     '''
 3     人和狗都是動物,所以創造一個Animal基類
 4     '''
 5     def __init__(self, name, aggressivity, life_value):
 6         self.name = name  # 人和狗都有自己的昵稱;
 7         self.aggressivity = aggressivity  # 人和狗都有自己的攻擊力;
 8         self.life_value = life_value  # 人和狗都有自己的生命值;
 9 
10     def eat(self):
11         print('%s is eating'%self.name)
12 
13 class Dog(Animal):
14     pass
15 
16 class Person(Animal):
17     pass
18 
19 egg = Person('egon',10,1000)
20 ha2 = Dog('二愣子',50,1000)
21 egg.eat()
22 ha2.eat()
復制代碼

 

提示:用已經有的類建立一個新的類,這樣就重用了已經有的軟件中的一部分設置大部分,大大生了編程工作量,這就是常說的軟件重用,不僅可以重用自己的類,也可以繼承別人的,比如標准庫,來定制新的數據類型,這樣就是大大縮短了軟件開發周期,對大型軟件開發來說,意義重大.

4.派生

當然子類也可以添加自己新的屬性或者在自己這里重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那么調用新增的屬性時,就以自己為准了。

復制代碼
 1 class Animal:
 2     '''
 3     人和狗都是動物,所以創造一個Animal基類
 4     '''
 5     def __init__(self, name, aggressivity, life_value):
 6         self.name = name  # 人和狗都有自己的昵稱;
 7         self.aggressivity = aggressivity  # 人和狗都有自己的攻擊力;
 8         self.life_value = life_value  # 人和狗都有自己的生命值;
 9 
10     def eat(self):
11         print('%s is eating'%self.name)
12 
13 class Dog(Animal):
14     '''
15     狗類,繼承Animal類
16     '''
17     def bite(self, people):
18         '''
19         派生:狗有咬人的技能
20         :param people:  
21         '''
22         people.life_value -= self.aggressivity
23 
24 class Person(Animal):
25     '''
26     人類,繼承Animal
27     '''
28     def attack(self, dog):
29         '''
30         派生:人有攻擊的技能
31         :param dog: 
32         '''
33         dog.life_value -= self.aggressivity
34 
35 egg = Person('egon',10,1000)
36 ha2 = Dog('二愣子',50,1000)
37 print(ha2.life_value)
38 print(egg.attack(ha2))
39 print(ha2.life_value)
復制代碼

注意:像ha2.life_value之類的屬性引用,會先從實例中找life_value然后去類中找,然后再去父類中找...直到最頂級的父類。

 

5.super

在子類中,新建的重名的函數屬性,在編輯函數內功能的時候,有可能需要重用父類中重名的那個函數功能,應該是用調用普通函數的方式,即:類名.func(),此時就與調用普通函數無異了,因此即便是self參數也要為其傳值.

在python3中,子類執行父類的方法也可以直接用super方法.:

復制代碼
 1 class Animal:
 2     '''
 3     人和狗都是動物,所以創造一個Animal基類
 4     '''
 5     def __init__(self, name, aggressivity, life_value):
 6         self.name = name  # 人和狗都有自己的昵稱;
 7         self.aggressivity = aggressivity  # 人和狗都有自己的攻擊力;
 8         self.life_value = life_value  # 人和狗都有自己的生命值;
 9 
10     def eat(self):
11         print('%s is eating'%self.name)
12 
13 class Dog(Animal):
14     '''
15     狗類,繼承Animal類
16     '''
17     def __init__(self,name,breed,aggressivity,life_value):
18         super().__init__(name, aggressivity, life_value) #執行父類Animal的init方法
19         self.breed = breed  #派生出了新的屬性
20 
21     def bite(self, people):
22         '''
23         派生出了新的技能:狗有咬人的技能
24         :param people:  
25         '''
26         people.life_value -= self.aggressivity
27 
28     def eat(self):
29         # Animal.eat(self)
30         #super().eat()
31         print('from Dog')
32 
33 class Person(Animal):
34     '''
35     人類,繼承Animal
36     '''
37     def __init__(self,name,aggressivity, life_value,money):
38         #Animal.__init__(self, name, aggressivity, life_value)
39         #super(Person, self).__init__(name, aggressivity, life_value)
40         super().__init__(name,aggressivity, life_value)  #執行父類的init方法
41         self.money = money   #派生出了新的屬性
42 
43     def attack(self, dog):
44         '''
45         派生出了新的技能:人有攻擊的技能
46         :param dog: 
47         '''
48         dog.life_value -= self.aggressivity
49 
50     def eat(self):
51         #super().eat()
52         Animal.eat(self)
53         print('from Person')
54 
55 egg = Person('egon',10,1000,600)
56 ha2 = Dog('二愣子','哈士奇',10,1000)
57 print(egg.name)
58 print(ha2.name)
59 egg.eat()
復制代碼

通過繼承建立了派生類與基類之間的關系,它是一種''的關系,比如白馬是馬,人是動物。

當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如教授是老師

復制代碼
 1 >>> class Teacher:
 2 ...     def __init__(self,name,gender):
 3 ...         self.name=name
 4 ...         self.gender=gender
 5 ...     def teach(self):
 6 ...         print('teaching')
 7 ... 
 8 >>> 
 9 >>> class Professor(Teacher):
10 ...     pass
11 ... 
12 >>> p1=Professor('egon','male')
13 >>> p1.teach()
14 teaching
復制代碼

 

6.接口類

繼承有兩種用途:

一:繼承基類的方法,並且做出自己的改變或者擴展(代碼重用)  

二:聲明某個子類兼容於某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,並且實現接口中的功能

復制代碼
 1 class Alipay:
 2     '''
 3     支付寶支付
 4     '''
 5     def pay(self,money):
 6         print('支付寶支付了%s元'%money)
 7 
 8 class Applepay:
 9     '''
10     apple pay支付
11     '''
12     def pay(self,money):
13         print('apple pay支付了%s元'%money)
14 
15 
16 def pay(payment,money):
17     '''
18     支付函數,總體負責支付
19     對應支付的對象和要支付的金額
20     '''
21     payment.pay(money)
22 
23 
24 p = Alipay()
25 pay(p,200)
復制代碼

 

開發中容易出現的問題

復制代碼
 1 class Alipay:
 2     '''
 3     支付寶支付
 4     '''
 5     def pay(self,money):
 6         print('支付寶支付了%s元'%money)
 7 
 8 class Applepay:
 9     '''
10     apple pay支付
11     '''
12     def pay(self,money):
13         print('apple pay支付了%s元'%money)
14 
15 class Wechatpay:
16     def fuqian(self,money):
17         '''
18         實現了pay的功能,但是名字不一樣
19         '''
20         print('微信支付了%s元'%money)
21 
22 def pay(payment,money):
23     '''
24     支付函數,總體負責支付
25     對應支付的對象和要支付的金額
26     '''
27     payment.pay(money)
28 
29 
30 p = Wechatpay()
31 pay(p,200)   #執行會報錯
復制代碼

 

接口初成:手動報異常:NotImplementedError來解決開發中遇到的問題

復制代碼
 1 class Payment:
 2     def pay(self):
 3         raise NotImplementedError
 4 
 5 class Wechatpay(Payment):
 6     def fuqian(self,money):
 7         print('微信支付了%s元'%money)
 8 
 9 
10 p = Wechatpay()  #這里不報錯
11 pay(p,200)      #這里報錯了
復制代碼

 

借用abc模塊來實現接口

復制代碼
 1 from abc import ABCMeta,abstractmethod
 2 
 3 class Payment(metaclass=ABCMeta):
 4     @abstractmethod
 5     def pay(self,money):
 6         pass
 7 
 8 
 9 class Wechatpay(Payment):
10     def fuqian(self,money):
11         print('微信支付了%s元'%money)
12 
13 p = Wechatpay() #不調就報錯了
復制代碼

實踐中,繼承的第一種含義意義並不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。

繼承的第二種含義非常重要。它又叫“接口繼承”。
接口繼承實質上是要求“做出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的所有對象”——這在程序設計上,叫做歸一化。

歸一化使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合——就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存、磁盤、網絡還是屏幕(當然,對底層設計者,當然也可以區分出“字符設備”和“塊設備”,然后做出針對性的設計:細致到什么程度,視需求而定)。

 

依賴倒置原則:
高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該應該依賴細節;細節應該依賴抽象。換言之,要針對接口編程,而不是針對實現編程

在python中根本就沒有一個叫做interface的關鍵字,上面的代碼只是看起來像接口,其實並沒有起到接口的作用,子類完全可以不用去實現接口 ,如果非要去模仿接口的概念,可以借助第三方模塊:

為何要用接口

接口提取了一群類共同的函數,可以把接口當做一個函數的集合。
 
然后讓子類去實現接口中的函數。
 
這么做的意義在於歸一化,什么叫歸一化,就是只要是基於同一個接口實現的類,那么所有的這些類產生的對象在使用時,從用法上來說都一樣。
 
歸一化,讓使用者無需關心對象的類是什么,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
 
比如:我們定義一個動物接口,接口里定義了有跑、吃、呼吸等接口函數,這樣老鼠的類去實現了該接口,松鼠的類也去實現了該接口,由二者分別產生一只老鼠和一只松鼠送到你面前,即便是你分別不到底哪只是什么鼠你肯定知道他倆都會跑,都會吃,都能呼吸。
 
再比如:我們有一個汽車接口,里面定義了汽車所有的功能,然后由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現了汽車接口,這樣就好辦了,大家只需要學會了怎么開汽車,那么無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函數調用)都一樣

7.抽象類

什么是抽象類

    java一樣,python也有抽象類的概念但是同樣需要借助模塊實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化

為什么要有抽象類

    如果說類是從一堆對象中抽取相同的內容而來的,那么抽象類是從一堆中抽取相同的內容而來的,內容包括數據屬性和函數屬性。

 比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要么是吃一個具體的香蕉,要么是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。

    從設計角度去看,如果類是從現實對象抽象而來的,那么抽象類就是基於類抽象而來的。

 從實現角度來看,抽象類與普通類的不同之處在於:抽象類中有抽象方法,該類不能被實例化,只能被繼承,且子類必須實現抽象方法。這一點與接口有點類似,但其實是不同的,即將揭曉答案

在python中實現抽象類

復制代碼
 1 #一切皆文件
 2 import abc #利用abc模塊實現抽象類
 3 
 4 class All_file(metaclass=abc.ABCMeta):
 5     all_type='file'
 6     @abc.abstractmethod #定義抽象方法,無需實現功能
 7     def read(self):
 8         '子類必須定義讀功能'
 9         pass
10 
11     @abc.abstractmethod #定義抽象方法,無需實現功能
12     def write(self):
13         '子類必須定義寫功能'
14         pass
15 
16 # class Txt(All_file):
17 #     pass
18 #
19 # t1=Txt() #報錯,子類沒有定義抽象方法
20 
21 class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法
22     def read(self):
23         print('文本數據的讀取方法')
24 
25     def write(self):
26         print('文本數據的讀取方法')
27 
28 class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法
29     def read(self):
30         print('硬盤數據的讀取方法')
31 
32     def write(self):
33         print('硬盤數據的讀取方法')
34 
35 class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法
36     def read(self):
37         print('進程數據的讀取方法')
38 
39     def write(self):
40         print('進程數據的讀取方法')
41 
42 wenbenwenjian=Txt()
43 
44 yingpanwenjian=Sata()
45 
46 jinchengwenjian=Process()
47 
48 #這樣大家都是被歸一化了,也就是一切皆文件的思想
49 wenbenwenjian.read()
50 yingpanwenjian.write()
51 jinchengwenjian.read()
52 
53 print(wenbenwenjian.all_type)
54 print(yingpanwenjian.all_type)
55 print(jinchengwenjian.all_type)
復制代碼

 

抽象類的本質還是類,指的是一組類的相似性,包括數據屬性(如all_type)和函數屬性(如readwrite),而接口只強調函數屬性的相似性。

抽象類是一個介於類和接口直接的一個概念,同時具備類和接口的部分特性,可以用來實現歸一化設計 

在python中,並沒有接口類這種東西,即便不通過專門的模塊定義接口,我們也應該有一些基本的概念。

 

多繼承問題

在繼承抽象類的過程中,我們應該盡量避免多繼承;
而在繼承接口的時候,我們反而鼓勵你來多繼承接口

接口隔離原則:
使用多個專門的接口,而不使用單一的總接口。即客戶端不應該依賴那些不需要的接口。

方法的實現

在抽象類中,我們可以對一些抽象方法做出基礎實現;
而在接口類中,任何方法都只是一種規范,具體的功能需要子類實現

8.鑽石繼承

繼承順序

 繼承順序代碼

復制代碼
 1 class A(object):
 2     def test(self):
 3         print('from A')
 4 
 5 class B(A):
 6     def test(self):
 7         print('from B')
 8 
 9 class C(A):
10     def test(self):
11         print('from C')
12 
13 class D(B):
14     def test(self):
15         print('from D')
16 
17 class E(C):
18     def test(self):
19         print('from E')
20 
21 class F(D,E):
22     # def test(self):
23     #     print('from F')
24     pass
25 f1=F()
26 f1.test()
27 print(F.__mro__) #只有新式才有這個屬性可以查看線性列表,經典類沒有這個屬性
復制代碼

 

#新式類繼承順序:F->D->B->E->C->A
#經典類繼承順序:F->D->B->A->E->C
#python3中統一都是新式類
#pyhon2中才分新式類與經典類

繼承原理

python到底是如何實現繼承的,對於你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如

>>> F.mro() #等同於F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

為了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。
而這個MRO列表的構造是通過一個C3線性化算法來實現的。我們不去深究這個算法的數學原理,它實際上就是合並所有父類的MRO列表並遵循如下三條准則:
1.子類會先於父類被檢查
2.多個父類會根據它們在列表中的順序被檢查
3.如果對下一個類存在兩個合法的選擇,選擇第一個父類

繼承的作用

減少代碼的重用
提高代碼可讀性
規范編程模式

新式類:廣度優先
經典類:深度優先

幾個名詞

抽象:抽象即抽取類似或者說比較像的部分。是一個從具題到抽象的過程。
繼承:子類繼承了父類的方法和屬性
派生:子類在父類方法和屬性的基礎上產生了新的方法和屬性
Top

二、面向對象的三大特性---多態

多態指的是一類事物有多種形態

動物有多種形態:人,狗,豬

復制代碼
 1 import abc
 2 class Animal(metaclass=abc.ABCMeta): #同一類事物:動物
 3     @abc.abstractmethod
 4     def talk(self):
 5         pass
 6 
 7 class People(Animal): #動物的形態之一:人
 8     def talk(self):
 9         print('say hello')
10 
11 class Dog(Animal): #動物的形態之二:狗
12     def talk(self):
13         print('say wangwang')
14 
15 class Pig(Animal): #動物的形態之三:豬
16     def talk(self):
17         print('say aoao')
復制代碼

文件有多種形態:文本文件,可執行文件

復制代碼
 1 import abc
 2 class File(metaclass=abc.ABCMeta): #同一類事物:文件
 3     @abc.abstractmethod
 4     def click(self):
 5         pass
 6 
 7 class Text(File): #文件的形態之一:文本文件
 8     def click(self):
 9         print('open file')
10 
11 class ExeFile(File): #文件的形態之二:可執行文件
12     def click(self):
13         print('execute file')
復制代碼

1.多態性

什么是多態動態綁定(在繼承的背景下使用時,有時也稱為多態性)

在面向對象方法中一般是這樣表述多態性:
向不同的對象發送同一條消息(!!!obj.func():是調用了obj的方法func,又稱為向obj發送了一條消息func),不同的對象在接收時會產生不同的行為(即方法)。
也就是說,每個對象可以用自己的方式去響應共同的消息。所謂消息,就是調用函數,不同的行為就是指不同的實現,即執行不同的函數。
 
比如:老師.下課鈴響了(),學生.下課鈴響了(),老師執行的是下班操作,學生執行的是放學操作,雖然二者消息一樣,但是執行的效果不同

代碼實現

復制代碼
 1 peo=People()
 2 dog=Dog()
 3 pig=Pig()
 4 
 5 #peo、dog、pig都是動物,只要是動物肯定有talk方法
 6 #於是我們可以不用考慮它們三者的具體是什么類型,而直接使用
 7 peo.talk()
 8 dog.talk()
 9 pig.talk()
10 
11 #更進一步,我們可以定義一個統一的接口來使用
12 def func(obj):
13     obj.talk()
復制代碼

 

2. 鴨子類型

逗比時刻:

  Python崇尚鴨子類型,即‘如果看起來像、叫聲像而且走起路來像鴨子,那么它就是鴨子’

python程序員通常根據這種行為來編寫程序。例如,如果想編寫現有對象的自定義版本,可以繼承該對象

也可以創建一個外觀和行為像,但與它無任何關系的全新對象,后者通常用於保存程序組件的松耦合度。

例1:利用標准庫中定義的各種‘與文件類似’的對象,盡管這些對象的工作方式像文件,但他們沒有繼承內置文件對象的方法

例2:序列類型有多種形態:字符串,列表,元組,但他們直接沒有直接的繼承關系

#二者都像鴨子,二者看起來都像文件,因而就可以當文件一樣去用
復制代碼
 1 class TxtFile:
 2     def read(self):
 3         pass
 4 
 5     def write(self):
 6         pass
 7 
 8 class DiskFile:
 9     def read(self):
10         pass
11     def write(self):
12         pass
復制代碼

 

Top

三、面向對象的三大特性---封裝

隱藏對象的屬性和實現細節,僅對外提供公共訪問方式。

在python中用雙下划線開頭的方式將屬性隱藏起來(設置成私有的)

1.原則和好處

好處

1. 將變化隔離; 

2. 便於使用;

3. 提高復用性; 

4. 提高安全性;

 

原則

     1. 將不需要對外提供的內容都隱藏起來;

      2. 把屬性都隱藏,提供公共方法對其訪問。

2.私有變量

復制代碼
 1 #其實這僅僅這是一種變形操作
 2 #類中所有雙下划線開頭的名稱如__x都會自動變形成:_類名__x的形式:
 3 
 4 class A:
 5     __N=0 #類的數據屬性就應該是共享的,但是語法上是可以把類的數據屬性設置成私有的如__N,會變形為_A__N
 6     def __init__(self):
 7         self.__X=10 #變形為self._A__X
 8     def __foo(self): #變形為_A__foo
 9         print('from A')
10     def bar(self):
11         self.__foo() #只有在類內部才可以通過__foo的形式訪問到.
12 
13 #A._A__N是可以訪問到的,即這種操作並不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形
復制代碼

這種自動變形的特點

1.類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果。

2.這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。

3.在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。

 

這種變形需要注意的問題是:

1.這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然后就可以訪問了,如a._A__N

2.變形的過程只在類的內部生效,在定義后的賦值操作,不會變形

私有方法

3.在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的

復制代碼
 1 #正常情況
 2 >>> class A:
 3 ...     def fa(self):
 4 ...         print('from A')
 5 ...     def test(self):
 6 ...         self.fa()
 7 ... 
 8 >>> class B(A):
 9 ...     def fa(self):
10 ...         print('from B')
11 ... 
12 >>> b=B()
13 >>> b.test()
14 from B
15  
16 
17 #把fa定義成私有的,即__fa
18 >>> class A:
19 ...     def __fa(self): #在定義時就變形為_A__fa
20 ...         print('from A')
21 ...     def test(self):
22 ...         self.__fa() #只會與自己所在的類為准,即調用_A__fa
23 ... 
24 >>> class B(A):
25 ...     def __fa(self):
26 ...         print('from B')
27 ... 
28 >>> b=B()
29 >>> b.test()
30 from A
復制代碼

 

3.封裝與擴展性

封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足為慮。

復制代碼
 1 #類的設計者
 2 class Room:
 3     def __init__(self,name,owner,width,length,high):
 4         self.name=name
 5         self.owner=owner
 6         self.__width=width
 7         self.__length=length
 8         self.__high=high
 9     def tell_area(self): #對外提供的接口,隱藏了內部的實現細節,此時我們想求的是面積
10         return self.__width * self.__length
11 
12 
13 #使用者
14 >>> r1=Room('卧室','egon',20,20,20)
15 >>> r1.tell_area() #使用者調用接口tell_area
復制代碼
復制代碼
 1 #類的設計者,輕松的擴展了功能,而類的使用者完全不需要改變自己的代碼
 2 class Room:
 3     def __init__(self,name,owner,width,length,high):
 4         self.name=name
 5         self.owner=owner
 6         self.__width=width
 7         self.__length=length
 8         self.__high=high
 9     def tell_area(self): #對外提供的接口,隱藏內部實現,此時我們想求的是體積,內部邏輯變了,只需求修該下列一行就可以很簡答的實現,而且外部調用感知不到,仍然使用該方法,但是功能已經變了
10         return self.__width * self.__length * self.__high
11 
12 
13 #對於仍然在使用tell_area接口的人來說,根本無需改動自己的代碼,就可以用上新功能
14 >>> r1.tell_area()
復制代碼

 

4.property屬性

property是一種特殊的屬性,訪問它時會執行一段功能(函數)然后返回值

例一:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便於理解)

成人的BMI數值:
過輕:低於18.5
正常:18.5-23.9
過重:24-27
肥胖:28-32
非常肥胖, 高於32
  體質指數(BMI)=體重(kg)÷身高^2(m)
  EX:70kg÷(1.75×1.75)=22.86
復制代碼
 1 class People:
 2     def __init__(self,name,weight,height):
 3         self.name=name
 4         self.weight=weight
 5         self.height=height
 6     @property
 7     def bmi(self):
 8         return self.weight / (self.height**2)
 9 
10 p1=People('egon',75,1.85)
11 print(p1.bmi)
復制代碼

 

例二:圓的周長和面積

復制代碼
 1 import math
 2 class Circle:
 3     def __init__(self,radius): #圓的半徑radius
 4         self.radius=radius
 5 
 6     @property
 7     def area(self):
 8         return math.pi * self.radius**2 #計算面積
 9 
10     @property
11     def perimeter(self):
12         return 2*math.pi*self.radius #計算周長
13 
14 c=Circle(10)
15 print(c.radius)
16 print(c.area) #可以向訪問數據屬性一樣去訪問area,會觸發一個函數的執行,動態計算出一個值
17 print(c.perimeter) #同上
18 '''
19 輸出結果:
20 314.1592653589793
21 62.83185307179586
22 '''
23 
24 #注意:此時的特性area和perimeter不能被賦值
25 c.area=3 #為特性area賦值
26 '''
27 拋出異常:
28 AttributeError: can't set attribute
復制代碼

為什么要用property

將一個類的函數定義成特性以后,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然后計算出來的,這種特性的使用方式遵循了統一訪問的原則

除此之外,看下

 

ps:面向對象的封裝有三種方式:
【public】
這種其實就是不封裝,是對外公開的
【protected】
這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什么大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開
【private】
這種封裝對誰都不公開
 
python並沒有在語法上把它們三個內建到自己的class機制中,在C++里一般會將所有的所有的數據都設置為私有的,然后提供set和get方法(接口)去設置和獲取,在python中通過property方法可以實現
復制代碼
 1 class Foo:
 2     def __init__(self,val):
 3         self.__NAME=val #將所有的數據屬性都隱藏起來
 4 
 5     @property
 6     def name(self):
 7         return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)
 8 
 9     @name.setter
10     def name(self,value):
11         if not isinstance(value,str):  #在設定值之前進行類型檢查
12             raise TypeError('%s must be str' %value)
13         self.__NAME=value #通過類型檢查后,將值value存放到真實的位置self.__NAME
14 
15     @name.deleter
16     def name(self):
17         raise TypeError('Can not delete')
18 
19 f=Foo('egon')
20 print(f.name)
21 # f.name=10 #拋出異常'TypeError: 10 must be str'
22 del f.name #拋出異常'TypeError: Can not delete'
復制代碼

一個靜態屬性property本質就是實現了get,set,delete三種方法

復制代碼
 1 class Foo:
 2     @property
 3     def AAA(self):
 4         print('get的時候運行我啊')
 5 
 6     @AAA.setter
 7     def AAA(self,value):
 8         print('set的時候運行我啊')
 9 
10     @AAA.deleter
11     def AAA(self):
12         print('delete的時候運行我啊')
13 
14 #只有在屬性AAA定義property后才能定義AAA.setter,AAA.deleter
15 f1=Foo()
16 f1.AAA
17 f1.AAA='aaa'
18 del f1.AAA
復制代碼
復制代碼
 1 class Foo:
 2     def get_AAA(self):
 3         print('get的時候運行我啊')
 4 
 5     def set_AAA(self,value):
 6         print('set的時候運行我啊')
 7 
 8     def delete_AAA(self):
 9         print('delete的時候運行我啊')
10     AAA=property(get_AAA,set_AAA,delete_AAA) #內置property三個參數與get,set,delete一一對應
11 
12 f1=Foo()
13 f1.AAA
14 f1.AAA='aaa'
15 del f1.AAA
16 
17 怎么用?
18 class Goods:
19 
20     def __init__(self):
21         # 原價
22         self.original_price = 100
23         # 折扣
24         self.discount = 0.8
25 
26     @property
27     def price(self):
28         # 實際價格 = 原價 * 折扣
29         new_price = self.original_price * self.discount
30         return new_price
31 
32     @price.setter
33     def price(self, value):
34         self.original_price = value
35 
36     @price.deleter
37     def price(self):
38         del self.original_price
39 
40 
41 obj = Goods()
42 obj.price         # 獲取商品價格
43 obj.price = 200   # 修改商品原價
44 print(obj.price)
45 del obj.price     # 刪除商品原價
復制代碼

 

5.Classmethod

復制代碼
1 class Classmethod_Demo():
2     role = 'dog'
3 
4     @classmethod
5     def func(cls):
6         print(cls.role)
7 
8 Classmethod_Demo.func()
復制代碼

 

6.Staticmethod

復制代碼
1 class Staticmethod_Demo():
2     role = 'dog'
3 
4     @staticmethod
5     def func():
6         print("當普通方法用")
7 
8 Staticmethod_Demo.func()
復制代碼

 

Top

四、面向對象的更多說明

面向對象的軟件開發

很多人在學完了python的class機制之后,遇到一個生產中的問題,還是會懵逼,這其實太正常了,因為任何程序的開發都是先設計后編程,python的class機制只不過是一種編程方式,如果你硬要拿着class去和你的問題死磕,變得更加懵逼都是分分鍾的事,在以前,軟件的開發相對簡單,從任務的分析到編寫程序,再到程序的調試,可以由一個人或一個小組去完成。但是隨着軟件規模的迅速增大,軟件任意面臨的問題十分復雜,需要考慮的因素太多,在一個軟件中所產生的錯誤和隱藏的錯誤、未知的錯誤可能達到驚人的程度,這也不是在設計階段就完全解決的。

    所以軟件的開發其實一整套規范,我們所學的只是其中的一小部分,一個完整的開發過程,需要明確每個階段的任務,在保證一個階段正確的前提下再進行下一個階段的工作,稱之為軟件工程

    面向對象的軟件工程包括下面幾個部:

1.面向對象分析(object oriented analysis OOA

    軟件工程中的系統分析階段,要求分析員和用戶結合在一起,對用戶的需求做出精確的分析和明確的表述,從大的方面解析軟件系統應該做什么,而不是怎么去做。面向對象的分析要按照面向對象的概念和方法,在對任務的分析中,從客觀存在的事物和事物之間的關系,貴南出有關的對象(對象的‘特征’和‘技能’)以及對象之間的聯系,並將具有相同屬性和行為的對象用一個類class來標識。

    建立一個能反映這是工作情況的需求模型,此時的模型是粗略的。

面向對象設計(object oriented designOOD

    根據面向對象分析階段形成的需求模型,對每一部分分別進行具體的設計。

    首先是類的設計,類的設計可能包含多個層次(利用繼承與派生機制)。然后以這些類為基礎提出程序設計的思路和方法,包括對算法的設計。

    在設計階段並不牽涉任何一門具體的計算機語言,而是用一種更通用的描述工具(如偽代碼或流程圖)來描述

面向對象編程(object oriented programmingOOP

    根據面向對象設計的結果,選擇一種計算機語言把它寫成程序,可以是python

面向對象測試(object oriented testOOT

    在寫好程序后交給用戶使用前,必須對程序進行嚴格的測試,測試的目的是發現程序中的錯誤並修正它。

    面向對的測試是用面向對象的方法進行測試,以類作為測試的基本單元。

面向對象維護(object oriendted soft maintenanceOOSM

    正如對任何產品都需要進行售后服務和維護一樣,軟件在使用時也會出現一些問題,或者軟件商想改進軟件的性能,這就需要修改程序。

    由於使用了面向對象的方法開發程序,使用程序的維護比較容易。

    因為對象的封裝性,修改一個對象對其他的對象影響很小,利用面向對象的方法維護程序,大大提高了軟件維護的效率,可擴展性高。

 

    在面向對象方法中,最早發展的肯定是面向對象編程(OOP),那時OOA和OOD都還沒有發展起來,因此程序設計者為了寫出面向對象的程序,還必須深入到分析和設計領域,尤其是設計領域,那時的OOP實際上包含了現在的OOD和OOP兩個階段,這對程序設計者要求比較高,許多人感到很難掌握。

    現在設計一個大的軟件,是嚴格按照面向對象軟件工程的5個階段進行的,這個5個階段的工作不是由一個人從頭到尾完成的,而是由不同的人分別完成,這樣OOP階段的任務就比較簡單了。程序編寫者只需要根據OOd提出的思路,用面向對象語言編寫出程序既可。

    在一個大型軟件開發過程中,OOP只是很小的一個部分。

    對於全棧開發的你來說,這五個階段都有了,對於簡單的問題,不必嚴格按照這個5個階段進行,往往由程序設計者按照面向對象的方法進行程序設計,包括類的設計和程序的設計

 

Top

五、面向對象常用術語

抽象/實現

抽象指對現實世界問題和實體的本質表現,行為和特征建模,建立一個相關的子集,可以用於 繪程序結構,從而實現這種模型。抽象不僅包括這種模型的數據屬性,還定義了這些數據的接口。

對某種抽象的實現就是對此數據及與之相關接口的現實化(realization)。現實化這個過程對於客戶 程序應當是透明而且無關的。 

封裝/接口

封裝描述了對數據/信息進行隱藏的觀念,它對數據屬性提供接口和訪問函數。通過任何客戶端直接對數據的訪問,無視接口,與封裝性都是背道而馳的,除非程序員允許這些操作。作為實現的 一部分,客戶端根本就不需要知道在封裝之后,數據屬性是如何組織的。在Python中,所有的類屬性都是公開的,但名字可能被“混淆”了,以阻止未經授權的訪問,但僅此而已,再沒有其他預防措施了。這就需要在設計時,對數據提供相應的接口,以免客戶程序通過不規范的操作來存取封裝的數據屬性。

注意:封裝絕不是等於“把不想讓別人看到、以后可能修改的東西用private隱藏起來”

真正的封裝是,經過深入的思考,做出良好的抽象,給出完整且最小的接口,並使得內部細節可以對外透明

(注意:對外透明的意思是外部調用者可以順利的得到自己想要的任何功能,完全意識不到內部細節的存在)

合成

合成擴充了對類的 述,使得多個不同的類合成為一個大的類,來解決現實問題。合成 述了 一個異常復雜的系統,比如一個類由其它類組成,更小的組件也可能是其它的類,數據屬性及行為, 所有這些合在一起,彼此是“有一個”的關系。

派生/繼承/繼承結構

派生描述了子類衍生出新的特性,新類保留已存類類型中所有需要的數據和行為,但允許修改或者其它的自定義操作,都不會修改原類的定義。
繼承描述了子類屬性從祖先類繼承這樣一種方式
繼承結構表示多“代”派生,可以述成一個“族譜”,連續的子類,與祖先類都有關系。

泛化/特化

基於繼承
泛化表示所有子類與其父類及祖先類有一樣的特點。
特化描述所有子類的自定義,也就是,什么屬性讓它與其祖先類不同。

多態與多態性

多態指的是同一種事物的多種狀態:水這種事物有多種不同的狀態:冰,水蒸氣

多態性的概念指出了對象如何通過他們共同的屬性和動作來操作及訪問,而不需考慮他們具體的類。

冰,水蒸氣,都繼承於水,它們都有一個同名的方法就是變成雲,但是冰.變雲(),與水蒸氣.變雲()是截然不同的過程,雖然調用的方法都一樣

自省/反射

自省也稱作反射,這個性質展示了某對象是如何在運行期取得自身信息的。如果傳一個對象給你,你可以查出它有什么能力,這是一項強大的特性。如果Python不支持某種形式的自省功能,dir和type內建函數,將很難正常工作。還有那些特殊屬性,像__dict__,__name__及__doc__


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM