草根學Python(九) 面向對象

前言

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

目錄

一、面向對象的概念

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

面向對象有三大特性,封裝、繼承和多態。

1、面向對象的兩個基本概念

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

  • 對象

通過類定義的數據結構實例

2、面向對象的三大特性

  • 繼承

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

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

  • 多態

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

  • 封裝性

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

二、類

1、定義類

類定義語法格式如下:

class ClassName:n <statement-1>n .n .n .n <statement-N>n

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

類的構造函數如下:

def __init__(self,[...):n

類定義了 init() 方法的話,類的實例化操作會自動調用 init() 方法。

那麼如構造函數相對應的是析構函數,理所當然,一個類創建的時候我們可以用過構造函數設置屬性,那麼當一個類銷毀的時候,就會調用析構函數。

析構函數語法如下:

def __del__(self,[...):n

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

那麼這個 self 代表什麼呢?

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

#!/usr/bin/env python3n# -*- coding: UTF-8 -*-nnclass Test:n def prt(self):n print(self)n print(self.__class__)nnt = Test()nt.prt()n

觀察輸出的結果:

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

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

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

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

#!/usr/bin/env pythonn# -*- coding: UTF-8 -*-nn# 舊式類nclass OldClass:n passnn# 新式類nclass NewClass(object):n passn

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

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

運行下下面的那段代碼:

#!/usr/bin/env pythonn# -*- coding: UTF-8 -*-nn# 舊式類nclass OldClass:n def __init__(self, account, name):n self.account = account;n self.name = name;nnn# 新式類nclass NewClass(object):n def __init__(self, account, name):n self.account = account;n self.name = name;nnnif __name__ == __main__:n old_class = OldClass(111111, OldClass)n print(old_class)n print(type(old_class))n print(dir(old_class))n print(n)n new_class=NewClass(222222,NewClass)n print(new_class)n print(type(new_class))n print(dir(new_class))n

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

三、類的屬性

1、直接在類中定義屬性

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

class UserInfo(object):n name=兩點水n

2、在構造函數中定義屬性

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

class UserInfo(object):n def __init__(self,name):n self.name=namen

3、屬性的訪問控制

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

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

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

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

#!/usr/bin/env pythonn# -*- coding: UTF-8 -*-nnclass UserInfo(object):n def __init__(self, name, age, account):n self.name = namen self._age = agen self.__account = accountnn def get_account(self):n return self.__accountnnnif __name__ == __main__:n userInfo = UserInfo(兩點水, 23, 347073565);n # 列印所有屬性n print(dir(userInfo))n # 列印構造函數中的屬性n print(userInfo.__dict__)n print(userInfo.get_account())n # 用於驗證雙下劃線是否是真正的私有屬性n print(userInfo._UserInfo__account)n

輸出的結果如下圖:

四、類的方法

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 pythonn# -*- coding: UTF-8 -*-nnclass User(object):n def upgrade(self):n passnn def _buy_equipment(self):n passnn def __pk(self):n passn

3、方法的裝飾器

  • @classmethod

    調用的時候直接使用類名類調用,而不是某個對象
  • @property

    可以像訪問屬性一樣調用方法

具體的使用看下實例:

#!/usr/bin/env pythonn# -*- coding: UTF-8 -*-nnclass UserInfo(object):n lv = 5nn def __init__(self, name, age, account):n self.name = namen self._age = agen self.__account = accountnn def get_account(self):n return self.__accountnn @classmethodn def get_name(cls):n return cls.lvnn @propertyn def get_age(self):n return self._agennnif __name__ == __main__:n userInfo = UserInfo(兩點水, 23, 347073565);n # 列印所有屬性n print(dir(userInfo))n # 列印構造函數中的屬性n print(userInfo.__dict__)n # 直接使用類名類調用,而不是某個對象n print(UserInfo.lv)n # 像訪問屬性一樣調用方法(注意看get_age是沒有括弧的)n print(userInfo.get_age)n

運行的結果:

五、類的繼承

1、定義類的繼承

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

class ClassName(BaseClassName):n <statement-1>n .n .n .n <statement-N>n

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

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

class ClassName(Base1,Base2,Base3):n <statement-1>n .n .n .n <statement-N>n

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

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

繼承的子類的好處:

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

2、調用父類的方法

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

#!/usr/bin/env pythonn# -*- coding: UTF-8 -*-nnclass UserInfo(object):n lv = 5nn def __init__(self, name, age, account):n self.name = namen self._age = agen self.__account = accountnn def get_account(self):n return self.__accountnnnclass UserInfo2(UserInfo):n passnnnif __name__ == __main__:n userInfo2 = UserInfo2(兩點水, 23, 347073565);n print(userInfo2.get_account())n

3、父類方法的重寫

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

示例:

#!/usr/bin/env python3n# -*- coding: UTF-8 -*-nnclass UserInfo(object):n lv = 5nn def __init__(self, name, age, account):n self.name = namen self._age = agen self.__account = accountnn def get_account(self):n return self.__accountnn @classmethodn def get_name(cls):n return cls.lvnn @propertyn def get_age(self):n return self._agennnclass UserInfo2(UserInfo):n def __init__(self, name, age, account, sex):n super(UserInfo2, self).__init__(name, age, account)n self.sex = sex;nnnif __name__ == __main__:n userInfo2 = UserInfo2(兩點水, 23, 347073565, );n # 列印所有屬性n print(dir(userInfo2))n # 列印構造函數中的屬性n print(userInfo2.__dict__)n print(UserInfo2.get_name())n

最後列印的結果:

這裡就是重寫了父類的構造函數。

3、子類的類型判斷

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

可以使用 isinstance() 函數,

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

#!/usr/bin/env python3n# -*- coding: UTF-8 -*-nnclass User1(object):n passnnnclass User2(User1):n passnnnclass User3(User2):n passnnnif __name__ == __main__:n user1 = User1()n user2 = User2()n user3 = User3()n # isinstance()就可以告訴我們,一個對象是否是某種類型n print(isinstance(user3, User2))n print(isinstance(user3, User1))n print(isinstance(user3, User3))n # 基本類型也可以用isinstance()判斷n print(isinstance(兩點水, str))n print(isinstance(347073565, int))n print(isinstance(347073565, str))n

輸出的結果如下:

TruenTruenTruenTruenTruenFalsen

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

六、類的多態

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

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

>>> 1 + 2n3n>>> a + bnabn

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

看下面的實例,來了解多態:

#!/usr/bin/env python3n# -*- coding: UTF-8 -*-nnclass User(object):n def __init__(self, name):n self.name = namenn def printUser(self):n print(Hello ! + self.name)nnnclass UserVip(User):n def printUser(self):n print(Hello ! 尊敬的Vip用戶: + self.name)nnnclass UserGeneral(User):n def printUser(self):n print(Hello ! 尊敬的用戶: + self.name)nnndef printUserInfo(user):n user.printUser()nnnif __name__ == __main__:n userVip = UserVip(兩點水)n printUserInfo(userVip)n userGeneral = UserGeneral(水水水)n printUserInfo(userGeneral)n

輸出的結果:

Hello ! 尊敬的Vip用戶:兩點水nHello ! 尊敬的用戶:水水水n

可以看到,userVip 和 userGeneral 是兩個不同的對象,對它們調用 printUserInfo 方法,它們會自動調用實際類型的 printUser 方法,作出不同的響應。這就是多態的魅力。

要注意喔,有了繼承,才有了多態,也會有不同類的對象對同一消息會作出不同的相應。

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

推薦閱讀:

對Github上Python開源項目進行分析時遇到的一個AttributeError的解釋及其解決方法。
基於pytesseract的簡單驗證碼識別

TAG:Python | Python入门 | Python教程 |