草根學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的簡單驗證碼識別