Python第七章-面向物件高階
阿新 • • 發佈:2020-04-03
# 面向物件高階
## 一、 特性
特性是指的`property`.
`property`這個詞的翻譯一直都有問題, 很多人把它翻譯為屬性, 其實是不恰當和不準確的. 在這裡翻譯成特性是為了和屬性區別開來.
屬性是指的`attribute`, 我們以前學習的例項變數和類變數是`attribute`, 所以也可以叫做例項屬性和類屬性.
---
**`property`(特性)到底是個什麼東西?**
我們前面學習類屬性和例項屬性的時候知道, 訪問他們的時候就可以直接獲取到這些屬性的值.
而特性可以看成一種特殊的屬性, 為什麼呢?
但從訪問方式來看, 特性和屬性看不出來差別, 但是特性實際上會經過計算之後再返回值. 所以每一個特性都始終與一個方法相關聯.
---
### 1.1 定義特性
**定義特性和定義例項方法類似**, 只需要另外在方法上面新增一個內建裝飾器:`@property`
**訪問特性和訪問例項變數完全一樣, 不需要使用新增括號去呼叫.**
---
```python
import math
class Circle:
def __init__(self, r):
self.r = r
@property
def area(self):
"""
定義特性
這個特性是計算出來圓的面積
:return:
"""
return math.pi * (self.r ** 2)
c = Circle(10)
print(c.area)
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124801245-683465522.png)
---
很明顯, 特性背後的本質是一個方法的存在, 所以你不可能在外面去修改這個特性的值!
試圖修改特性的值只會丟擲一個異常.
```python
c.area = 100
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124813931-630469034.png)
---
### 1.2 使用特性的設計哲學
這種特性使用方式遵循所謂的 _**統一訪問原則**_.
實際上, 定義一個類總是保持介面的統一總是好的.
有了特性, 把訪問屬性和訪問方法統一了, 都像在訪問屬性一樣, 省得去考慮到底什麼時候需要新增括號,什麼時候不用新增括號.
---
### 1.3 特性的攔截操作
python 還提供了設定和刪除屬性.
通過給方法新增其他內建裝飾器來實現
設定:`@特性名.setter`
刪除:`@特性名.deleter`
```python
class Student:
def __init__(self, name):
self._name = name # name 是特性了, 所以用例項變數儲存特性的值的是換個變數名!!!
@property
def name(self):
return self._name
@name.setter
def name(self, name):
if type(name) is str and len(name) > 2:
self._name = name
else:
print("你提供的值" + str(name) + "不合法!")
@name.deleter
def name(self):
print("對不起, name 不允許刪除")
s = Student("李四")
print(s.name)
s.name = "彩霞"
print(s.name)
s.name = "張三"
print(s.name)
del s.name
```
---
## 二、三大特性之一-封裝性
面向物件的三大特徵:封裝, 繼承, 多型
###2.1什麼是封裝性
1.封裝是面向物件程式設計的一大特點
2.面向物件程式設計的第一步,就是講屬性和方法封裝到一個抽象的類中
3.外界使用類建立物件,然後讓物件呼叫方法
4.物件方法的細節都被封裝在類的內部
在類中定義屬性, 定義方法就是在封裝資料和程式碼.
###2.2 私有化屬性
首先先明確一點, python 不能真正的對屬性(和方法)進行私有, 因為 python 沒有想 java 那樣的`private`可用.
python 提供的"私有", 是為了怕在程式設計的過程中對物件屬性不小心"誤傷"提供的一種保護機制! 這種級別的私有稍微只要知道了規則, 是很容易訪問到所謂的私有屬性或方法的.
------
#### 2.2.1 為什麼需要私有
封裝和保護資料的需要.
預設情況下, 類的所有屬性和方法都是公共的, 也就意味著對他們的訪問沒有做任何的限制.
意味著, 在基類中定義的所有內容都可以都會被派生類繼承, 並可從派生類內部進行訪問.
在面向物件的應用程式設計中, 我們通常不希望這種行為, 因為他們暴露基類的內部實現, 可能導致派生類中的使用的私有名稱與基類中使用的相同的私有名稱發生衝突.
屬性或方法私有後就可以避免這種問題!
------
#### 2.2.2 "私有"機制
為了解決前面說的問題, python 提供了一種叫做***名稱改寫(name mangling)***的機制
如果給屬性或者方法命名的時候, 使用兩個下劃線開頭(`__`)的屬性和方法名會自動變形為`_類名__方法名`, 這樣就避免了在基礎中命名衝突的問題.
```python
class Student:
def __init__(self):
pass
def __say(self):
print("我是私有方法你信嗎?")
s = Student()
s.__say() # 雙下劃線開頭的方法已經被形變, 此處訪問不到
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124836010-1606444133.png)
------
```python
s._Student__say()
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124848245-688690301.png)
------
#### 2.2.3 不是真正的私有
儘管這種方案隱藏了資料, 但是並沒有提供嚴格的機制來限制對私有屬性和方法的訪問.
雖然這種機制好像多了一層處理, 但是這種變形是發生在類的定義期間, 並不會在方法執行期間發生, 所以並沒有新增額外的開銷.
#### 2.2.4 不同的聲音
有部分人認為這種使用雙`__`的機制好辣雞, 寫兩個下劃線影響效率. 他們使用一個下劃線, 並把這個作為一個約定.
好吧, 你喜歡哪種呢?
## 三、面向物件三大特性-繼承性(Inheritance)
這一節我們來學習面向的物件的再一個特徵: 繼承
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124901526-460680220.png)
### 3.1繼承性的概念
繼承(`extends`)是建立新類的一種機制, 目的是專門使用和修改先有類的行為.
原有類稱為超類(`super class`), 基類(`base class`)或父類.
新類稱為子類或派生類.
通過繼承建立類時, 所建立的類將繼承其基類所有的屬性和方法, 派生類也可以重新定義任何這些屬性和方法, 並新增自己的新屬性和方法
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403124954501-1360999803.png)
### 3.2 繼承性的意義
繼承實現程式碼的重用,相同的程式碼不需要重複的編寫
> 從子類的角度來看,避免了重複的程式碼。(子類繼承父類後,子類可以直接使用父類的屬性和方法)
>
> 從父類的角度來看,子類擴充套件了父類的功能。(因為子類也是一個特殊的父類)
1. 子類可以直接訪問父類的屬性和方法。
2. 子類可以新增自己的屬性和方法。
3. 子類可以重寫父類的方法。
### 3.3 繼承的語法和具體實現
繼承的語法如下:
```python
class 父類名:
pass
class 子類名(父類名):
pass
```
#### 3.3.1最簡單的繼承
python 的繼承是在類名的後面新增括號, 然後在括號中宣告要繼承的父類.
```python
class Father:
def speak(self):
print("我是父類中的 speak 方法")
# Son繼承 Father 類
class Son(Father):
pass
s = Son()
s.speak()
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125011481-1627097341.png)
**說明:**
1. 從字面上我們看到`Son`沒有定義任何的方法, 但是由於`Son`繼承自`Father`, 則`Son`會繼承`Father`的所有屬性和方法
2. 呼叫方法時, 方法的查詢規則: 先在當前類中查詢, 當前類找不到想要的方法, 則去父類中查詢, 還找不到然後繼續向上查詢. 一旦找到則立即執行. 如果找到最頂層還找不到, 則會丟擲異常
------
示例程式碼
```python
# 建立人類
class Person:
# 定義吃東西方法
def eat(self):
print("吃窩窩頭。。")
# 定義睡覺方法
def sleep(self):
print("睡著啦。。")
# 建立學生類
class Student(Person):
# 子類新增方法:學習
def study(self):
print("學生學習啦。。。把你爸樂壞了。。。。。")
# 建立父類物件,訪問父類的方法
zhangsan = Person();
zhangsan.eat()
zhangsan.sleep()
# 建立子類物件,訪問父類的方法和子類的方法
ergou = Student();
ergou.eat() # 訪問父類的方法
ergou.sleep() # 訪問父類的方法
ergou.study() # 訪問子類的新增方法
```
##
####3.3.2 繼承中的`__init__()`的呼叫規則
如果子類沒有手動`__init__()`方法, 則 python 自動呼叫子類的`__init__()`的時候, 也會自動的呼叫基類的`__init()__`方法.
```python
class Father:
def __init__(self):
print("基類的 init ")
# Son繼承 Father 類
class Son(Father):
def speak(self):
pass
s = Son()
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125026961-609675782.png)
------
如果子類手動添加了`__init__()`, 則 python 不會再自動的去呼叫基類的`__init__()`
```python
class Father:
def __init__(self):
print("基類的 init ")
# Son繼承 Father 類
class Son(Father):
def __init__(self):
print("子類的 init ")
def speak(self):
pass
s = Son()
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125038891-1506310844.png)
------
如果想通過基類初始化一些資料, 則必須顯示的呼叫這個方法, 呼叫語法是:
`基類名.__init__(self, 引數...)`
```python
class Father:
def __init__(self, name):
print("基類的 init ")
self.name = name
def speak(self):
print("我是父類中的 speak 方法" + self.name)
# Son繼承 Father 類
class Son(Father):
def __init__(self, name, age):
# name 屬性的初始化應該交給基類去完成, 手動呼叫基類的方法. 一般放在首行
Father.__init__(self, name) # 調動指定類的方法, 並手動繫結這個方法的 self
print("子類的 init ")
self.age = age
s = Son("李四", 20)
s.speak()
print(s.name)
print(s.age)
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125052596-1821115968.png)
------
### 3.4方法的重寫(override)
#### 3.4.1重寫的概念
我們已經瞭解了呼叫方法時候的查詢規則, 先在子類中查詢, 子類查詢不到再去父類中查詢.
如果父類的方法不滿足子類的需求, 利用這個查詢規則, 我們就可以在子類中新增一個與父類的一樣的方法, 那麼以後就會直接執行子類的方法, 而不會再去父類中查詢.
這就叫方法的覆寫.(`override`)
```
>重寫,就是子類將父類已有的方法重新實現。
```
父類封裝的方法,不能滿足子類的需求,子類可以重寫父類的方法。在呼叫時,**呼叫的是重寫的方法,而不會呼叫父類封裝的方法。**
#### 3.4.2重寫父類方法的兩種情況
1. 覆蓋父類 的方法
父類的方法實現和子類的方法實現,完全不同,子類可以重新編寫父類的方法實現。
> 具體的實現方式,就相當於在子類中定義了一個和父類同名的方法並且實現
2. 對父類方法進行擴充套件
子類的方法實現中包含父類的方法實現。(也就是說,父類原本封裝的方法實現是子類方法的一部分)。
> 在子類中重寫父類的方法
>
> 在需要的位置使用`super().父類方法`來呼叫父類的方法
>
> 程式碼其他的位置針對子類的需求,編寫子類特有的程式碼實現。
如果在覆寫的方法中, 子類還需要執行父類的方法, 則可以手動呼叫父類的方法:
`父類名.方法(self, 引數...)`
------
```python
class Father:
def __init__(self, name):
self.name = name
def speak(self):
print("我是父類中的 speak 方法" + self.name)
# Son繼承 Father 類
class Son(Father):
def __init__(self, name, age):
Father.__init__(self, name)
self.age = age
# 子類中覆寫了父類的方法
def speak(self):
Father.speak(self)
print("我是子類的 speak 方法" + self.name + " 年齡:" + str(self.age))
s = Son("李四", 20)
s.speak()
```
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125110716-1086987139.png)
#### 3.4.3關於super
在Python中super是一個特殊的類(Python 3.x以後出現)
super()就是使用super類創建出來的物件
最常使用的場景就是在重寫父類方法時,呼叫在父類中封裝的方法實現
### 3.5、父類的私有屬性和方法
- 子類物件不能在自己的方法內部,直接訪問父類的私有屬性或私有方法
- 子類物件可以通過父類的共有方法間接訪問到私有屬性或私有方法
> 私有屬性和方法是物件的隱私,不對外公開,外界以及子類都不能直接訪問
>
> 私有屬性和方法通常用於做一些內部的事情
### 3.6、多繼承
#### 3.6.1多繼承的概念
多繼承:子類可以擁有多個父類,並且具有所有父類的屬性和方法
比如:孩子會繼承自己的父親和母親的特性
![](https://img2020.cnblogs.com/blog/1988132/202004/1988132-20200403125124061-1033896425.png)
#### 3.6.2多繼承的語法
```
class 子類名(父類名1, 父類名2...):
pass
```
示例程式碼:
```python
# 父類A
class A:
def test1(self):
print("A類中的test1方法。。")
# 父類B
class B:
def test2(self):
print("B類中的test2方法。。")
# 子類C同時繼承A和B
class C(A,B):
pass
# 建立C物件
c1 = C()
c1.test1()
c1.test2()
```
#### 3.6.3多繼承的注意事項
提問:如果不同的父類中存在同名的方法,子類物件在呼叫方法時,會呼叫哪一個父類中的方法呢?
> 開發時,應該儘量避免這種容易產生混淆的情況。如果父類之間存在同名的屬性或者方法,應該儘量避免使用多繼承
#### 3.6.4 Python中的 MRO (方法搜尋順序)[擴充套件]
python中針對類提供了一個內建屬性,`___mro__`可以檢視方法搜尋順序
MRO是method resolution order,主要用於在多繼承時判斷方法,屬性的呼叫路徑
```python
print(C.__mro__)
```
輸出結果:
```
(