二 面向對象三大特性

?

?

?

一 繼承與派生

  一、繼承定義

  二、繼承與抽象的關系

  三、繼承與重用性

  四、派生

  五、組合與重用性

  六、接口與歸一化設計

  七、抽象類

  八、繼承實現的原理

  九、子類中調用父類的方法

二 多態與多態性

  一、多態

  二、多態性

三 封裝

  一、封裝定義

  二、特性(property)

  三、封裝與擴展性

?

一 繼承與派生

一、繼承定義

什么是繼承

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

子類會“”遺傳”父類的屬性,從而解決代碼重用問題(比如練習7中Garen與Riven類有很多冗余的代碼)

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'>)

經典類與新式類

1.只有在python2中才分新式類和經典類,python3中統一都是新式類
2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類
3.在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類
3.在python3中,無論是否繼承object,都默認繼承object,即python3中所有類均為新式類
#關于新式類與經典類的區別,我們稍后討論

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

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

?

二、繼承與抽象的關系

繼承描述的是子類與父類之間的關系,是一種什么是什么的關系。要找出這種關系,必須先抽象再繼承

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

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

?

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

?

三、繼承與重用性

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

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

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

 1 class Hero:
 2     def __init__(self,nickname,aggressivity,life_value):
 3         self.nickname=nickname
 4         self.aggressivity=aggressivity
 5         self.life_value=life_value
 6 
 7     def move_forward(self):
 8         print('%s move forward' %self.nickname)
 9 
10     def move_backward(self):
11         print('%s move backward' %self.nickname)
12 
13     def move_left(self):
14         print('%s move forward' %self.nickname)
15 
16     def move_right(self):
17         print('%s move forward' %self.nickname)
18 
19     def attack(self,enemy):
20         enemy.life_value-=self.aggressivity
21 class Garen(Hero):
22     pass
23 
24 class Riven(Hero):
25     pass
26 
27 g1=Garen('草叢倫',100,300)
28 r1=Riven('銳雯雯',57,200)
29 
30 print(g1.life_value)
31 r1.attack(g1)
32 print(g1.life_value)
33 
34 '''
35 運行結果
36 '''

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

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

 1 class Foo:
 2     def f1(self):
 3         print('Foo.f1')
 4 
 5     def f2(self):
 6         print('Foo.f2')
 7         self.f1()
 8 
 9 class Bar(Foo):
10     def f1(self):
11         print('Foo.f1')
12 
13 
14 b=Bar()
15 b.f2()

?

四、派生

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

1 class Riven(Hero):
2     camp='Noxus'
3     def attack(self,enemy): #在自己這里定義新的attack,不再使用父類的attack,且不會影響父類
4         print('from riven')
5     def fly(self): #在自己這里定義新的
6         print('%s is flying' %self.nickname)

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

class Riven(Hero):camp='Noxus'def __init__(self,nickname,aggressivity,life_value,skin):Hero.__init__(self,nickname,aggressivity,life_value) #調用父類功能self.skin=skin #新屬性def attack(self,enemy): #在自己這里定義新的attack,不再使用父類的attack,且不會影響父類Hero.attack(self,enemy) #調用功能print('from riven')def fly(self): #在自己這里定義新的print('%s is flying' %self.nickname)r1=Riven('銳雯雯',57,200,'大衣')
r1.fly()
print(r1.skin)'''
運行結果
銳雯雯 is flying
大衣'''

?

五、組合與重用性

軟件重用的重要方式除了繼承之外還有另外一種方式,即:組合

組合指的是,在一個類中以另外一個類的對象作為數據屬性,稱為類的組合

組合與繼承都是有效地利用已有類的資源的重要方式。但是二者的概念和使用場景皆不同,

1.繼承的方式

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

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

2.組合的方式

用組合的方式建立了類與組合的類之間的關系,它是一種‘有’的關系,比如教授有生日,教授教python和linux課程,教授有學生s1、s2、s3...

 1 class People:
 2     def __init__(self,name,age,sex):
 3         self.name=name
 4         self.age=age
 5         self.sex=sex
 6 
 7 class Course:
 8     def __init__(self,name,period,price):
 9         self.name=name
10         self.period=period
11         self.price=price
12     def tell_info(self):
13         print('<%s %s %s>' %(self.name,self.period,self.price))
14 
15 class Teacher(People):
16     def __init__(self,name,age,sex,job_title):
17         People.__init__(self,name,age,sex)
18         self.job_title=job_title
19         self.course=[]
20         self.students=[]
21 
22 
23 class Student(People):
24     def __init__(self,name,age,sex):
25         People.__init__(self,name,age,sex)
26         self.course=[]
27 
28 
29 lucy=Teacher('lucy',56,'male','python講師')
30 s1=Student('anna',18,'female')
31 
32 python=Course('python','3mons',3000.0)
33 linux=Course('python','3mons',3000.0)
34 
35 #為老師egon和學生s1添加課程
36 lucy.course.append(python)
37 lucy.course.append(linux)
38 s1.course.append(python)
39 
40 #為老師lucy添加學生s1
41 lucy.students.append(s1)
42 
43 
44 #使用
45 for obj in lucy.course:
46     obj.tell_info()
例子:繼承與組合

當類之間有顯著不同,并且較小的類是較大的類所需要的組件時,用組合比較好

?

六、接口與歸一化設計

1.什么是接口

?

 1 =================第一部分:Java 語言中的接口很好的展現了接口的含義: IAnimal.java
 2 /*
 3 * Java的Interface接口的特征:
 4 * 1)是一組功能的集合,而不是一個功能
 5 * 2)接口的功能用于交互,所有的功能都是public,即別的對象可操作
 6 * 3)接口只定義函數,但不涉及函數實現
 7 * 4)這些功能是相關的,都是動物相關的功能,但光合作用就不適宜放到IAnimal里面了 */
 8 
 9 package com.oo.demo;
10 public interface IAnimal {
11     public void eat();
12     public void run(); 
13     public void sleep(); 
14     public void speak();
15 }
16 
17 =================第二部分:Pig.java:豬”的類設計,實現了IAnnimal接口 
18 package com.oo.demo;
19 public class Pig implements IAnimal{ //如下每個函數都需要詳細實現
20     public void eat(){
21         System.out.println("Pig like to eat grass");
22     }
23 
24     public void run(){
25         System.out.println("Pig run: front legs, back legs");
26     }
27 
28     public void sleep(){
29         System.out.println("Pig sleep 16 hours every day");
30     }
31 
32     public void speak(){
33         System.out.println("Pig can not speak"); }
34 }
35 
36 =================第三部分:Person2.java
37 /*
38 *實現了IAnimal的“人”,有幾點說明一下: 
39 * 1)同樣都實現了IAnimal的接口,但“人”和“豬”的實現不一樣,為了避免太多代碼導致影響閱讀,這里的代碼簡化成一行,但輸出的內容不一樣,實際項目中同一接口的同一功能點,不同的類實現完全不一樣
40 * 2)這里同樣是“人”這個類,但和前面介紹類時給的類“Person”完全不一樣,這是因為同樣的邏輯概念,在不同的應用場景下,具備的屬性和功能是完全不一樣的 */
41 
42 package com.oo.demo;
43 public class Person2 implements IAnimal { 
44     public void eat(){
45         System.out.println("Person like to eat meat");
46     }
47 
48     public void run(){
49         System.out.println("Person run: left leg, right leg");
50     }
51 
52     public void sleep(){
53         System.out.println("Person sleep 8 hours every dat"); 
54     }
55 
56     public void speak(){
57         System.out.println("Hellow world, I am a person");
58     } 
59 }
60 
61 =================第四部分:Tester03.java
62 package com.oo.demo;
63 
64 public class Tester03 {
65     public static void main(String[] args) {
66         System.out.println("===This is a person==="); 
67         IAnimal person = new Person2();
68         person.eat();
69         person.run();
70         person.sleep();
71         person.speak();
72         
73         System.out.println("\n===This is a pig===");
74         IAnimal pig = new Pig();
75         pig.eat();
76         pig.run();
77         pig.sleep();
78         pig.speak();
79     } 
80 }
java中的interface

2. 為何要用接口

接口提取了一群類共同的函數,可以把接口當做一個函數的集合。

然后讓子類去實現接口中的函數。

這么做的意義在于歸一化,什么叫歸一化,就是只要是基于同一個接口實現的類,那么所有的這些類產生的對象在使用時,從用法上來說都一樣。

?

歸一化的好處在于:

1. 歸一化讓使用者無需關心對象的類是什么,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。

2. 歸一化使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合

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

2.2:再比如:我們有一個汽車接口,里面定義了汽車所有的功能,然后由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現了汽車接口,這樣就好辦了,大家只需要學會了怎么開汽車,那么無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函數調用)都一樣

3. 模仿interface

在python中根本就沒有一個叫做interface的關鍵字,如果非要去模仿接口的概念

可以借助第三方模塊:

http://pypi.python.org/pypi/zope.interface

twisted的twisted\internet\interface.py里使用zope.interface

文檔https://zopeinterface.readthedocs.io/en/latest/

設計模式:https://github.com/faif/python-patterns

?

也可以使用繼承:?

繼承的兩種用途

一:繼承基類的方法,并且做出自己的改變或者擴展(代碼重用):實踐中,繼承的這種用途意義并不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。

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

class Interface:#定義接口Interface類來模仿接口的概念,python中壓根就沒有interface關鍵字來定義一個接口。def read(self): #定接口函數readpassdef write(self): #定義接口函數writepassclass Txt(Interface): #文本,具體實現read和writedef read(self):print('文本數據的讀取方法')def write(self):print('文本數據的讀取方法')class Sata(Interface): #磁盤,具體實現read和writedef read(self):print('硬盤數據的讀取方法')def write(self):print('硬盤數據的讀取方法')class Process(Interface):def read(self):print('進程數據的讀取方法')def write(self):print('進程數據的讀取方法')

上面的代碼只是看起來像接口,其實并沒有起到接口的作用,子類完全可以不用去實現接口?,這就用到了抽象類

?

七、抽象類

1 什么是抽象類

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

2 為什么要有抽象類

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

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

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

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

3. 在python中實現抽象類

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

4. 抽象類與接口

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

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

?

八、繼承實現的原理

1 繼承順序

在Java和C#中子類只能繼承一個父類,而Python中子類可以同時繼承多個父類,如A(B,C,D)

如果繼承關系為非菱形結構,則會按照先找B這一條分支,然后再找C這一條分支,最后找D這一條分支的順序直到找到我們想要的屬性

如果繼承關系為菱形結構,那么屬性的查找方式有兩種,分別是:深度優先和廣度優先

 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__) #只有新式才有這個屬性可以查看線性列表,經典類沒有這個屬性
28 
29 #新式類繼承順序:F->D->B->E->C->A
30 #經典類繼承順序:F->D->B->A->E->C
31 #python3中統一都是新式類
32 #pyhon2中才分新式類與經典類

?

2 繼承原理(python如何實現的繼承)

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

1 >>> F.mro() #等同于F.__mro__
2 [<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.如果對下一個類存在兩個合法的選擇,選擇第一個父類

?

九、子類中調用父類的方法

方法一:指名道姓,即父類名.父類方法()

 1 #_*_coding:utf-8_*_
 2 __author__ = 'Linhaifeng'
 3 
 4 class Vehicle: #定義交通工具類
 5      Country='China'
 6      def __init__(self,name,speed,load,power):
 7          self.name=name
 8          self.speed=speed
 9          self.load=load
10          self.power=power
11 
12      def run(self):
13          print('開動啦...')
14 
15 class Subway(Vehicle): #地鐵
16     def __init__(self,name,speed,load,power,line):
17         Vehicle.__init__(self,name,speed,load,power)
18         self.line=line
19 
20     def run(self):
21         print('地鐵%s號線歡迎您' %self.line)
22         Vehicle.run(self)
23 
24 line13=Subway('中國地鐵','180m/s','1000人/箱','',13)
25 line13.run()

方法二:super()

 1 class Vehicle: #定義交通工具類
 2      Country='China'
 3      def __init__(self,name,speed,load,power):
 4          self.name=name
 5          self.speed=speed
 6          self.load=load
 7          self.power=power
 8 
 9      def run(self):
10          print('開動啦...')
11 
12 class Subway(Vehicle): #地鐵
13     def __init__(self,name,speed,load,power,line):
14         #super(Subway,self) 就相當于實例本身 在python3中super()等同于super(Subway,self)
15         super().__init__(name,speed,load,power)
16         self.line=line
17 
18     def run(self):
19         print('地鐵%s號線歡迎您' %self.line)
20         super(Subway,self).run()
21 
22 class Mobike(Vehicle):#摩拜單車
23     pass
24 
25 line13=Subway('中國地鐵','180m/s','1000人/箱','',13)
26 line13.run()

了解部分:

即使沒有直接繼承關系,super仍然會按照mro繼續往后查找

 1 #A沒有繼承B,但是A內super會基于C.mro()繼續往后找
 2 class A:
 3     def test(self):
 4         super().test()
 5 class B:
 6     def test(self):
 7         print('from B')
 8 class C(A,B):
 9     pass
10 
11 c=C()
12 c.test() #打印結果:from B
13 
14 
15 print(C.mro())
16 #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

指名道姓與super()的區別

 1 #指名道姓
 2 class A:
 3     def __init__(self):
 4         print('A的構造方法')
 5 class B(A):
 6     def __init__(self):
 7         print('B的構造方法')
 8         A.__init__(self)
 9 
10 
11 class C(A):
12     def __init__(self):
13         print('C的構造方法')
14         A.__init__(self)
15 
16 
17 class D(B,C):
18     def __init__(self):
19         print('D的構造方法')
20         B.__init__(self)
21         C.__init__(self)
22 
23     pass
24 f1=D() #A.__init__被重復調用
25 '''
26 D的構造方法
27 B的構造方法
28 A的構造方法
29 C的構造方法
30 A的構造方法
31 '''
32 
33 
34 #使用super()
35 class A:
36     def __init__(self):
37         print('A的構造方法')
38 class B(A):
39     def __init__(self):
40         print('B的構造方法')
41         super(B,self).__init__()
42 
43 
44 class C(A):
45     def __init__(self):
46         print('C的構造方法')
47         super(C,self).__init__()
48 
49 
50 class D(B,C):
51     def __init__(self):
52         print('D的構造方法')
53         super(D,self).__init__()
54 
55 f1=D() #super()會基于mro列表,往后找
56 '''
57 D的構造方法
58 B的構造方法
59 C的構造方法
60 A的構造方法
61 '''

當你使用super()函數時,Python會在MRO列表上繼續搜索下一個類。只要每個重定義的方法統一使用super()并只調用它一次,那么控制流最終會遍歷完整個MRO列表,每個方法也只會被調用一次(注意注意注意:使用super調用的所有屬性,都是從MRO列表當前的位置往后找,千萬不要通過看代碼去找繼承關系,一定要看MRO列表

?

?

二 多態與多態性

一、多態

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

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

 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 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()

二 為什么要用多態性(多態性的好處)

其實大家從上面多態性的例子可以看出,我們并沒有增加什么新的知識,也就是說python本身就是支持多態性的,這么做的好處是什么呢?

1.增加了程序的靈活性

  以不變應萬變,不論對象千變萬化,使用者都是同一種形式去調用,如func(animal)

2.增加了程序額可擴展性

  通過繼承animal類創建了一個新的類,使用者無需更改自己的代碼,還是用func(animal)去調用?

 1 >>> class Cat(Animal): #屬于動物的另外一種形態:貓
 2 ...     def talk(self):
 3 ...         print('say miao')
 4 ... 
 5 >>> def func(animal): #對于使用者來說,自己的代碼根本無需改動
 6 ...     animal.talk()
 7 ... 
 8 >>> cat1=Cat() #實例出一只貓
 9 >>> func(cat1) #甚至連調用方式也無需改變,就能調用貓的talk功能
10 say miao
11 
12 '''
13 這樣我們新增了一個形態Cat,由Cat類產生的實例cat1,使用者可以在完全不需要修改自己代碼的情況下。使用和人、狗、豬一樣的方式調用cat1的talk方法,即func(cat1)
14 '''

三 ?鴨子類型

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

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

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

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

例2:其實大家一直在享受著多態性帶來的好處,比如Python的序列類型有多種形態:字符串,列表,元組,多態性體現如下

 1 #str,list,tuple都是序列類型
 2 s=str('hello')
 3 l=list([1,2,3])
 4 t=tuple((4,5,6))
 5 
 6 #我們可以在不考慮三者類型的前提下使用s,l,t
 7 s.__len__()
 8 l.__len__()
 9 t.__len__()
10 
11 len(s)
12 len(l)
13 len(t)

?

三 封裝

一、封裝定義

1. 引子

從封裝本身的意思去理解,封裝就好像是拿來一個麻袋,把小貓,小狗,小王八,還有alex一起裝進麻袋,然后把麻袋封上口子。照這種邏輯看,封裝=‘隱藏’,這種理解是相當片面的

2. 先看如何隱藏

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

 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是可以訪問到的,
14 #這種,在外部是無法通過__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)封裝數據:將數據隱藏起來這不是目的。隱藏起來然后對外提供操作該數據的接口,然后我們可以在接口附加上對該數據操作的限制,以此完成對數據屬性操作的嚴格控制。

 1 class Teacher:
 2     def __init__(self,name,age):
 3         # self.__name=name
 4         # self.__age=age
 5         self.set_info(name,age)
 6 
 7     def tell_info(self):
 8         print('姓名:%s,年齡:%s' %(self.__name,self.__age))
 9     def set_info(self,name,age):
10         if not isinstance(name,str):
11             raise TypeError('姓名必須是字符串類型')
12         if not isinstance(age,int):
13             raise TypeError('年齡必須是整型')
14         self.__name=name
15         self.__age=age
16 
17 
18 t=Teacher('lucy',18)
19 t.tell_info()
20 
21 t.set_info('lucy',19)
22 t.tell_info()

2) 封裝方法:目的是隔離復雜度

封裝方法舉例:?

1. 你的身體沒有一處不體現著封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然后為你提供一個尿的接口就可以了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。

2. 電視機本身是一個黑盒子,隱藏了所有細節,但是一定會對外提供了一堆按鈕,這些按鈕也正是接口的概念,所以說,封裝并不是單純意義的隱藏!!!

3.?快門就是傻瓜相機為傻瓜們提供的方法,該方法將內部復雜的照相功能都隱藏起來了

提示:在編程語言里,對外提供的接口(接口可理解為了一個入口),可以是函數,稱為接口函數,這與接口的概念還不一樣,接口代表一組接口函數的集合體。

 1 #取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢
 2 #對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,很明顯這么做
 3 #隔離了復雜度,同時也提升了安全性
 4 
 5 class ATM:
 6     def __card(self):
 7         print('插卡')
 8     def __auth(self):
 9         print('用戶認證')
10     def __input(self):
11         print('輸入取款金額')
12     def __print_bill(self):
13         print('打印賬單')
14     def __take_money(self):
15         print('取款')
16 
17     def withdraw(self):
18         self.__card()
19         self.__auth()
20         self.__input()
21         self.__print_bill()
22         self.__take_money()
23 
24 a=ATM()
25 a.withdraw()
隔離復雜度的例子

3) 了解

python并不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,如果模塊名以單下劃線開頭,那么from module import *時不能被導入,但是你from module import _private_module依然是可以導入的

其實很多時候你去調用一個模塊的功能時會遇到單下劃線開頭的(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內部調用的,作為外部的你,一意孤行也是可以用的,只不過顯得稍微傻逼一點點

python要想與其他編程語言一樣,嚴格控制屬性的訪問權限,只能借助內置方法如__getattr__

?

二、 特性(property)?

什么是特性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('lucy',45,1.65)
11 print(p1.bmi)

為什么要用property

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

除此之外,看下

1 ps:面向對象的封裝有三種方式:
2 【public】
3 這種其實就是不封裝,是對外公開的
4 【protected】
5 這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什么大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開
6 【private】
7 這種封裝對誰都不公開

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'
 1 class Foo:
 2     def __init__(self,val):
 3         self.__NAME=val #將所有的數據屬性都隱藏起來
 4 
 5     def getname(self):
 6         return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)
 7 
 8     def setname(self,value):
 9         if not isinstance(value,str):  #在設定值之前進行類型檢查
10             raise TypeError('%s must be str' %value)
11         self.__NAME=value #通過類型檢查后,將值value存放到真實的位置self.__NAME
12 
13     def delname(self):
14         raise TypeError('Can not delete')
15 
16     name=property(getname,setname,delname) #不如裝飾器的方式清晰

?

三、封裝與擴展性

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

 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
16 
17 
18 #類的設計者,輕松的擴展了功能,而類的使用者完全不需要改變自己的代碼
19 class Room:
20     def __init__(self,name,owner,width,length,high):
21         self.name=name
22         self.owner=owner
23         self.__width=width
24         self.__length=length
25         self.__high=high
26     def tell_area(self): #對外提供的接口,隱藏內部實現,此時我們想求的是體積,內部邏輯變了,只需求修該下列一行就可以很簡答的實現,而且外部調用感知不到,仍然使用該方法,但是功能已經變了
27         return self.__width * self.__length * self.__high
28 
29 
30 #對于仍然在使用tell_area接口的人來說,根本無需改動自己的代碼,就可以用上新功能
31 >>> r1.tell_area()

?

轉載于:https://www.cnblogs.com/eric_yi/p/8451338.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/284117.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/284117.shtml
英文地址,請注明出處:http://en.pswp.cn/news/284117.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CSS3新屬性

邊框&#xff1a; border-radius 用于創建圓角 div { border:2px solid; border-radius:25px; -moz-border-radius:25px; /* Old Firefox */ } box-shadow 用于向方框添加陰影 div { box-shadow: 10px 10px 5px #888888; } border-image 使用圖片來創建邊框 div { border-image…

Android實用筆記——使用Spinner實現下拉列表

2019獨角獸企業重金招聘Python工程師標準>>> 1、編輯activity_main.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"mat…

基于.NET 6 的開源訪客管理系統

簡單介紹一下系統功能系統用于簡化訪客登記、查詢、保存。傳統的登記方式&#xff0c;不僅浪費紙張&#xff0c;而且還面臨保存的問題&#xff0c;查閱不方便。該系統為了在疫情期間能很好管理訪客登記做好風險管控,同時可以整合智能設備做到自動確認并跟蹤訪客的行動軌跡,該項…

完整的產品管理工作流程

產品經理的工作具體會落實到工作流程中&#xff0c;所以工作流程很大程度上會體現工作層次。很多白領產品經理&#xff0c;多年來在一個低層次的流程中轉圈——理需求、畫原型、寫文檔、管項目、驗收上線&#xff0c;一個版本上線之后立刻對下一個版本理需求、畫原型、寫文檔、…

java爬蟲-簡單爬取網頁圖片

剛剛接觸到“爬蟲”這個詞的時候是在大一&#xff0c;那時候什么都不明白&#xff0c;但知道了百度、谷歌他們的搜索引擎就是個爬蟲。 現在大二。再次燃起對爬蟲的熱愛&#xff0c;查閱資料&#xff0c;知道常用java、python語言編程&#xff0c;這次我選擇了java。在網上查找的…

擴展方法必須在非泛型靜態類中定義

擴展方法必須在非泛型靜態類中定義&#xff1a;public class CustomerHelperClass{public static MvcHtmlString CreateImage(string p_w_picpathSource, string altText, string width, string height){//通過TagBulider創建標簽TagBuilder p_w_picpathTag new TagBuilder(&…

Windows Server 2016-圖形化遷移FSMO角色

上章節我們簡單介紹了三種不同方式查看FSMO主機角色信息&#xff0c;在開篇之前我們簡單回顧一下FSMO五種操作主機角色&#xff1a;林范圍操作主機角色有兩種&#xff0c;分別是 架構主機角色&#xff08;Schema Master&#xff09;和 域命名主機角色&#xff08;Domain Naming…

C# WPF設備監控軟件(經典)-下篇

上節已經對本軟件的功能和意圖進行了詳細講解&#xff0c;這節就不再啰嗦&#xff0c;本節主要對功能實現和代碼部分展開講解.01—前臺代碼前臺XAML:<Window x:Class"EquipmentMonitor.EquipmentView"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/pr…

[轉]互聯網最大謠言:程序員35歲必淘汰?今天我就來擊碎他

朋友&#xff0c;只要你是程序員&#xff0c;你一定知道996和“程序員35歲必死”的言論。 這兩個話題在互聯網上的討論一次比一次激烈。 996工作制&#xff0c;眾所周知&#xff0c;每天早上9點到崗&#xff0c;一直待到晚上9點&#xff0c;每周工作6天&#xff0c;很多互聯網公…

【ArcGIS微課1000例】0057:將多波段柵格(影像.tif)背景設置為無數據nodata的方法

本文講解將多波段柵格(影像.tif)背景設置為無數據nodata的方法。 文章目錄 一、背景值識別二、背景值去除【推薦閱讀】: 【ArcGIS微課1000例】0056:將單波段柵格背景設置為無數據NoData的方法 一、背景值識別 可以用【識別】工具來獲取影像數據的背景值。 在背景上單擊,…

華為HCIA認證H12-811題庫新增

801、[單選題]178/832、在系統視圖下鍵入什么命令可以切換到用戶視圖? A quit B souter C system-view D user-view 試題答案&#xff1a;A 試題解析&#xff1a;在系統視圖下鍵入quit命令退出到用戶視圖。因此答案選A。 802、[單選題]“網絡管理員在三層交換機上創建了V…

經典Java微服務架構教程 微服務從開發到部署

圖書目錄腦圖&#xff1a; 本書根據開源項目整理&#xff0c;由于原在線文檔無法正常使用&#xff0c;本人重新在Github上重新布署了一套在線文檔。 書中講解非常詳細&#xff0c;并且有在線的視頻教程&#xff0c;另有在線文檔和在線的源碼。 書中的代碼由于PDF排版問題可能顯…

linux下redis安裝

轉自&#xff1a;http://blog.java1234.com/blog/articles/311.html Redis從一開始就只支持Linux&#xff0c;后面雖然有團隊搞出Window版本&#xff0c;但是我還是建議大伙安裝到Linux中。 準備工作 &#xff08;wm VirtualBox&#xff09; VMware 以及Xshell https://redis…

cobbler koan自動重裝系統

介紹 koan是kickstart-over-a-network的縮寫&#xff0c;它是cobbler的客戶端幫助程序&#xff0c;koan允許你通過網絡提供虛擬機&#xff0c;也允許你重裝已經存在的客戶端。當運行時&#xff0c;koan會從遠端的cobbler server獲取安裝信息&#xff0c;然后根據獲取的安裝信息…

Quartz.NET simple_demo

Quartz.NET是一個開源的作業調度框架&#xff0c;非常適合在平時的工作中&#xff0c;定時輪詢數據庫同步&#xff0c;定時郵件通知&#xff0c;定時處理數據等。 Quartz.NET允許開發人員根據時間間隔&#xff08;或天&#xff09;來調度作業。它實現了作業和觸發器的多對多關系…

Hello Playwright:(9)執行 JavaScript 代碼

Playwright 提供了大量的 API 用于與頁面元素交互&#xff0c;但是在某些場景下還是不能完全滿足要求。比如我們需要獲得包括元素本身的 HTML&#xff0c;但是目前只有下列 API :InnerHTMLAsync 返回元素內的 HTML 內容InnerTextAsync 返回元素內的文本內容而使用 JavaScript 執…

【PhotoScan精品教程】photoscan無法啟動此程序,因為計算機中丟失cholmod.dll解決辦法

安裝完航測軟件photoscan&#xff0c;打開時提示&#xff1a;無法啟動此程序&#xff0c;因為計算機中丟失 cholmod.dll解決辦法。 錯誤提示&#xff1a; 解決辦法&#xff1a; 并不是缺少該動態鏈接庫文件&#xff0c;而是補丁文件拷貝錯了。

什么是中臺?企業為什么要建中臺?從數據中臺到AI中臺。

從去年開始&#xff0c;好像就有一只無形的手一直將我與“微服務”、“平臺化”、“中臺化”撮合在一起&#xff0c;給我帶來了很多的困擾和思考與收獲。 故事的開始源于去年的技術雷達峰會&#xff0c;我在會上做了一場關于平臺崛起的主題分享&#xff08;《The Rise of Plat…

老司機帶你重構Android的v4包的部分源碼

版權聲明&#xff1a;本文為博主原創文章&#xff0c;未經博主允許不得轉載。https://www.jianshu.com/p/a08d754944c4 轉載請標明出處&#xff1a;https://www.jianshu.com/p/a08d754944c4 本文出自 AWeiLoveAndroid的博客 【前言】過年回家忙著干活&#xff0c;忙著給親戚的孩…

.NET靜態代碼織入——肉夾饃(Rougamo) 發布1.1.0

肉夾饃是什么肉夾饃(https://github.com/inversionhourglass/Rougamo)通過靜態代碼織入方式實現AOP的組件。.NET常用的AOP有Castle DynamicProxy、AspectCore等&#xff0c;以上兩種AOP組件都是通過運行時生成一個代理類執行AOP代碼的&#xff0c;肉夾饃則是在代碼編譯時直接修…