草根學Python(九) 物件導向

兩點水發表於2019-03-02

前言

這篇寫的很糾結,不過還是寫完了。弄了個很遜的公眾號,如果對本文有興趣,可以關注下公眾號喔,會持續更新。

公眾號
公眾號

目錄

草根學Python(九)物件導向
草根學Python(九)物件導向

一、物件導向的概念

Python 是一門物件導向的語言, 物件導向是一種抽象,抽象是指用分類的眼光去看世界的一種方法。 用 JAVA 的程式設計思想來說就是:萬事萬物皆物件。也就是說在物件導向中,把構成問題事務分解成各個物件。

物件導向有三大特性,封裝、繼承和多型。

1、物件導向的兩個基本概念

用來描述具有相同的屬性和方法的物件的集合。它定義了該集合中每個物件所共有的屬性和方法。物件是類的例項。

  • 物件

通過類定義的資料結構例項

2、物件導向的三大特性

  • 繼承

即一個派生類(derived class)繼承基類(base class)的欄位和方法。繼承也允許把一個派生類的物件作為一個基類物件對待。

例如:一個 Dog 型別的物件派生自 Animal 類,這是模擬"是一個(is-a)"關係(例圖,Dog 是一個 Animal )。

  • 多型

它是指對不同型別的變數進行相同的操作,它會根據物件(或類)型別的不同而表現出不同的行為。

  • 封裝性

“封裝”就是將抽象得到的資料和行為(或功能)相結合,形成一個有機的整體(即類);封裝的目的是增強安全性和簡化程式設計,使用者不必瞭解具體的實現細節,而只是要通過外部介面,一特定的訪問許可權來使用類的成員。

二、類

1、定義類

類定義語法格式如下:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>複製程式碼

一個類也是由屬性和方法組成的,有些時候我們定義類的時候需要設定類的屬性,因此這就需要構造函

類的建構函式如下:

def __init__(self,[...):複製程式碼

類定義了 init() 方法的話,類的例項化操作會自動呼叫 init() 方法。

那麼如建構函式相對應的是解構函式,理所當然,一個類建立的時候我們可以用過建構函式設定屬性,那麼當一個類銷燬的時候,就會呼叫解構函式。

解構函式語法如下:

def __del__(self,[...):複製程式碼

仔細觀察的童鞋都會發現,類的方法與普通的函式有一個特別的區別,它們必須有一個額外的第一個引數名稱, 按照慣例它的名稱是 self。

那麼這個 self 代表什麼呢?

我們可以看下例項,通過例項來找出答案:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class Test:
    def prt(self):
        print(self)
        print(self.__class__)

t = Test()
t.prt()複製程式碼

觀察輸出的結果:

Python self
Python self

從執行結果可以很明顯的看出,self 代表的是類的例項,輸出的是當前物件的地址,而 self.__class__ 則指向類。

當然 self 不是 python 關鍵字,也就是說我們把他換成其他的字元也是可以正常執行的。只不過我們習慣使用 self

2、Python 定義類的歷史遺留問題

Python 在版本的迭代中,有一個關於類的歷史遺留問題,就是新式類和舊式類的問題,具體先看以下的程式碼:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# 舊式類
class OldClass:
    pass

# 新式類
class NewClass(object):
    pass複製程式碼

可以看到,這裡使用了兩者中不同的方式定義類,可以看到最大的不同就是,新式類繼承了object 類,在 Python2 中,我們定義類的時候最好定義新式類,當然在 Python3 中不存在這個問題了,因為 Python3 中所有類都是新式類。

那麼新式類和舊式類有什麼區別呢?

執行下下面的那段程式碼:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# 舊式類
class OldClass:
    def __init__(self, account, name):
        self.account = account;
        self.name = name;


# 新式類
class NewClass(object):
    def __init__(self, account, name):
        self.account = account;
        self.name = name;


if __name__ == '__main__':
    old_class = OldClass(111111, 'OldClass')
    print(old_class)
    print(type(old_class))
    print(dir(old_class))
    print('\n')
    new_class=NewClass(222222,'NewClass')
    print(new_class)
    print(type(new_class))
    print(dir(new_class))複製程式碼

仔細觀察輸出的結果,對比一下,就能觀察出來,注意喔,Pyhton3 中輸出的結果是一模一樣的,因為Python3 中沒有新式類舊式類的問題。

三、類的屬性

1、直接在類中定義屬性

定義類的屬性,當然最簡單最直接的就是在類中定義,例如:

class UserInfo(object):
    name='兩點水'複製程式碼

2、在建構函式中定義屬性

故名思議,就是在構造物件的時候,對屬性進行定義。

class UserInfo(object):
    def __init__(self,name):
        self.name=name複製程式碼

3、屬性的訪問控制

在 Java 中,有 public (公共)屬性 和 private (私有)屬性,這可以對屬性進行訪問控制。那麼在 Python 中有沒有屬性的訪問控制呢?

一般情況下,我們會使用 __private_attrs 兩個下劃線開頭,宣告該屬性為私有,不能在類地外部被使用或直接訪問。在類內部的方法中使用時 self.__private_attrs

為什麼只能說一般情況下呢?因為實際上, Python 中是沒有提供私有屬性等功能的。但是 Python 對屬性的訪問控制是靠程式設計師自覺的。為什麼這麼說呢?看看下面的示例:

Python 屬性訪問控制
Python 屬性訪問控制

仔細看圖片,為什麼說雙下劃線不是真正的私有屬性呢?我們看下下面的例子,用下面的例子來驗證:


#!/usr/bin/env python
# -*- coding: UTF-8 -*-

class UserInfo(object):
    def __init__(self, name, age, account):
        self.name = name
        self._age = age
        self.__account = account

    def get_account(self):
        return self.__account


if __name__ == '__main__':
    userInfo = UserInfo('兩點水', 23, 347073565);
    # 列印所有屬性
    print(dir(userInfo))
    # 列印建構函式中的屬性
    print(userInfo.__dict__)
    print(userInfo.get_account())
    # 用於驗證雙下劃線是否是真正的私有屬性
    print(userInfo._UserInfo__account)複製程式碼

輸出的結果如下圖:

Python 屬性訪問控制
Python 屬性訪問控制

四、類的方法

1、類專有的方法

一個類建立的時候,就會包含一些方法,主要有以下方法:

類的專有方法:

方法 說明
__init__ 建構函式,在生成物件時呼叫
__del__ 解構函式,釋放物件時使用
__repr__ 列印,轉換
__setitem__ 按照索引賦值
__getitem__ 按照索引獲取值
__len__ 獲得長度
__cmp__ 比較運算
__call__ 函式呼叫
__add__ 加運算
__sub__ 減運算
__mul__ 乘運算
__div__ 除運算
__mod__ 求餘運算
__pow__ 乘方

當然有些時候我們需要獲取類的相關資訊,我們可以使用如下的方法:

  • type(obj):來獲取物件的相應型別;
  • isinstance(obj, type):判斷物件是否為指定的 type 型別的例項;
  • hasattr(obj, attr):判斷物件是否具有指定屬性/方法;
  • getattr(obj, attr[, default]) 獲取屬性/方法的值, 要是沒有對應的屬性則返回 default 值(前提是設定了 default),否則會丟擲 AttributeError 異常;
  • setattr(obj, attr, value):設定該屬性/方法的值,類似於 obj.attr=value;
  • dir(obj):可以獲取相應物件的所有屬性和方法名的列表:

2、方法的訪問控制

其實我們也可以把方法看成是類的屬性的,那麼方法的訪問控制也是跟屬性是一樣的,也是沒有實質上的私有方法。一切都是靠程式設計師自覺遵守 Python 的程式設計規範。

示例如下,具體規則也是跟屬性一樣的,

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

class User(object):
    def upgrade(self):
        pass

    def _buy_equipment(self):
        pass

    def __pk(self):
        pass複製程式碼

3、方法的裝飾器

  • @classmethod
    呼叫的時候直接使用類名類呼叫,而不是某個物件

  • @property
    可以像訪問屬性一樣呼叫方法

具體的使用看下例項:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

class UserInfo(object):
    lv = 5

    def __init__(self, name, age, account):
        self.name = name
        self._age = age
        self.__account = account

    def get_account(self):
        return self.__account

    @classmethod
    def get_name(cls):
        return cls.lv

    @property
    def get_age(self):
        return self._age


if __name__ == '__main__':
    userInfo = UserInfo('兩點水', 23, 347073565);
    # 列印所有屬性
    print(dir(userInfo))
    # 列印建構函式中的屬性
    print(userInfo.__dict__)
    # 直接使用類名類呼叫,而不是某個物件
    print(UserInfo.lv)
    # 像訪問屬性一樣呼叫方法(注意看get_age是沒有括號的)
    print(userInfo.get_age)複製程式碼

執行的結果:

Python 方法的裝飾器
Python 方法的裝飾器

五、類的繼承

1、定義類的繼承

首先我們來看下類的繼承的基本語法:

class ClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>複製程式碼

在定義類的時候,可以在括號裡寫繼承的類,一開始也提到過,如果不用繼承類的時候,也要寫繼承 object 類,因為在 Python 中 object 類是一切類的父類。

當然上面的是單繼承,Python 也是支援多繼承的,具體的語法如下:

class ClassName(Base1,Base2,Base3):
    <statement-1>
    .
    .
    .
    <statement-N>複製程式碼

多繼承有一點需要注意的:若是父類中有相同的方法名,而在子類使用時未指定,python 在圓括號中父類的順序,從左至右搜尋 , 即方法在子類中未找到時,從左到右查詢父類中是否包含方法。

那麼繼承的子類可以幹什麼呢?

繼承的子類的好處:

  • 會繼承父類的屬性和方法
  • 可以自己定義,覆蓋父類的屬性和方法

2、呼叫父類的方法

一個類繼承了父類後,可以直接呼叫父類的方法的,比如下面的例子,UserInfo2 繼承自父類 UserInfo ,可以直接呼叫父類的 get_account 方法。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

class UserInfo(object):
    lv = 5

    def __init__(self, name, age, account):
        self.name = name
        self._age = age
        self.__account = account

    def get_account(self):
        return self.__account


class UserInfo2(UserInfo):
    pass


if __name__ == '__main__':
    userInfo2 = UserInfo2('兩點水', 23, 347073565);
    print(userInfo2.get_account())複製程式碼

3、父類方法的重寫

當然,也可以重寫父類的方法。

示例:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class UserInfo(object):
    lv = 5

    def __init__(self, name, age, account):
        self.name = name
        self._age = age
        self.__account = account

    def get_account(self):
        return self.__account

    @classmethod
    def get_name(cls):
        return cls.lv

    @property
    def get_age(self):
        return self._age


class UserInfo2(UserInfo):
    def __init__(self, name, age, account, sex):
        super(UserInfo2, self).__init__(name, age, account)
        self.sex = sex;


if __name__ == '__main__':
    userInfo2 = UserInfo2('兩點水', 23, 347073565, '男');
    # 列印所有屬性
    print(dir(userInfo2))
    # 列印建構函式中的屬性
    print(userInfo2.__dict__)
    print(UserInfo2.get_name())複製程式碼

最後列印的結果:

Python 類的繼承
Python 類的繼承

這裡就是重寫了父類的建構函式。

3、子類的型別判斷

對於 class 的繼承關係來說,有些時候我們需要判斷 class 的型別,該怎麼辦呢?

可以使用 isinstance() 函式,

一個例子就能看懂 isinstance() 函式的用法了。

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class User1(object):
    pass


class User2(User1):
    pass


class User3(User2):
    pass


if __name__ == '__main__':
    user1 = User1()
    user2 = User2()
    user3 = User3()
    # isinstance()就可以告訴我們,一個物件是否是某種型別
    print(isinstance(user3, User2))
    print(isinstance(user3, User1))
    print(isinstance(user3, User3))
    # 基本型別也可以用isinstance()判斷
    print(isinstance('兩點水', str))
    print(isinstance(347073565, int))
    print(isinstance(347073565, str))複製程式碼

輸出的結果如下:

True
True
True
True
True
False複製程式碼

可以看到 isinstance() 不僅可以告訴我們,一個物件是否是某種型別,也可以用於基本型別的判斷。

六、類的多型

多型的概念其實不難理解,它是指對不同型別的變數進行相同的操作,它會根據物件(或類)型別的不同而表現出不同的行為。

事實上,我們經常用到多型的性質,比如:

>>> 1 + 2
3
>>> 'a' + 'b'
'ab'複製程式碼

可以看到,我們對兩個整數進行 + 操作,會返回它們的和,對兩個字元進行相同的 + 操作,會返回拼接後的字串。也就是說,不同型別的物件對同一訊息會作出不同的響應。

看下面的例項,來了解多型:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class User(object):
    def __init__(self, name):
        self.name = name

    def printUser(self):
        print('Hello !' + self.name)


class UserVip(User):
    def printUser(self):
        print('Hello ! 尊敬的Vip使用者:' + self.name)


class UserGeneral(User):
    def printUser(self):
        print('Hello ! 尊敬的使用者:' + self.name)


def printUserInfo(user):
    user.printUser()


if __name__ == '__main__':
    userVip = UserVip('兩點水')
    printUserInfo(userVip)
    userGeneral = UserGeneral('水水水')
    printUserInfo(userGeneral)複製程式碼

輸出的結果:

Hello ! 尊敬的Vip使用者:兩點水
Hello ! 尊敬的使用者:水水水複製程式碼

可以看到,userVip 和 userGeneral 是兩個不同的物件,對它們呼叫 printUserInfo 方法,它們會自動呼叫實際型別的 printUser 方法,作出不同的響應。這就是多型的魅力。

要注意喔,有了繼承,才有了多型,也會有不同類的物件對同一訊息會作出不同的相應。

最後,本章的所有程式碼都可以在 github.com/TwoWater/Py… 上面找到,文章的內容和原始檔都放在上面。同步更新到 Gitbooks。

相關文章