豬行天下之Python基礎——8.1 類與物件

coder-pig發表於2019-04-09

內容簡述:

  • 1、面相物件的理解
  • 2、類與物件
  • 3、繼承
  • 4、組合
  • 5、物件相關的內建函式

1、面相物件的理解

考慮到部分讀者可能沒有接觸過物件導向程式設計,所以先介紹下物件導向的一些特徵,形成一個物件導向概念的基本認知,有助於後面具體的學習Python的物件導向程式設計。


① 物件引入

按照普通人的認知,物件就是我們日常生活中談論到的男女物件。見過這樣的問題:

“我沒有物件是不是就沒辦法學習物件導向程式設計了?”

答案肯定不是,程式設計界有這樣一句名言,“萬物皆物件”,意思:把任何事物都看做一個物件。

所有的事物都具有兩個特點:「有什麼」和「能做什麼」,舉個例子:

老鷹有翅膀,能夠飛翔。在物件導向看來,「老鷹」就是一個「物件」,「翅膀」是「屬性」,「用來描述物件」,而「能飛翔」是「方法」,這就是「物件的功能」。


② 類的引入

自然界除了老鷹外,還有很多有翅膀的動物,比如燕子,天鵝等。它們都具有上面兩個相似的特點,我們可以把這種「具有相同或相似性質的物件」進行抽象(抽取共性)從而形成一個類,比如這裡的鳥類,可以寫出這樣的虛擬碼:

鳥類{
    屬性:翅膀 = (不同的顏色,不同的形狀)
    方法:飛翔(能否)
}
複製程式碼

然後有個名字叫「類的例項」,類的一個個具體實現,其實就是「物件」,比如這裡例項化不同的鳥的虛擬碼:

老鷹 = 鳥類(翅膀 = 長而寬闊,飛翔(能))
燕子 = 鳥類(翅膀 = 俊俏輕快,飛翔(能))
鴨子 = 鳥類(翅膀 = 較短,飛翔(否))
複製程式碼

③ 類設計的要求和思想

  • 要求:高內聚,低耦合,從而提高物件的可複用性。
  • 思想:以物件為中心,先開發類,得到物件,再通過物件間的相互通訊實現功能。

2、類與物件

對類與物件的概念有個大概的認知後,我們來開始學習Python中類與物件相關的語法。

① 定義類和例項

Python中使用class關鍵字定義類,我們來定義一個鳥類:

class Bird:
    """
    鳥類
    """

    kind = '動物'
name = "鳥"

    def __init__(self, name, wings):
        self.name = name
        self.wings = wings

    def can_fly(self, can):
        print("%s有 %s 的翅膀,%s飛翔" % (self.name, self.wings, '能' if can else '不能'))

if __name__ == '__main__':
    eagle = Bird("老鷹""長而寬闊")
    duck = Bird("鴨子""較短")
    eagle.can_fly(True)
    duck.can_fly(False)
複製程式碼

執行結果如下

老鷹有 長而寬闊 的翅膀,能飛翔
鴨子有 較短 的翅膀,不能飛翔
複製程式碼

② __init__初始化函式

在例項化物件的時候會自動呼叫,給該物件屬性的值初始化,要注意: 不推薦使用類名.__init__()這種方式去呼叫初始化函式,以避免引起不必要的問題。


new建構函式

關於這個函式就涉及到Python經典類與新式類的概念了,在Python 2.x中,預設經典類除非顯式的繼承object類才是新式類。而在Python 3.x預設所有類都是新式類不用顯式繼承object類
 
新式類相比經典類增加了很多的內建屬性,比如可以通過__class__獲得自身型別等,還有這裡的__new__()函式。而這個函式的呼叫時機在init__函式之前,作用是:可以呼叫其他類的構造方法或者直接返回別的物件來作為本類的例項
 
__new__(cls, *args,**kw),第一個參數列示要例項化的類,該引數在例項化的時候由Python直譯器自動提供。另外要注意,__new()
函式,必須要有返回值,如果該函式沒有成功返回cls型別的物件,是不會呼叫__init__()來對物件進行初始化!!!

初學者容易把這兩個弄混,還是列出來幫助區分:

  • __init__():用於初始化新例項控制初始化過程,可新增一些屬性,做些額外操作,發生在類例項被建立完後,它是物件級別的函式
  • __new__():用於控制生成新例項的過程,它是類級別的函式

④ 類屬性與例項屬性

類屬性類的屬性,當定義了一個類屬性後,這個變數雖然歸類所有但是類和例項都可以訪問到類屬性是類和例項的共有資料。當類屬性和例項屬性同名時,訪問順序是:例項屬性 -> 類屬性如果兩個都不存在則會報錯。比如下面這樣的程式碼:

print("類訪問類變數:%s" % Bird.kind)
print("類訪問變類量:%s" % Bird.name)
print("例項訪問類變數:%s" % eagle.kind)
print("例項訪問同名變數:%s" % duck.name)
複製程式碼

執行結果如下

類訪問類變數:動物
類訪問變類量:鳥
例項訪問類變數:動物
例項訪問同名變數:鴨子
複製程式碼

例項屬性則是與例項繫結的屬性,可以通過 例項名.屬性名 的方式呼叫,代表了例項的資料部分


⑤ 類函式,成員函式與靜態函式

「類函式」用於訪問類屬性,使用@classmethod裝飾器來修飾,第一個引數是cls,類本身用於呼叫類屬性但是不能訪問例項屬性。類方法可以通過類直接呼叫,或通過例項直接呼叫。但無論哪種呼叫方式,最左側傳入的引數一定是類本身!!!程式碼示例如下:

class A:
    @classmethod
    def fun_a(cls):
        print(type(cls), cls)

if __name__ == '__main__':
    A.fun_a()
    a = A()
    a.fun_a()
複製程式碼

執行結果如下

<class 'type'> <class '__main__.A'>
<class 'type'> <class '__main__.A'>
複製程式碼

「成員函式和類例項繫結」類例項化後才能呼叫,它的第一個參數列示例項本身,一般用self表示成員函式可以直接操作物件內部的資料。如果使用類直接呼叫成員函式需要顯式地將例項作為引數傳入。程式碼示例如下:

class B:
    def fun_b(self):
        print("Call fun_b()")

if __name__ == '__main__':
    b = B()
    b.fun_b()
    B.fun_b(b)  # 類呼叫成員函式需將例項傳入
複製程式碼

執行結果如下

Call fun_b()
Call fun_b()
複製程式碼

靜態函式」,在定義上面的fun_b函式的時候,智慧提示裡就有一個Make method static的選項,對於這種不需要self引數的函式(無需例項參與)都可以定義成靜態函式呼叫過程中無需將類例項化。使用@staticmethod裝飾器宣告,通過 類名.函式名例項.函式名進行呼叫,程式碼示例如下:

class C:
    @staticmethod
    def fun_c():
        print("Call fun_c()")

if __name__ == '__main__':
    C.fun_c()
    c = C()
    c.fun_c()
複製程式碼

執行結果如下

Call fun_c()
Call fun_c()
複製程式碼

⑥ 訪問控制

所謂的訪問控制,就是「類的屬性和方法是公有還是私有」,如果屬性和方法只能在類內部訪問,而不能被例項訪問的話,我們就稱這個屬性或方法為私有的
 
Python和其他程式語言不同,沒有類似於public和private這樣的訪問許可權修飾符,而是採用一種「名字改編技術」。預設公有,而私有的屬性名和方法名會加上兩下劃線,比如下面的__skill,當然這只是偽私有,改成了_類名私有屬性/方法名,比如下面呼叫people._Person__skill,是可以訪問到私有成員的:

class People:
    sex = 1  # 類屬性
    __skill = "敲程式碼"  # 私有類屬性,只能類內部訪問,外部無法訪問

    def speak(self):
        print("我是一個人,技能是:%s" % self.__skill, end='\t')

people = People()
people.speak()
people.sex = -1
print("性別:" + ("男" if people.sex == 1 else "女"))
print("訪問私有屬性:%s" % people._People__skill)
複製程式碼

執行結果如下

我是一個人,技能是:敲程式碼   性別:女
訪問私有屬性:敲程式碼
複製程式碼

雖然可以這樣訪問到私有成員,但是不建議這樣做!

另外還有一種「單下劃線開頭的變數名或方法名」,同樣是私有成員,不過類和例項都能訪問,也會被子類繼承。如果你不想屬性或方法被子類繼承就還是用雙下劃線吧!還有一種「開頭結尾都是雙下劃線的屬性或函式」是類的特殊成員,有特殊用途,比如上面的__init__初始化方法;最後如果你「定義的變數和某個保留關鍵字衝突」的話,可以使用單下劃線作為字尾,比如:in_ = 1。


⑦ 動態繫結

Python中可以「動態地為類或物件繫結屬性或函式」。類動態繫結屬性與函式,對該類的所有例項有效。程式碼示例如下:

class A:
    def __init__(self, id_):
        self.id_ = id_

# 定義一個用於動態繫結的函式
def set_name(self, name):
    print("呼叫了動態繫結的函式")
    self.name = name

if __name__ == '__main__':
    # 動態繫結一個屬性
    A.kind = "人類"

    # 動態繫結一個函式
    A.set_name = set_name
    a = A(1)

    # 類訪問動態繫結的屬性
    print(A.kind)

    # 例項訪問動態繫結的屬性
    print(a.kind)

    # 類訪問動態繫結的函式
    A.set_name(a,'123')

    # 例項訪問動態繫結的函式
    a.set_name('321')
複製程式碼

執行結果如下

人類
人類
呼叫了動態繫結的函式
呼叫了動態繫結的函式
複製程式碼

例項動態繫結屬性與函式,只對當前物件有效,對其他例項無效,需要用到一個MethodType類,程式碼示例如下:

from types import MethodType

class B:
    def __init__(self, id_):
        self.id_ = id_

# 定義一個用於動態繫結的函式
def set_name(self, name):
    print("呼叫了動態繫結的函式")
    self.name = name

if __name__ == '__main__':
    b_1 = B('1')

    # 動態為例項1繫結一個屬性
    b_1.kind = "人類"

    # 動態為例項1繫結一個函式
    b_1.set_name = MethodType(set_name, b_1)

    # 例項1設定動態繫結的屬性與函式
    print(b_1.kind)
    b_1.set_name('123')

    # 另一個類例項呼叫動態繫結的屬性
    b_2 = B('2')
    print(b_2.kind)
複製程式碼

執行結果如下

人類
Traceback (most recent call last):
呼叫了動態繫結的函式
  File "/Users/jay/Project/Python/Book/Chapter 9/9_7.py", line 30in <module>
    print(b_2.kind)
AttributeError: 'B' object has no attribute 'kind'
複製程式碼

3、繼承

物件導向的最大優點是程式碼重用,而實現程式碼重用的重要的方法就是通過Python的繼承機制。這個繼承理解為我們日常說的遺產繼承,兒子繼承父親的遺產。類比成程式設計裡對應子類和父類,子類繼承父類所有的屬性與函式,可以進行重寫者進行擴充套件以實現更多的功能。

Python中關於繼承的規則如下

  • 繼承寫法class 子類(父類)
  • 子類可以繼承父類的所有屬性與方法
  • 子類定義與父類同名的屬性與方法會自動覆蓋
  • 重寫時如果想呼叫父類的同名方法可以使用super().方法名呼叫
  • 父類的私有屬性、方法不能繼承,即__(雙下劃線)開頭的屬性名和方法名`
  • 子類可以呼叫super().init()的方式初始化父類

① 單繼承

所謂的單繼承就是隻繼承一個父類,程式碼示例如下:

class Bird:
    def __init__(self, name):
        self.name = name
    def can_fly(self, can):
        self.can = can
    def __str__(self):
        return self.name + ("能夠" if self.can == True else "不能夠") + "飛翔。"

class Duck(Bird):
    # 子類擴充套件父類中的方法
    def set_color(self, color):
        self.color = color

    # 重寫父類中裡的方法
    def __str__(self):
        return self.color + "的" + self.name + ("能夠" if self.can == True else "不能夠") + "飛翔," + "會游泳。"

if __name__ == '__main__':
    duck = Duck("小鴨子")
    duck.can_fly(False)
    duck.set_color("黃色")
    print(duck)
複製程式碼

執行結果如下

黃色的小鴨子不能夠飛翔,會游泳。
複製程式碼

另外要注意,父類是無法呼叫子類方法的,比如下面的程式碼:

bird = Bird('鳥')
bird.can_swin()
複製程式碼

執行後直接報錯

Traceback (most recent call last):
  File "/Users/jay/Project/Python/Book/Chapter 9/9_8.py", line 33in <module>
    bird.can_swin()
AttributeError: 'Bird' object has no attribute 'can_swin'
複製程式碼

② 多繼承

多繼承就是「同時繼承多個父類的屬性與方法」,多個父類間用逗號隔開,另外要注意如果父類們中有相同的方法,呼叫的順序是:誰在前面先呼叫那個父類中的方法,比如有class Person(Name, Sex,Age),三個父類裡都有一個show的方法,那麼子類呼叫的是Name裡的show()!

你可以通過內建屬性__mro__檢視物件搜尋方法時的先後順序。另外,如果不是非得用多繼承不可的話,應該儘量避免使用它,有時會出現一些不可遇見的BUG。多繼承的程式碼示例如下:

class A:
    def show_A(self):
        print('父類A')

class B:
    def show_B(self):
        print('父類B')

# 定義一個子類,繼承A和B類
class C(A, B):
    def show_C(self):
        print('子類C')

if __name__ == '__main__':
    c = C()
    c.show_A()
    c.show_B()
    c.show_C()
複製程式碼

執行結果如下

父類A
父類B
子類C
複製程式碼

4、組合

多繼承的一個替代方案就是通過組合的方式,「把需要用到的類丟到組合類中例項化」,程式碼示例如下:

class Book:
    def __init__(self, num):
        self.num = num

class Phone:
    def __init__(self, num):
        self.num = num
class Wallet:
    def __init__(self, num):
        self.num = num

class Bag:
    def __init__(self, x, y, z):
        self.book = Book(x)
        self.phone = Phone(y)
        self.wallet = Wallet(z)
    def show_bag(self):
        print("您的揹包裡有:【書本】* %d 【手機】* %d 【錢包】* %d" %
(self.book.num, self.phone.num, self.wallet.num))

if __name__ == '__main__':
    bag = Bag(321)
    bag.show_bag()
複製程式碼

執行結果如下

您的揹包裡有:【書本】* 3 【手機】* 2 【錢包】* 1
複製程式碼

5、物件相關的內建函式

Python中還為我們提供了一些與物件相關的內建函式,如下表所示:

函式 作用
issubclass(class, classinfo) 如果第一個引數是第二個引數的子類,返回True,
否則返回False
isinstance(object, classinfo) 如果第一個引數是第二個引數的例項物件,返回True,
否則返回False
hasattr(object, name) 測試一個物件中是否有指定的屬性,屬性名要用引號
括著!
getattr(object, name, [,default]) 返回物件的指定屬性值,不存在返回default值,
沒設會報ArttributeError異常
setattr(object, name, value) 設定物件中指定屬性的值,屬性不存在會新建並賦值
delattr(object, name) 刪除物件中的指定屬性的值,不存在會報ArttributeError異常
property(fget,fset,fdel,doc) 返回一個可以設定屬性的屬性

如果本文對你有所幫助,歡迎
留言,點贊,轉發
素質三連,謝謝?~


相關文章